最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

15-對part13-interrupt的翻譯和搬運(yùn)

2023-08-19 21:19 作者:Xdz-2333  | 我要投稿

非黑色字體均為我自己添加

圖均為原作中的圖

原文放在文章末尾

什么是中斷

????????如果你在這些教程中花了一些時(shí)間看藍(lán)牙代碼的話,你會注意到我們總是在"輪詢"更新.實(shí)際上,在part11-breakout中我們呢綁定了整個核,只是為了等待某種事情發(fā)生.很明顯這不能最好的利用CPU的時(shí)間.幸運(yùn)的是,這個世界用中斷在幾十年前解決了我們的問題.

????????理想情況下,我們想要告訴某個硬件做某件事,它只是在完成的時(shí)候通知我們所以我們可以繼續(xù)把主要的時(shí)間用于我們的生活.這些通知也被叫為中斷因?yàn)樗鼈兇驍嗔苏5某绦驁?zhí)行并且迫使立即CPU運(yùn)行中斷句柄.

最簡單的能發(fā)起中斷的設(shè)備

????????我們最有用的一塊內(nèi)置的硬件就是系統(tǒng)時(shí)鐘,它可以被編程為以一定的間隔中斷,比如每秒.如果在一個核上對多個進(jìn)程進(jìn)行規(guī)劃來運(yùn)行,比如使用時(shí)間片劃分,你就會需要它.

????????然而對于現(xiàn)在,我們只是要對如何對計(jì)時(shí)器進(jìn)行編程以及相應(yīng)它的中斷進(jìn)行學(xué)習(xí).

代碼庫

????????讓我們快速解釋一下你在part13-interrupts所看到的代碼:

  • boot/?:與part12-wpt中的boot代碼目錄

  • include/?:一些直接從part11-multicore所復(fù)制來的有用的頭文件

  • lib/?:一些直接從part11-multicore所復(fù)制來的有用的庫

  • kernel/?:?這個教程中我們唯一需要關(guān)注的新的代碼

????????請注意:我也做了一些工作來理清Makefile,并且保持了目錄結(jié)構(gòu),但并沒有什么大改動.

新代碼

????????你可以看到來自part10-multicore的大量的kernel.c的代碼,除了我們不是展現(xiàn)四個核并且播放音樂,而是現(xiàn)在只是用0和1,另外,使用兩個定時(shí)器中斷來展現(xiàn)我們的四個進(jìn)度條.因此, main()例程啟動核1,設(shè)置定時(shí)器,然后最終啟動核0的工作負(fù)載.

????????使用這些調(diào)用來設(shè)置定時(shí)器:

irq_init_vectors();

enable_interrupt_controller();

irq_enable();

timer_init();

初始化異常向量列表

????????實(shí)際上中斷時(shí)異常的一種更特殊的類型 -- 當(dāng)"舉起"的時(shí)候,需要立即被處理器關(guān)注的東西.一個完美的例子:當(dāng)壞代碼嘗試做一些"不可能"的事情(比如除以零)的時(shí)候,這時(shí)一個異常就會發(fā)生.CPU需要知道它發(fā)生后如何響應(yīng),比如跳轉(zhuǎn)到能優(yōu)雅的處理這些異常的代碼的地址并且運(yùn)行,比如通過打印錯誤到屏幕.這些地址被存儲在一個異常向量列表中.

????????irqentry.S 設(shè)置了一個列表,叫作 vector,它包含了獨(dú)立的向量項(xiàng).這些向量項(xiàng)只是跳轉(zhuǎn)到處理代碼程序的指令.

????????在kernel.c中的main()調(diào)用irq_init_vectors()的過程中CPU被告訴了哪里異常向量表被存儲.你會在util.S里面找到代碼:

irq_init_vectors:

? ? adr x0, vectors

? ? msr vbar_el1, x0

? ? ret

????????它只是設(shè)置了向量基地址寄存器到向量列表的地址.

中斷處理

????????為了這篇教程的目的,我們唯一真正關(guān)心的向量項(xiàng)就是 handle_el1_irq.它是一個任何來自?EL1(內(nèi)核處理中斷)的中斷請求(IRQ)的通用處理程序.

