中断下半部分析之软中断softirq

中断下半部分为三种:软中断、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在什么时候被处理呢?标记为pendingsoftirq在下面几个时机可能被处理:

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();      /*开硬中断*/

 

softirq_vec     /*获取中断向量表*/

do  

if (pending 1) {  

unsigned int vec_nr 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循环里面,会从低位到高位,依次执行pendingsoftirq,这样就保证了在同一批次的软中断中,高优先级的先处理(低位的优先级高)。pending32位的,因此这个循环里一次最多能够处理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低位的优先级高,先得到处理。



你可能感兴趣的:(linux,内核,中断)