一、软中断概述
- 现在我们开始讨论下半部的实现——先从软中断开始
- **软中断使用的比较少。**tasklet是下半部更常用的一种形式(但是,
由于tasklet是通过软中断实现的,所以我们先来研究软中断)
- 软中断的代码位于
kernel/softirq.c文件中
二、软中断的实现
struct softirq_action
- 软中断是在编译期间静态分配的。它不像tasklet那样能被动态地注册或注销
- 软中断由
struct softirq_action结构表示,它定义在<linux/interrupt.h>中:
其中action为软中断的处理程序的函数(见下softirq_handler)
softirq_vec数组
- kernel/softirq.c中定义了一个包
含有32个该结构体的数组
- 每个被注册的软中断都占据该数组的一项,因此最多可能有32个软中断。注意,这是一个定值——注册的软中断数目的最大值没法动态改变。在当前版本的内核中,这32个项中只用到9个
①软中断处理程序(softirq_handler())
- 软中断
softirq_action结构中的的action函数就是这个函数,其原型如下:
- 当
内核运行一个软中断处理程序的时候,它就会执行这个函数,其唯一的参数为指向相应softirq_action结构体的指针。例如,如果my_softirq指向softirq_vec数组的某项,那么内核会用如下的方式调用软中断处理程序中的函数:
- 当你看到内核把整个结构体都传递给软中断处理程序而不是仅仅传递数据值的时候,你可能会很吃惊。
这个小技巧可以保证将来在结构体中加入新的域时,无须对所有的软中断处理程序都进行变动。如果需要,软中断处理程序可以方便地解析它的参数,从数据成员中提取数值
- **一个软中断不会抢占另外一个软中断。**实际上,唯一可以抢占软中断的是中断处理程序。不过 ,
其他的软中断(甚至是相同类型的软中断)可以在其他处理器上同时执行
②注册软中断、执行软中断(do_softirq())
- 一个注册的软中断
必须在被标记后才会执行。这被称作
触发软中断。通常,中断处理程序会在返回前标记它的软中断,使其在稍后被执行。于是,在合适的时刻,该软中断就会运行。在下列地方,
待处理的软中断会被检査和执行:
- 从一个硬件中断代码处返回时
- 在ksoftirqd内核线程返回时
- 在那些显式检查和执行待处理的软中断的代码中,如网络子系统中
- 不管是用什么办法唤起,
软中断都要在do_softirq()中执行。该函数很简单。如果有待处理的软中断,do_softirq()会循环遍历每一个,调用它们的处理程序。让我们观察一下do_softirq()经过简化后的核心部分:
- 以上摘录的是软中断处理的核心部分。
它检査并执行所有待处理的软中断,具体要做的包括:
- 1.用局部变量pending保存local_softirq_pending()宏的返回值。它是待处理的软中断的32位位图——如果第n位被设置为1,那么第n位对应类型的软中断等待处理
- 2.现在待处理的软中断位已经被保存,可以将实际的软中断位图清零了
- 3.将指针h指向softirq_vec的第一项
- 4.如果pending的第一位被设置为1,则h->action(h)被调用
- 5.指针加1,所以现在它指向softirq_vec数组的第二项
- 6.位掩码pending右移一位。这样会丢弃第一位,然后让其他各位一次向右移动一个位置。于是,原来的第二位现在就在第一位的位置上了(以此类推)
- 7.现在指针h指向数组的第二项,pending位掩码的第二位现在也到了第一位上。重复执行上面的步骤
- 8.一直重复下去,知道pending变为0,这表明已经没有待处理的软中断了,我们的任务也就完成了。注意,这种检查足以保证h总指向softirq_vec的有效项,因为pending最多只可能设置32位,循环最多也只能执行32次
三、使用软中断
- **软中断保留给系统中对时间要求最严格以及最重要的下半部使用。**目前,只有两个子系统 (网络和SCSI)直接使用软中断。此外,内核定时器和tasklet都是建立在软中断上的。如果你想加入一个新的软中断,首先应该问问自己为什么用tasklet实现不了。tasklet可以动态生成,由于它们对加锁的要求不高,所以使用起来也很方便,而且它们的性能也非常不错。当然,
对于时间要求严格并能自己高效地完成加锁工作的应用,软中断会是正确的选择
①分配索引
- 在编译期间,通过在<linux/interrupt.h>中定义的
一个枚举类型来静态地声明软中断。内核用这些从0开始的索引来表示一种相对优先级。
**索引号小的软中断在索引号大的软中断之前执行 **
- 建立一个新的软中断必须在此枚举类型中加入新的项。而加入时,你不能像在其他地方 一样,简单地把新项加到列表的末尾。相反,你必须根据希望赋予它的优先级来决定加入的位 置。习惯上,HI_SOFTIRQ通常作为第一项,而RCU_SOFTIRQ作为最后一项
。新项可能插在BLOCK_SOFTIRQ和TASKLET_SOFTIRQ之间
- 下标列举出了已有的tasklet类型
②注册你的处理程序(open_softirq())
- 接着,在运行时通过
调用open_softirq()注册软中断处理程序,该函数有两个参数:
软中断的索引号和处理函数
如网络子系统,在net/coreldev.c通过以下方式注册自己的软中断:
软中断处理程序执行的时候,允许相应中断,但它自己不能休眠。在一个处理程序运行的时候 ,
当前处理器上的软中断被禁止。但其他的处理器仍可以执行别的软中断。实际上,如果同一个软中断在它被执行的同时再次被触发了,那么另外一个处理器可以同时运行其处理程序。这意味着任何共享数据(甚至是仅在软中断处理程序内部使用的全局变量)
都需要严格的锁保护。这点很重要,它也是tasklet更受青睐的原因。单纯地禁止你的软中断处理程序同时执行不是很理想。如果仅仅通过互斥的加锁方式来防止它自身的并发执行,那么使用软中断就没有任何意义了。因此,大部分软中断处理程序,都通过采取单处理器数 (仅属于某一个处理器的数据,因此根本不需要加锁)或其他一些技巧来避免显式地加锁,从而提供更出色的性能
- **引入软中断的主要原因是其可扩展性。**如果不需要扩展到多个处理器,那么,就使用tasklet吧。tasklet本质上也是软中断,只不过同一个处理程序的多个实例不能在多个处理器上同时运行
③触发你的软中断
- 通过在枚举类型的列表中添加新项以及调用open_softirq()进行注册以后,
新的软中断处理程序就能够运行
**raise_softirq()函数可以将一个软中断设置为挂起状态,让它在下次调用do_softirq()函数时投入运行。**举个例子,网络子系统可能会调用:
这会
触发NET_TX_SOFTIRQ软中断。它的处理程序net_tx_action()就会在内核下一次执行软中断时投入运行。该函数在触发一个
软中断之前先要禁止中断,触发后再恢复原来的状态。如果中断本来就已经被禁止了,那么可以调用另一函数raise_softirq_irqoff,这会带来一些优化效果 。如 :
- 在中断处理程序中触发软中断是最常见的形式。在这种情况下,中断处理程序执行硬件设备的相关操作,然后触发相应的软中断,最后退出。内
核在执行完中断处理程序以后,马上就会调用do_softirq()函数。于是软中断开始执行中断处理程序留给它去完成的剩余任务。在这个例子中,“上半部”和 “下半部”名字的含义一目了然