????????如果你想要更深的理解,我高度推薦閱讀 s-matyukevich 的工作(https://github.com/s-matyukevich/raspberry-pi-os/blob/master/docs/lesson03/rpi-os.md)

handle_el1_irq:

????????????????kernel_entry

????????????????bl? ? ? handle_irq

????????????????kernel_exit

????????簡單來講, kernel_entry 保存了中斷處理程序運(yùn)行之前的寄存器狀態(tài),并且 kernel_exit 在返回時(shí)回復(fù)了寄存器狀態(tài).因?yàn)槲覀冋谥袛嗾5某绦驁?zhí)行,我們希望確保把東西恢復(fù)成原來的樣子,這樣我們內(nèi)核代碼回復(fù)的時(shí)候不會有不可預(yù)料的事情發(fā)生.

????????在中間我們只是調(diào)用一個叫?handle_irq() 的函數(shù),它使用C代碼編寫,在 irq.c中.它的目的是更仔細(xì)的查看中斷請求,指出哪個設(shè)備負(fù)責(zé)產(chǎn)生的中斷,并且運(yùn)行正確的子處理程序:

void handle_irq() {

? ? unsigned int irq = REGS_IRQ->irq0_pending_0;


? ? while(irq) {

? ? ? ? if (irq & SYS_TIMER_IRQ_1) {

? ? ? ? ? ? irq &= ~SYS_TIMER_IRQ_1;


? ? ? ? ? ? handle_timer_1();

? ? ? ? }


? ? ? ? if (irq & SYS_TIMER_IRQ_3) {

? ? ? ? ? ? irq &= ~SYS_TIMER_IRQ_3;


? ? ? ? ? ? handle_timer_3();

? ? ? ? }

? ? }

}

????????就像你看到的那樣,我們在這個程序中處理兩個不同的定時(shí)器中斷.實(shí)際上,handle_timer_1() 和?handle_timer_3() 在kernel.c中實(shí)現(xiàn),并且服務(wù)于通過遞增進(jìn)度計(jì)數(shù)器來展現(xiàn)定時(shí)器被啟動了,并且更新代表它的值的圖形.定時(shí)器3被設(shè)置為比定時(shí)器1進(jìn)展塊4倍.

中斷控制器

????????定時(shí)器是一個當(dāng)中斷發(fā)生時(shí)負(fù)責(zé)告訴CPU的硬件.我們可以使用中斷控制器作為門控使用來允許/阻止(或者說使能/失能)中斷.我們同樣使用它來指出哪個設(shè)備產(chǎn)生了中斷,就像我們在hand_irq().中做的那樣.

void enable_interrupt_controller() {

? ? REGS_IRQ->irq0_enable_0 = SYS_TIMER_IRQ_1 | SYS_TIMER_IRQ_3;

}


void disable_interrupt_controller() {

? ? REGS_IRQ->irq0_enable_0 = 0;

}

屏蔽/揭露中斷

????????為了開始接收中斷,我們需要再走一步:揭露(原文為unmasking)所有類型的中斷.

????????掩膜(原文為masking)是一種由CPU使用的技術(shù),來防止特定的代碼片段被中斷.它用來保護(hù)重要的必須完成的代碼.想想一下如果我們的kernel_entry代碼(它保存了寄存器狀態(tài))被中途中斷了!在這個例子里面,寄存器狀態(tài)將會被覆蓋并且丟失.這就是為什么當(dāng)異常處理程序執(zhí)行的時(shí)候CPU會屏蔽所有中斷.

????? ? util.S中的 irq_enable 和 irq_disable 函數(shù)負(fù)責(zé)屏蔽和揭開中斷.

.globl irq_enable

irq_enable:

? ? msr daifclr, #2

? ? ret


.globl irq_disable

irq_disable:

? ? msr daifset, #2

? ? ret

????????一旦irq_enable()從 kernel.c 里的?main() 當(dāng)中被調(diào)用,當(dāng)定時(shí)器中斷觸發(fā)時(shí)定時(shí)器處理程序就會運(yùn)行.嗯,有點(diǎn)……!

初始化系統(tǒng)定時(shí)器

????????我們?nèi)匀恍枰跏蓟〞r(shí)器.

????????樹莓派的系統(tǒng)定時(shí)器簡單的不能再簡單.它有一個每個時(shí)鐘周期都遞增1的計(jì)時(shí)器.然后它有4條中斷線,(0和2為GPU保留,1和3被我們的教程使用!)和4個比較寄存器.當(dāng)計(jì)數(shù)器的值與其中一個比較寄存器的值相同到時(shí)候,相應(yīng)的中斷啟動了.

