Linux Interrupt Handling

Recently, I was studying asynchronous message processing, and suddenly I remembered the interrupt processing of the Linux kernel, which runs through the asynchronous processing idea of ​​"important things are done immediately, and unimportant things are postponed" from beginning to end. So let's sort it out~

The first stage gets the interrupt number

Each CPU has the ability to respond to interrupts, and each CPU follows the same process when responding to interrupts. This process is the interrupt service routine provided by the kernel.

When entering the interrupt service routine, the CPU has automatically disabled the interrupt response on this CPU, because the CPU cannot assume that the interrupt service routine is reentrant.

The first step in an interrupt handler does two things:

1. Push the interrupt number onto the stack; (interrupts with different interrupt numbers correspond to different interrupt service routine entries) 2. Push the current register information onto the stack; (so that it can be restored when the interrupt exits)

Obviously, these two steps are not reentrant (if it is interrupted when saving the register value, then another operation is likely to rewrite the register, and the scene will not be able to recover), so the CPU mentioned above enters the interrupt service Interrupts are automatically disabled during the program.

The information on the stack is used as a function parameter to call the do_IRQ function.

Second stage interrupt serialization

Enter the do_IRQ function, the first step is to serialize the interrupt, serialize an interrupt generated by multiple CPUs at the same time. The method is if the current interrupt is in the "execution" state (indicating that another CPU is processing the same interrupt), it resets its "triggered" flag, and then returns immediately. After the CPU that is handling the same interrupt completes a process, it checks the "triggered" flag again, and if set, triggers the process again.

Therefore, the processing of interrupts is a loop process, and each loop calls handle_IRQ_event to handle the interrupt.

Interrupt processing under the third stage interrupt condition

Enter the handle_IRQ_event function, and call the corresponding interrupt handler function registered by the kernel or kernel module through the request_irq function.

The registered interrupt handler function has an interrupt switch attribute. In general, the interrupt handler function is always performed when the interrupt is turned off. When the request_irq registered interrupt handler function is called, the interrupt handler function can also be set to open the interrupt. This situation is rare, because it requires that the interrupt handling code must be reentrant. (In addition, if the interrupt is opened here, the interrupt being processed will generally be blocked. Because when an interrupt is being processed, This interrupt on the hardware interrupt controller is not acked, and the hardware will not issue the next same interrupt.)

The process of the interrupt handler may be very long. If the entire process is performed with interrupts turned off, subsequent interrupts will be blocked for a long time.

So, there is soft_irq. The non-reentrant part is done in the interrupt handler (close the interrupt), then call raise_softirq to set a soft interrupt, and the interrupt handler ends. The rest of the work will be done in soft_irq.

Soft interrupt under the fourth stage open interrupt condition

After the previous stage loop calls all currently triggered interrupt handlers, the do_softirq function is called to start processing software interrupts.

In the soft interrupt mechanism, a mask set of several bits is maintained for each CPU, and each mask represents an interrupt number. In the interrupt processing function of the previous stage, call raise_softirq to set the corresponding soft interrupt, here we are , the handler function corresponding to the soft interrupt will be called (the handler function is registered by the open_softirq function).

It can be seen that the model of soft interrupt is very similar to that of interrupt. Each CPU has a set of interrupt numbers, interrupts have their corresponding priorities, and each CPU handles its own interrupts. The biggest difference is the opening and closing of interrupts.

Therefore, an interrupt processing process is divided into two parts, the first part is to turn off the interrupt in the interrupt processing function, and the second part is to open the interrupt in the soft interrupt processing function.

Since this step is performed under the condition of open interrupt, a new interrupt (interrupt nesting) may also occur here, and then the interrupt processing corresponding to the new interrupt will start a new first stage ~ third stage. In this new third stage, a new soft interrupt may be triggered. But this new interrupt handler does not enter the fourth stage, but exits after completing the third stage when it finds itself as a nested interrupt. That is to say, only the interrupt processing process of the first layer will enter the fourth stage, and the interrupt processing process that occurs in nesting will only be executed to the third stage.

