为什么需要下半部
中断处理程序有如下局限性:
- 中断处理程序是异步中断,被其中断执行的代码(包括别的中断处理程序)可能正在执行非常重要的任务,为了避免被中断进程停止过长时间,中断处理程序的执行应该越快越好。
- 中断处理程序会禁用其服务的中断线(没有设置IRQF_DISABLE),这是最好的情况。最坏的情况下会禁用当前处理器上所有中断(设置了IRQF_DISABLED)。禁用中断期间,硬件设备无法和操作系统通信,所以中断处理程序的执行要越快越好。
- 中断处理程序通常对时延非常敏感,因为它们要和硬件交互。
- 中断处理程序运行在中断上下文中,所以不能阻塞,那么就不能执行可能导致阻塞的操作或者调用可能导致阻塞的函数,这限制了中断处理程序能做的事情。
操作系统肯定是需要一个快速的、异步的、简单的机制用于立刻响应硬件的请求并执行任何对时延敏感的操作。中断处理程序很好的实现了这个功能,但是不那么重要的工作可以并且应该推迟到中断处于激活状态时处理。基于上述的考量,中断处理被分成了两个部分:上半部(top halve)和下半部(bottom halve)。上半部,即中断处理程序,负责立刻响应硬件中断并处理对时延敏感的任务。下半部则负责在中断程序运行不久后完成中断处理程序没有处理的任务。
“不久后” 指的意思并不是在某些特定的时间点去执行,而是说将任务推迟到未来任意一个系统不太繁忙并且中断处于激活状态的时间点执行。通常情况下,下半部都会在中断处理程序返回之后立刻执行。最关键的是:下半部执行时,所有中断都处于激活状态(没有被禁用)
如何给上半部和下半部分配工作
首先,决定权完全在设备驱动开发者的手里,没有统一的标准,总的来说,分配目标就是在保证功能和性能的同时最小化中断处理程序的执行时间。下面是一些可供参考的原则:
- 如果任务是时延敏感的,在中断处理程序中执行
- 如果任务和硬件相关,在中断处理程序中执行
- 如果任务需要确保不会被另一个中断(特别是同一个中断)打断,在中断处理程序中执行
- 其他所有的任务,考虑在下半部执行。
下半部(Bottom Halve)
2.6版本的内核中存在三种下半部机制:软中断(softirq),tasklet,工作队列。其中tasklet是基于软中断来实现的,软中断很少被直接使用,tasklet用得更多一些。软中断比较适合对时延较为敏感和执行频率较高的场景(内核中只有网络子系统和块设备直接使用软中断)。软中断和tasklet都是运行在中断上下中(不能阻塞,中断处于激活状态)。工作队列则是基于内核线程来做的,运行在进程上下文中(能阻塞,中断处于激活状态)。
软中断(Softirq)
软中断是一种将函数推后执行的机制,首先在内核中静态地(编译期间)注册处理一个软中断类型及其处理程序,在中断处理程序中标记想要运行的软中断类型,然后内核会在中断处理程序返回后寻找一个合适的时机运行对应的软中断处理程序。
软中断的实现
软中断是在编译期间静态分配的,不能动态的注册和注销软中断。软中断使用softirq_action结构体来表示,该结构只有一个成员,即处理函数(softirq handler)。定义如下:
struct softirq_action {
void (*action)(struct softirq_action *);
};
Linux使用一个softirq_action数组来保存所有注册的软中断,数组上每个元素表示一个软中断类型:
static struct softirq_action softirq_vec[NR_SOFTIRQS];
每个注册的软中断占据数组中的一项,软中断的数量是在编译期间静态决定,不能动态改变。最多可以注册32个软中断(原因可以看后面的实现,pending bitmask是32bit的),但是现在的内核中实际上只注册了10个软中断。
软中断处理程序程序action()的函数原型如下:
void softirq_handler(struct softirq_action *)
一个软中断不会抢占另一个软中断,唯一可能抢占软中断的只有中断处理程序(软中断不会被调度程序抢占)。不过,其他软中断(可以是相同类型的软中断)可以在其他处理器上同时执行。
软中断的执行
一个已注册的软中断必须被标记后才能执行,这被叫做触发软中断(raising the softirq)。通常,中断处理程序会在返回之前触发它对应的软中断。然后软中断会在一个合适的时机运行。内核会在下面几个地方检查和执行已经触发的软中断:
- 从硬件中断的代码返回时
- 在ksoftirqd内核线程中(后面说)
- 在那些显式地检查、执行已触发软中断的代码中,比如网络子系统。
无论是在哪个地方执行,都会调用do_softirq(),这个函数会调用__do_softirq()来遍历当前已经被触发的所有软中断。下面是简化后的__do_softirq()代码:
u32 pending;
/*
* 获得当前触发的软中断bitmask
* 置1的位置表示在软中断数组中对应位置的软中断被触发
*/
pending = local_softirq_pending();
if (pending) {
struct softirq_action *h;
/*重置pending的bitmask,因为bitmask已经在上面被提取出来了*/
set_softirq_pending(0);