1. 引入软中断
一个中断处理程序的一个或几个中断服务例程在执行结束之前,内核处于中断环境中,当前CPU不再响应同类型的中断,如果不允许中断嵌套,则CPU需要屏蔽掉所有中断。也就是说,一个CPU忙于服务于一个中断事件时,就不能处理其他中断,同时CPU不能执行其他进程,即不能被抢占,这种情况下,如果在中断服务例程中消耗的时间过多,就会对性能产生潜在的影响。
一般情况下,一个中断事件所触发的动作可能需要占用很多CPU时间,但通常其中多数内容都是可以等待的。为了保证对硬件保持较短的响应时间,在一个中断事件到来时,可以先抢占CPU,将必须尽快处理的事情做完,然后释放CPU,在稍后的某一时刻,当内核不需要再做一些紧迫之事,再处理中断事件剩下的事情。
这些可以延后的处理程序被称为中断下半部。例如网卡收包的处理,当CPU收到一个收包中断时,需要把数据包从DMA中搬运到内存中内核预先设置好的位置,并设置某些标记来通知内核有数据包到来,然后内核分配一个skb缓冲区,将数据内容拷贝到缓冲区里,并初始化一个skb实例,然后将数据包交给上层协议栈处理,这个过程非常复杂,需要耗费大量CPU时间,不可能都在中断处理程序中完成。有了中断下半部机制,在中断处理程序中只需将数据包放到内存并设好标记,这可以很快完成,而剩下实际的数据包处理过程则放到下半部中去执行。
内核使用软中断(softirq)和微任务(tasklet)两种可延迟函数来实现中断下半部机制,他们是一种非紧迫、可中断的内核函数,因为他们在执行过程是开中断的。
tasklet是在软中断之上实现的,所以在内核代码中“软中断”通常表示可延迟函数的所有种类。另外一个被广泛使用的术语“中断上下文”表示内核当前正在执行一个中断处理程序或一个可延迟函数。
2. 软中断的实现
内核2.6.31中使用有限个软中断,所有的软中断类型定义如下。
/*PLEASE, avoid to allocate new softirqs, if you need not _really_ high frequencythreaded job scheduling. For almost all the purposes
taskletsare more than enough. F.e. all serial device BHs et
al.should be converted to tasklets, not to softirqs.
*/
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /*Preferable RCU should always be the last softirq */
NR_SOFTIRQS
};
内核中共有NR_SOFTIRQS种软中断,标号从0到NR_SOFTIRQS-1,优先级由高到低,即HI_SOFTIRQ的优先级最高,在执行软中断处理函数时,将按照优先级的顺序来执行。
2.1 相关数据结构
在每个进程的thread_info结构中,都有一个抢占计数器preempt_count。
struct thread_info {
……
__u32 cpu; /* current CPU */
int preempt_count; /* 0 => preemptable */
……
};
在thread_info结构体中,preempt_count成员用来跟踪内核抢占和内核控制路径的嵌套,这个整型数包含三个计数器和两个标记位:
- bits 0-7:这个计数器表示在内核代码中禁用本地内核抢占的次数,等于0表示允许内核抢占。
- bits 8-15:可延迟函数被禁用的程度,为0表示可延迟函数处于激活状态。
- bits 16-25:本地CPU上中断处理程序的嵌套数,嵌套(nested)数还受堆栈大小的限制,所以不一定能达到1024。
- bit 26:是否处于不可屏蔽中断(NMI)上下文中。
- bit 28:PREEMPT_ACTIVE标记,在进程调度的时候表示进程是通过抢占而被调度的。
每个进程都有一个thread_info结构,所以每个进程都有自己的preempt_count,通过current_thread_info()->preempt_count可以获取该计数器。
每个CPU都维护一个全局的irq_cpustat_t结构体,在mips中该结构体只有一个成员:挂起的软中断的掩码,它是32位整型,也就是说系统最多支持32个软中断,现有的软中断在上面已列出。
typedef struct {
unsigned int __softirq_pending;
}____cacheline_aligned irq_cpustat_t;
irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;
通过查看__softirq_pending成员的值(每个bit位对应一个软中断号)就可以知道哪些软中断正等待被处理,函数接口为local_softirq_pending()。
#define__IRQ_STAT(cpu, member) (irq_stat[cpu].member)
#define local_softirq_pending() \
__IRQ_STAT(smp_processor_id(),__softirq_pending)
2.2 处理softirq
注册软中断:
所有软中断的处理函数都放到一个全局数组softirq_vec中,
static struct softirq_action softirq_vec[NR_SOFTIRQS]__cacheline_aligned_in_smp;
注册软中断处理函数的接口为open_softirq():
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}
该函数接受两个参数:软中断类型的标号nr和将要注册的处理函数action。从第二个参数action可以看出,注册的处理函数需要一个参数:指向特定类型软中断的struct softirq_action实例&#