However, the interrupt processing process that occurs in nesting may also trigger soft interrupts, so the first-level interrupt processing process needs to be a cyclic process in the fourth stage, and all soft interrupts that occur in nesting need to be processed cyclically. Why do you want to do this? Because these soft interrupts can be executed in the order in which the soft interrupts are triggered, otherwise the subsequent soft interrupts may be executed first.

In extreme cases, there may be a lot of nested soft interrupts, and it may take a long time to process all of them, so the kernel will push the remaining unhandled soft interrupts to a ksoftirqd after processing a certain number of soft interrupts. The kernel thread to process, and then end the interrupt processing process.

The fifth stage opens the tasklet under the interrupt condition

In fact, soft interrupts are rarely used directly. The second part of the processing in the case of interrupts is generally completed by the tasklet mechanism.

The tasklet is derived from the soft interrupt. The kernel defines two soft interrupt masks HI_SOFTIRQ and TASKLET_SOFTIRQ (the two have different priorities). The soft interrupt processing functions corresponding to these two masks are used as the entry to enter the tasklet processing process.

Therefore, in the interrupt handling function of the third stage, after completing the part of turning off the interrupt, call tasklet_schedule/tasklet_hi_schedule to mark a tasklet, and then the interrupt handler ends. The subsequent work is handled by the soft interrupt handler corresponding to HI_SOFTIRQ/TASKLET_SOFTIRQ. Marked tasklets (each tasklet has a handler function set when it is initialized).

看上去, tasklet只不过是在softirq的基础上多了一层调用, 其作用是什么呢? 前面说过, softirq是与CPU相对应的, 每个CPU处理自己的softirq. 这些softirq的处理函数需要设计为可重入的, 因为它们可能在多个CPU上同时运行. 而tasklet则是在多个CPU间被串行化执行的, 其处理函数不必考虑可重入的事情.

然而, softirq毕竟还是要比tasklet少绕点弯路, 所以少数实时性要求相对较高的处理过程还是在精心设计之后, 直接使用softirq了. 比如: 时钟中断处理过程, 网络发送/接收处理过程.

结尾阶段

CPU接收到中断以后, 以历以上五个阶段, 中断处理完成. 最后需要恢复第一阶段中被保存在栈上的寄存器信息. 中断处理结束.

关于调度

上面的流程中, 还隐含了一个问题, 整个处理过程是持续占有CPU的(除了开中断情况下可能被新的中断打断以外). 并且, 中断处理的这几个阶段中, 程序不能够让出CPU!

这是由内核的设计决定的, 中断服务程序没有自己的task结构(即操作系统教科书上说的进程控制块), 所以它不能被内核调度. 通常说一个进程让出CPU, 在之后如果满足某种条件, 内核会通过它的task结构找到它, 并调度其运行.

这里可能存在两方面的问题:

1. 连续的低优先的中断可能持续占有CPU, 而高优先的某些进程则无法获得CPU;2. 中断处理的这几个阶段中不能调用可能导致睡眠的函数(包括分配内存);

对于第一个问题, 较新的linux内核增加了ksoftirqd内核线程, 如果持续处理的softirq超过一定数量, 则结束中断处理过程, 然后唤醒ksoftirqd, 让它来继续处理. 虽然softirq可能被推后到ksoftirqd内核线程去处理, 但是还是不能在softirq处理过程中睡眠, 因为不能保证softirq一定在ksoftirqd内核线程中被处理.

据说在montavista(一种嵌入式实时linux)中, 将内核的中断机制做了修改. (某些中断的)中断处理过程被赋予了task结构, 能够被内核调度. 解决了上述两个问题. (montavista的目标是实时性, 这样的做法牺牲了一定的整体性能.)

工作队列

linux基线版本的内核在解决上述问题上, 提供了workqueue机制.

定义一个work结构(包含了处理函数), 然后在上述的中断处理的几个阶段的某一步中调用schedule_work函数, work便被添加到workqueue中, 等待处理.

工作队列有着自己的处理线程, 这些work被推迟到这些线程中去处理. 处理过程只可能发生在这些工作线程中, 所以这里可以睡眠.

内核默认启动了一个工作队列, 对应一组工作线程events/n(n代表处理器编号, 这样的线程有n个). 驱动程序可以直接向这个工作队列添加任务. 某些驱动程序还可能会创建并使用属于自己的工作队列.


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325598536&siteId=291194637