下半部和推后执行的工作
1.下半部
下半部的任务就是执行与中断处理密切相关但中断处理程序本身不执行的工作,对于在上半部和下半部之间划分工作,尽管不存在某种严格的规则,但还是有一些提示可供借鉴:
1)如果一个任务对时间非常敏感,将其放在中断处理程序中执行。
2)如果一个任务和硬件相关,将其放在中断处理程序中执行。
3)如果一个任务要保证不被其他中断(特别是相同的中断)打断,将其放在中断处理程序中执行。
4)其他所有任务,考虑放置在下半部执行。
我们希望尽量减少中断处理程序中需要完成的工作量,因为在它运行的时候当前的中断线(或全局中断)都会被屏蔽,而缩短中断被屏蔽的时间对系统的响应能力和性能都至关重要。再加上中断处理程序要与其他程序(甚至是其他中断程序)异步执行,所以要缩短中断处理程序的执行,把一些工作交给下半部执行。
2.软中断
软中断是在编译期间静态分配的,在kernel/softirq.c中定义了一个包含有32个softirq_action结构体的数组,因此最多只能有32个软中断。注意这是一个定值——注册的软中断数目的最大值没法动态改变。在2.6内核中,这32个项中只用到了6个。
软中断处理程序action的函数原型为 void softirq_handler(struct softirq_action *),当内核运行一个软中断处理程序的时候,它就会执行这个action函数,其唯一的参数为指向相应softirq_action结构体的指针,一个软中断不会抢占另外一个软中断,唯一可以抢占软中断的是中断处理程序。不过,其他的软中断——甚至是相同类型的软中断可以在其他处理器上执行。
一个注册的软中断必须在被标记后才会执行。称作触发软中断(raising the softirq)。通常,中断处理程序会返回前标记它的软中断,使其在稍后被执行。在下列地方,待处理的软中断会被检查和执行:
1)从一个硬件中断代码处返回时。
2)在ksoftirqd内核线程中。
3)在那些显式检查的执行待处理的软中断的代码中,如网络子系统中。
不管是用什么方法唤起,软中断都要在do_softirq()中执行。该函数很简单,如果有待处理的软中断,do_softirq()会循环遍历每一个,调用它的处理程序。
u32 pending = softirq_pending(cpu); if( pending ){ struct softirq_action *h = softirq_vec; softirq_pending( cpu ) = 0; do { if( pending & 1 ) h->action( h ); h++; pending >>= 1; }while( pending ); }
它检查并执行所有待处理的软中断,具体要做的包括:
1)用局部变量pending保存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,这表明已经没有待处理的软中断了。
3.使用软中断
1)分配索引。在编译期间,可以通过<linux/interrupt.h>中定义的一个枚举类型来静态地声明软中断。内核用从0开始的索引来表示一种相对优先级。索引号小的软中断在索引号大的软中断之前执行。
2)注册处理程序。在运行时通过调用open_softirq()注册软中断处理程序,该函数有三个参数:软中断的索引号,处理函数和data域存放的数值。
open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL);
软中断处理程序执行的时候,允许响应中断,但它自己不能休眠。在一个处理程序运行的时候,当前处理器上的软中断被禁止,但其他的处理器仍可以执行别的软中断(甚至是同 一类型的软中断)。这意味着任何共享数据都需要严格的锁保护。大部分软中断处理程序都通过采取单处理器数据(仅属于某一个处理器的数据,因此根本不需要加锁)或其他一些 技巧来避免显式地加锁,从而提供更出色的性能。
3)触发软中断。raise_softirq()函数可以将一个软中断设置为挂起状态,让它下次调用do_softirq()函数时投入运行。
raise_softirq(NET_TX_SOFTIRQ);
该函数在触发一个软中断之前先要禁止中断,触发后再恢复回原来的状态。
在中断处理程序中触发软中断是最常见的形式。在这种情况下,中断处理程序执行硬件设备的相关操作,然后触发相应的软中断,最后退出。内核在执行完中断处理程序以后,马上就会调用do_softirq()函数,于是软中断开始执行中断处理程序留给它去完成的剩余任务。