中断下半部分为三种:软中断、tasklet和工作队列。
软中断softirq
软中断softirq是用于不紧急的延期操作,是tasklet的基础。网卡接收数据的过程中,首先网卡发起中断告诉cpu取数据,然后内核从网卡读取数据存入缓存中,再由内核解析数据并将数据送到应用层。上面的整个过程如果都由中断处理程序来处理,耗时太长,会丢失新来的中断。因此解析数据这些不紧急的部分就放到软中断中执行。
3.6.10内核中可用的软中断有下面几种类型:
enum { HI_SOFTIRQ=0, TIMER_SOFTIRQ, NET_TX_SOFTIRQ, NET_RX_SOFTIRQ, BLOCK_SOFTIRQ, BLOCK_IOPOLL_SOFTIRQ, TASKLET_SOFTIRQ, SCHED_SOFTIRQ, HRTIMER_SOFTIRQ, RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
NR_SOFTIRQS }; |
HI_SOFTIRQ的优先级最高,HI_SOFTIRQ对应的处理程序在其他所有类型的软中断处理程序之前执行,尤其是在构成软中断主体的网络处理程序之前执行。
TIMER_SOFTIRQ用来实现软件定时器,处理与时钟相关的软中断。
NET_TX_SOFTIRQ和NET_RX_SOFTIRQ是网络传输和接收对应的软中断类型。
要使用软中断,首先要注册软中断,就是往softirq_vec数组中增加相应的中断处理函数。内核是调用open_softirq函数,比较简单,不做分析。
内核调用raise_softirq函数提交一个softirq,进而调用raise_softirq_irqoff函数将相应软中断位标记为pending,这样就完成了提交。
软中断处理时机
那么标记为pending的软中断softirq在什么时候被处理呢?标记为pending的softirq在下面几个时机可能被处理:
1、 硬中断处理完成,do_IRQ即将退出时处理
do_IRQàirq_exit
/* * Exit an interrupt context. Process softirqs if needed and possible: */ void irq_exit(void) { ... if (!in_interrupt() && local_softirq_pending()) invoke_softirq(); ... } |
in_interrupt是判断当前是否处在硬中断或软中断环境中。如果有硬中断嵌套,或正在执行软中断,就不会调用invoke_softirq函数。local_softirq_pending用来判断是否有软中断处在pending状态。这里两个判断条件就是硬中断已经执行完了,并且硬中断有安装软中断,才会调用invoke_softirq函数执行软中断。
一个软中断如果被硬中断打断,这样也避免了在处理完硬中断的时候,不会重新调用do_softirq,而是返回中断之前的软中断。
invoke_softirq函数会调用do_softirq函数来处理软中断。
asmlinkage void do_softirq(void) { /*判断是否有硬中断嵌套,或者有软中断正在执行*/ if (in_interrupt()) return;
local_irq_save(flags); /*关中断*/
pending = local_softirq_pending(); /*判断是否有pending的软中断需要处理*/ if (pending) __do_softirq();
local_irq_restore(flags); /*开中断*/ } |
首先通过in_interrupt来判断是否有硬中断嵌套,或者有软中断正在执行。这里说明如果有新的一批软中断到来,无论优先级高低,都得等前一批的软中断处理完才能被处理。
在__do_softirq处理软中断之前,调用local_irq_save关中断。
asmlinkage void __do_softirq(void) { struct softirq_action *h; __u32 pending; int max_restart = MAX_SOFTIRQ_RESTART;
pending = local_softirq_pending(); /*获取本地软中断pending位*/ /*屏蔽其他软中断*/ __local_bh_disable((unsigned long)__builtin_return_address(0), SOFTIRQ_OFFSET);
cpu = smp_processor_id(); restart: /* Reset the pending bitmask before enabling irqs */ set_softirq_pending(0); /*清除pending位*/ local_irq_enable(); /*开硬中断*/
h = softirq_vec; /*获取中断向量表*/ do { if (pending & 1) { unsigned int vec_nr = h - softirq_vec; int prev_count = preempt_count(); h->action(h); } h++; pending >>= 1; } while (pending);
local_irq_disable(); /*关中断*/
pending = local_softirq_pending(); if (pending && --max_restart) /*最多重复10次*/ goto restart;
if (pending) wakeup_softirqd(); |
__local_bh_disable函数会设置preempt_count对应的SOFTIRQ_OFFSET位,禁止其他的软中断softirq,这也说明了每个cpu上同时运行的软中断只能有一个。
在开中断前,先清除软中断的pending位,这样就可接收新的按照的软中断。但是因为之前有屏蔽软中断,因此这里开了硬中断,只可能被硬中断打断,不会被软中断打断。
在while循环里面,会从低位到高位,依次执行pending的softirq,这样就保证了在同一批次的软中断中,高优先级的先处理(低位的优先级高)。pending是32位的,因此这个循环里一次最多能够处理32个软中断。
前面处理软中断的过程中是打开硬中断的,因此可能会多次发生硬中断抢占。每次硬中断抢占都可能注册软中断,因此会设置pending位,这里就需要检查本地软中断的pending位了。如果有设置pending位,即有软中断需要处理,跳转到restart,这里最多重复10此检查。
如果超过10次,还有未处理完的软中断,就会唤醒ksoftirqd线程来进行处理。
2、ksoftirqd内核线程被唤醒后处理
前面所述的情况,就是ksoftirqd内核线程被唤醒的一种情况。
static int run_ksoftirqd(void * __bind_cpu) { set_current_state(TASK_INTERRUPTIBLE); /*设置线程为可中断睡眠状态,可被信号唤醒*/
while (!kthread_should_stop()) { /*判断此线程是否会停止,不会停止就进入循环*/ preempt_disable(); /*关抢占*/ if (!local_softirq_pending()) { /*判断是否有pending的软中断需要处理*/ schedule_preempt_disabled(); }
__set_current_state(TASK_RUNNING); /*设置为运行状态*/
while (local_softirq_pending()) { /*有pending的软中断需要处理*/ /* Preempt disable stops cpu going offline. If already offline, we'll be on wrong CPU: don't process */ if (cpu_is_offline((long)__bind_cpu)) goto wait_to_die; local_irq_disable(); /*关硬中断*/ if (local_softirq_pending()) __do_softirq(); local_irq_enable(); /*开硬中断*/ sched_preempt_enable_no_resched(); /*开抢占*/ cond_resched(); /*可能放弃cpu*/ preempt_disable(); } preempt_enable(); /*开抢占*/ set_current_state(TASK_INTERRUPTIBLE); } ?end while !kthread_should_stop() ? __set_current_state(TASK_RUNNING); return 0; |
开始设置此内核线程为可中断的睡眠状态,这样可以被信号唤醒。接下来判断线程是否会停止,不停止就进入while大循环中进行处理。
大循环中首先调用preempt_disable关内核抢占,这样此内核线程就不会被抢占。如果没有pending的软中断需要处理,就调用schedule_preempt_disabled函数,此函数会先开抢占,然后调用schedule函数放弃cpu使用权,schedule函数返回,就是此内核线程调度回来了,那么就关内核抢占。
如果有pending的软中断处理,就进入小的while循环中。首先判断内核线程关联的cpu如果下线了,则直接跳转到wait_to_die标签处,不处理软中断,等待线程退出。因为前面有关抢占,不会有别人来下线此cpu的,这里只能说明我们处在一个本身是要下线的cpu上。
在调用__do_softirq函数处理软中断前后分别是关硬中断和开硬中断。在处理完一批软中断之后,会调用sched_preempt_enable_no_resched函数来开抢占,然后调用cond_resched,可能会放弃cpu。这样做市为了防止软中断过多的情况下,此内核线程占用了过多的cpu资源,使得其他进程得不到调度。
3、显示的调用do_softirq处理软中断
显示的调用do_softirq来处理软中断的地方很多。
软中断优先级问题
关于软中断的优先级:
① 如果软中断被打断,硬中断触发更高优先级的软中断
此种情况下,在硬中断处理返回前irq_exit函数中会判断in_interrupt是否为0,这里之前的软中断还未处理,不为0,因此就不会调用do_softirq,而是返回中断之前的软中断
② 新一批的软中断无论优先级多高,都得等前一批的软中断处理完毕之后才处理。而优先级的高低,在同一批次的软中断处理中才得到体现。即pending低位的优先级高,先得到处理。