????????所以在我們接收任何定時(shí)器中斷之前,我們必須設(shè)置正確的比較寄存器的值為一個非零值. timer_init()函數(shù)(從kernel.c里面的main()函數(shù)調(diào)用)得到現(xiàn)有的定時(shí)器的值,添加定時(shí)器的間隔,并且設(shè)置比較寄存器為它們的總數(shù),所以當(dāng)正確的時(shí)鐘數(shù)值傳遞時(shí),中斷被啟動.它們同時(shí)為定時(shí)器1和定時(shí)器3做到這個,讓定時(shí)器3以4倍速度運(yùn)行.

處理定時(shí)器中斷

????????這是最簡單的位.

????????我們更新比較寄存器這樣下一個中斷就會在相同的間隔再次觸發(fā).重要的是我們通過設(shè)置狀態(tài)寄存器來確認(rèn)中斷

????????然后我們更新屏幕來展現(xiàn)我們的進(jìn)度.

????????和…您看!您像專業(yè)人士一樣處理兩個系統(tǒng)計(jì)時(shí)器中斷!


原作者屏幕效果圖


原文如下


What are interrupts?

If you've spent any time looking at the Bluetooth code in these tutorials, you'll notice we're always "polling" for updates. In fact, in _part11-breakout-smp_ we tie up an entire core just waiting around for something to happen. This clearly isn't the best use of CPU time. Fortunately, the world solved that problem for us years ago with _interrupts_.


Ideally, we want to tell a piece of hardware to do something and have it simply notify us when the work is complete so we can move on with our lives in the meantime. These notifications are known as _interrupts_ because they disrupt normal program execution and force the CPU to immediately run an _interrupt handler_.


The simplest device that interrupts

One useful piece of built-in hardware is a system timer, which can be programmed to interrupt at regular intervals e.g. every second. You'll need this if you want to schedule multiple processes to run on a single core e.g. using the principle of time slicing.


For now, however, we're simply going to learn how to program the timer and respond to its interrupts.


The codebase

Let me quickly explain what you're looking at in the _part13-interrupts_ code:


?* _boot/_ : the same _boot_ code directory from _part12-wgt_

?* _include/_ : some useful headers copied directly from _part11-multicore_?

?* _lib/_ : some useful libraries copied directly from _part11-multicore_

?* _kernel/_ : the only new code we need to concern ourselves with in this tutorial


Please note: I have also done some work to tidy up the _Makefile_ and respect this directory structure, but nothing to write home about!


The new code

You'll recognise a lot of _kernel.c_ from _part10-multicore_, except instead of showing four cores at work and playing sound, we're now only using core 0 & 1 and, in addition, making use of two timer interrupts to show four progress bars. So, the `main()` routine kicks off core 1, sets up the timers, and then finally kicks off core 0's workload.


The timers are set up using these calls:


```c

irq_init_vectors();

enable_interrupt_controller();

irq_enable();

timer_init();

```


Initialising the exception vector table

In fact, interrupts are a more specific kind of _exception_ - something that, when "raised", needs the immediate attention of the processor. A perfect example of when an exception might occur is when bad code tries to do something "impossible" e.g. divide by zero. The CPU needs to know how to respond when/if this happens i.e. jump to an address of some code to run which handles this exception gracefully e.g. by printing an error to the screen. These addresses are stored in an _exception vector table_.


_irqentry.S_ sets up a list called `vectors` which contains individual _vector entries_. These vector entries are simply jump instructions to handler code.


The CPU is told where this exception vector table is stored during the `irq_init_vectors()` call from `main()` in _kernel.c_. You'll find this code in _utils.S_:


```c

irq_init_vectors:

? ? adr x0, vectors

? ? msr vbar_el1, x0

? ? ret

```


It simply sets the Vector Base Address Register to the address of the `vectors` list.


Interrupt handling

The only vector entry we really care about for the purposes of this tutorial is `handle_el1_irq`. This is a generic handler for any interrupt request (IRQ) that comes in at EL1 (kernel execution level).


If you do want a deeper understanding, I highly recommend reading s-matyukevich's work [here](https://github.com/s-matyukevich/raspberry-pi-os/blob/master/docs/lesson03/rpi-os.md).


```c

handle_el1_irq:

kernel_entry

bl? ? ? handle_irq

kernel_exit

```


Put simply, `kernel_entry` saves the register state before the interrupt handler runs, and `kernel_exit` restores this register state before we return. As we're _interrupting_ normal program execution, we want to be sure that we put things back to how they were so that nothing unpredictable happens as our kernel code resumes.


In the middle we simply call a function called `handle_irq()` which is written in the C language in _irq.c_. Its purpose is to look more closely at the interrupt request, figure out what device was responsible for generating an interrupt, and run the right sub-handler:


