15-对part13-interrupt的翻译和搬运

非黑色字体均为我自己添加
图均为原作中的图
原文放在文章末尾
什么是中断
如果你在这些教程中花了一些时间看蓝牙代码的话,你会注意到我们总是在"轮询"更新.实际上,在part11-breakout中我们呢绑定了整个核,只是为了等待某种事情发生.很明显这不能最好的利用CPU的时间.幸运的是,这个世界用中断在几十年前解决了我们的问题.
理想情况下,我们想要告诉某个硬件做某件事,它只是在完成的时候通知我们所以我们可以继续把主要的时间用于我们的生活.这些通知也被叫为中断因为它们打断了正常的程序执行并且迫使立即CPU运行中断句柄.
最简单的能发起中断的设备
我们最有用的一块内置的硬件就是系统时钟,它可以被编程为以一定的间隔中断,比如每秒.如果在一个核上对多个进程进行规划来运行,比如使用时间片划分,你就会需要它.
然而对于现在,我们只是要对如何对计时器进行编程以及相应它的中断进行学习.
代码库
让我们快速解释一下你在part13-interrupts所看到的代码:
boot/ :与part12-wpt中的boot代码目录
include/ :一些直接从part11-multicore所复制来的有用的头文件
lib/ :一些直接从part11-multicore所复制来的有用的库
kernel/ : 这个教程中我们唯一需要关注的新的代码
请注意:我也做了一些工作来理清Makefile,并且保持了目录结构,但并没有什么大改动.
新代码
你可以看到来自part10-multicore的大量的kernel.c的代码,除了我们不是展现四个核并且播放音乐,而是现在只是用0和1,另外,使用两个定时器中断来展现我们的四个进度条.因此, main()例程启动核1,设置定时器,然后最终启动核0的工作负载.
使用这些调用来设置定时器:
irq_init_vectors();
enable_interrupt_controller();
irq_enable();
timer_init();
初始化异常向量列表
实际上中断时异常的一种更特殊的类型 -- 当"举起"的时候,需要立即被处理器关注的东西.一个完美的例子:当坏代码尝试做一些"不可能"的事情(比如除以零)的时候,这时一个异常就会发生.CPU需要知道它发生后如何响应,比如跳转到能优雅的处理这些异常的代码的地址并且运行,比如通过打印错误到屏幕.这些地址被存储在一个异常向量列表中.
irqentry.S 设置了一个列表,叫作 vector,它包含了独立的向量项.这些向量项只是跳转到处理代码程序的指令.
在kernel.c中的main()调用irq_init_vectors()的过程中CPU被告诉了哪里异常向量表被存储.你会在util.S里面找到代码:
irq_init_vectors:
adr x0, vectors
msr vbar_el1, x0
ret
它只是设置了向量基地址寄存器到向量列表的地址.
中断处理
为了这篇教程的目的,我们唯一真正关心的向量项就是 handle_el1_irq.它是一个任何来自 EL1(内核处理中断)的中断请求(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 保存了中断处理程序运行之前的寄存器状态,并且 kernel_exit 在返回时回复了寄存器状态.因为我们正在中断正常的程序执行,我们希望确保把东西恢复成原来的样子,这样我们内核代码回复的时候不会有不可预料的事情发生.
在中间我们只是调用一个叫 handle_irq() 的函数,它使用C代码编写,在 irq.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();
}
}
}
就像你看到的那样,我们在这个程序中处理两个不同的定时器中断.实际上,handle_timer_1() 和 handle_timer_3() 在kernel.c中实现,并且服务于通过递增进度计数器来展现定时器被启动了,并且更新代表它的值的图形.定时器3被设置为比定时器1进展块4倍.
中断控制器
定时器是一个当中断发生时负责告诉CPU的硬件.我们可以使用中断控制器作为门控使用来允许/阻止(或者说使能/失能)中断.我们同样使用它来指出哪个设备产生了中断,就像我们在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使用的技术,来防止特定的代码片段被中断.它用来保护重要的必须完成的代码.想想一下如果我们的kernel_entry代码(它保存了寄存器状态)被中途中断了!在这个例子里面,寄存器状态将会被覆盖并且丢失.这就是为什么当异常处理程序执行的时候CPU会屏蔽所有中断.
util.S中的 irq_enable 和 irq_disable 函数负责屏蔽和揭开中断.
.globl irq_enable
irq_enable:
msr daifclr, #2
ret
.globl irq_disable
irq_disable:
msr daifset, #2
ret
一旦irq_enable()从 kernel.c 里的 main() 当中被调用,当定时器中断触发时定时器处理程序就会运行.嗯,有点……!
初始化系统定时器
我们仍然需要初始化定时器.
树莓派的系统定时器简单的不能再简单.它有一个每个时钟周期都递增1的计时器.然后它有4条中断线,(0和2为GPU保留,1和3被我们的教程使用!)和4个比较寄存器.当计数器的值与其中一个比较寄存器的值相同到时候,相应的中断启动了.
所以在我们接收任何定时器中断之前,我们必须设置正确的比较寄存器的值为一个非零值. timer_init()函数(从kernel.c里面的main()函数调用)得到现有的定时器的值,添加定时器的间隔,并且设置比较寄存器为它们的总数,所以当正确的时钟数值传递时,中断被启动.它们同时为定时器1和定时器3做到这个,让定时器3以4倍速度运行.
处理定时器中断
这是最简单的位.
我们更新比较寄存器这样下一个中断就会在相同的间隔再次触发.重要的是我们通过设置状态寄存器来确认中断
然后我们更新屏幕来展现我们的进度.
和…您看!您像专业人士一样处理两个系统计时器中断!

原文如下

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!_