```c

void handle_irq() {

? ? unsigned int irq = REGS_IRQ->irq0_pending_0;


? ? while(irq) {

? ? ? ? if (irq & SYS_TIMER_IRQ_1) {

? ? ? ? ? ? irq &= ~SYS_TIMER_IRQ_1;


? ? ? ? ? ? handle_timer_1();

? ? ? ? }


? ? ? ? if (irq & SYS_TIMER_IRQ_3) {

? ? ? ? ? ? irq &= ~SYS_TIMER_IRQ_3;


? ? ? ? ? ? handle_timer_3();

? ? ? ? }

? ? }

}

```


As you can see, we're handling two different timer interrupts in this code. In fact, `handle_timer_1()` and `handle_timer_3()` are implemented in _kernel.c_ and serve to demonstrate that the timer has fired by incrementing a progress counter and updating a graphical representation of its value. Timer 3 is configured to progress at 4 times the speed of Timer 1.


The interrupt controller

The interrupt controller is the hardware responsible for telling the CPU about interrupts as they occur. We can use the interrupt controller to act as a gatekeeper and allow/block (or enable/disable) interrupts. We can also use it to figure out which device generated the interrupt, as we did in `handle_irq()`.


In `enable_interrupt_controller()`, called from `main()` in _kernel.c_, we allow the Timer 1 and Timer 3 interrupts through and in `disable_interrupt_controller()` we block all interrupts:


```c

void enable_interrupt_controller() {

? ? REGS_IRQ->irq0_enable_0 = SYS_TIMER_IRQ_1 | SYS_TIMER_IRQ_3;

}


void disable_interrupt_controller() {

? ? REGS_IRQ->irq0_enable_0 = 0;

}

```


Masking/unmasking interrupts

To begin receiving interrupts, we need to take one more step: unmasking all types of interrupts.


Masking is a technique used by the CPU to prevent a particular piece of code from being stopped in its tracks by an interrupt. It's used to protect important code that *must* complete. Imagine what would happen if our `kernel_entry` code (that saves register state) was interrupted halfway through! In this case, the register state would be overwritten and lost. This is why the CPU automatically masks all interrupts when an exception handler is executed.


The `irq_enable` and `irq_disable` functions in _utils.S_ are responsible for masking and unmasking interrupts:


```c

.globl irq_enable

irq_enable:

? ? msr daifclr, #2

? ? ret


.globl irq_disable

irq_disable:

? ? msr daifset, #2

? ? ret

```


As soon as `irq_enable()` is called from `main()` in _kernel.c_, the timer handler is run when the timer interrupt fires. Well, sort of...!


Initialising the system timer

We still need to initialise the timer.


The RPi4's system timer couldn't be simpler. It has a counter which increases by 1 with each clock tick. It then has 4 interrupt lines (0 & 2 reserved for the GPU, 1 & 3 used by us in this tutorial!) with 4 corresponding compare registers. When the value of the counter becomes equal to a value in one of the compare registers, the corresponding interrupt is fired.


So before we receive any timer interrupts, we must also set the right compare registers to have a non-zero value. The `timer_init()` function (called from `main()` in _kernel.c_) gets the current timer value, adds the timer interval and sets the compare register to that total, so when the right number of clock ticks pass, the interrupt fires. It does this for both Timer 1 and Timer 3, setting Timer 3 to run 4 times as fast.


Handling the timer interrupts

This is the simplest bit.


We update the compare register so the next interrupt will be generated after the same interval again. Importantly we then acknowledge the interrupt by setting the right bit of the Control Status register.


Then we update the screen to show our progress!


_And... hey presto! You're handling two system timer interrupts like a pro!_


![Timers firing on all cylinders on the Raspberry Pi 4](images/13-interrupts-running.jpg)



15-對part13-interrupt的翻譯和搬運(yùn)的評論 (共 條)

分享到微博請遵守國家法律
宽城| 蒲江县| 广宁县| 西充县| 漳州市| 贵港市| 阿瓦提县| 抚远县| 黄冈市| 景泰县| 寻乌县| 余干县| 青州市| 玛纳斯县| 黄冈市| 安溪县| 龙江县| 庆元县| 利川市| 万州区| 湘阴县| 襄樊市| 洪湖市| 华蓥市| 龙里县| 北碚区| 右玉县| 漯河市| 连江县| 佳木斯市| 康马县| 内江市| 垦利县| 兴城市| 青海省| 沅陵县| 本溪市| 龙门县| 峨山| 建阳市| 隆昌县|