linux中断之下半部


一、概要

   中断是内核不可缺少的一部分,但是中断处理程序本身存在一些局限性,

  a.中断方式以异步方式执行并且有可能会打断期待其他重要代码,甚至包括其它中断处理程序的执行,因此为了避免被打断的代码停止的时间过长,中断处理程序应该执行得越快越好。
  b.如果当前有一个中断处理程序在运行,在最好的情况下(如果设置了SA_INTERRUPT),与该中断同级的其它中断会被屏蔽,在最坏的情况下,当前处理器上所有其它中断都会被屏蔽,导致丢失中断,因此应当让他们执行得越快越好。
  c.由于中断处理程序往往需要对硬件进行操作,所以他们通常有很高的时限要求。
  d.中断处理程序不在进程上下文中运行,所以他们不能被阻塞。这限制了他们所做的事情。
 内核将中断分为上下两个部分,第一部分是中断处理程序,用来完成对硬件中断的即使响应,第二部分就是我们所说的下半部,完成余下的相对宽松的任务。
二、上下部分的具体划分:
  a.如果一个任务对时间非常敏感,将其放在中断处理程序中运行
  b.如果一个任务和迎接爱你相关,放到中断处理程序
  c.如果一个任务要保证不被其它中断特别的相同的中断打断,将其放到中断处理程序
  d.余下任务放到下半部
  中断程序在运行当中,当前的中断线在所有处理器上都会被屏蔽,如果一个处理程序是SA_INTERRUPT类型,它执行的时候还会禁止所有本地中断,把本地中短线全局地屏蔽掉,缩短中断被屏蔽的时间对系统的响应能力和性能至关重要。下半部执行的关键在于他们运行的时候,允许响应中断。
三、下半部的实现方式
          2.6中内核提供了三种不同形式的下半部实现机制,软中断,tasklet和工作队列。还有一个特殊的延迟工具内核定时器。
   tasklet通过软中断形式实现。利用了一个软中断进入口。
  1、软中断
   软中断在编程的时候静态分配,用softirq_action结构表示,在<linux/interrupt.h>中,
   struct softirq_action {
              void (*action)(struct softirq_action *);
              void *data;
  }
  kernel/softirq.c中定义了一个包含32个该结构的数组。
  static struct softirq_action softirq_vec[32];
  由此可知最多有32个软中断。
  软中断处理程序原型如下:
  void softirq_handler(struct softirq_action *);
  一个软中断不会抢占另外一个软中断,唯一可抢占软中断的是中断处理程序,不过其它的软中断,或者相同类型的中断是可以在其它处理器上同时运行的。
  软中断的执行,触发;
  一个注册的软中断必须在被标记后才会执行,这被称作触发软中断,中断处理程序会返回前标记它的软中断,使其
  稍后被执行,于是在合适的时刻,该软中断就会运行,这些时刻包括如下这些:
   a.从一个硬件中断处理代码返回时
   b.zai ksoftirqd内核线程中
   c.在显示检查和执行待处理的软中断代码中,如网络子系统中。
   这些方法最终都是调用do_softirq()也就是,触发软中断一定要调用该函数。do_softirq()会循环遍历每一个调用他们的处理程序。
    
   2、软中断的使用
         软中断保留给系统中时间要求最严格最重要的下半部,目前只有两个子系统--网络和scsi--直接使用软中断,
   此外,内核定时器和tasklet都是简历在软中断上的,如果你想加入一个软中断,首先要考虑tasklet为什么不可以。
   编译期间可以通过<linux/interrupt.h>中定义一个枚举类型来静态地声明软中断,内核用从0开始表示一种相对优先级,索引号小的软中断在索引大的软中断之前运行。
   已有的tasklet类型,
   HI_SOFTIRQ              //0,优先级高的tasklet
   TIMER_SOFTIRQ           //1,定时器的下半部
   NET_TX_SOFTIRQ          //2,发送网络数据包
   NET_RX_SOFTIRQ          //3,接收网络数据包
   SCSI_SOFTIRQ            //4,SCSI的下半部
   TASKLET_SOFTIRQ         //5,tasklet
  可以选择定义优先级在这几个中间,一般插在网络之后最后一项之前。
  软中断的注册,
  open_softirq(索引号,处理函数,相关数据);

  软中断处理程序执行时候,允许响应中断,但它自己不能睡眠,在一个处理程序运行的时候,当前处理器上的软中断被禁止。其它处理器仍可以执行别的软中断。实际上如果 同一个软中断在它被执行的时候再次被触发,那么另外一个处理器可以同事运行其处理程序,因为进入接口只有一个,所以任何共享数据即使内部的全局变量都要严格保护。

  tasklet本身也是软中断,只不过同一个处理程序的多个实例不能在多个处理器上运行。
  raise_softirq()可以将一个软中断设置为挂起,让他在下次调用do_softirq时候运行。
  该函数在触发一个软中断之前要先禁止中断,触发后在回复回原来的状态,如果中断本来就被禁止了,可以使用raise_softirq_irqoff.
  3、tasklet
        tasklet是利用软中断实现的一种下半部机制,在执行频率很高和连续性要求很高的情况下使用软中断,否则请使用tasklet。
  tasklet本身也是软中断,tasklet由tasklet_struct结构表示,每个结构体代表一个tasklet,在<linux/interrupt.h>中定义: 
  struct tasklet_struct{
        struct tasklet_struct *next;         //下一个tasklet
        unsigned long state;                  //状态,0,TASKLET_STATE_SCHED,TASKLET_STATE_RUN.
        atomic_t      count;                      //引用计数,如果不为0则tasklet被禁止,不允许执行,如果为0,激活
        void (*func)(unsigned long);    //tasklet处理函数
        unsigned long data;                  //处理函数参数
  }
      结构tasklet_vec代表普通tasklet,tasklet_hi_vec代表优先级高的tasklet,tasklet由tasklet_schedule()和
 tasklet_hi_schedule()函数进行调度,他们接受一个指向tasklet_stuct结构的指针作为参数,看一下tasklet_schedule()细节,
 a.检查tasklet转台是否为tasklet_state_sched,如果是说明tasklet已经被调度过了,函数立即返回,此时可能
    该tasklet被调度但还没有被执行。
 b.保存中断状态,禁止本地中断。保证数据不会弄乱。
 c.把要调度的tasklet加到对应的tasklet_VEC或者tasklet_hi_vec中去
 d.唤起TASKLET_SOFTIRQ或者TASKLET_IRQ软中断,下次就会执行do_softirq调用到
 e.恢复中断
 其中tasklet_action()和tasklet_hi_action()是tasklet处理的核心:
 a.禁止中断,
 b将当前处理器上的该链表清空
 c.允许响应中断
 d.循环遍历获得链表上的每一个待处理的tasklet
 e.多处理器通过检测tasklet_state_run来判断是否在其它处理器上运行,如果运行那么现在该处理器不运行,同一  时间相同的tasklet只执行一个
 f.如果当前的tasklet没有执行,将其状态标志位tasklet_state_run这样别的处理器就不会执行他
 g.检查count是否为0,如果被禁止,跳到下一个
 h.执行tasklet 
 j.清除tasklet的state状态
 k.重复执行下一个tasklet,直到没有剩余的等待处理的tasklet。
 所有的tasklet都是通过HI_SOFTIRQ和TASKLET_SOFTIRQ这两个软中断实现,在他们之上又延伸了一层。
  4.tasklet的使用:
   a.声明你的tasklet

   可以静态也可以动态

   静态分配:DECLARE_TASKLET(name, func, data);   //引用计数为0,处于激活状态
   DELARE_TASKLET_DISABLED(name, func, data);     //处于禁止状态
   
   动态创建:tasklet_init(t, tasklet_handler, dev);
   b.编写你自己的tasklet处理程序
      void tasklet_handler(unsigned long data)
  靠软中断实现,不能睡眠,以为着你不能在tasklet中使用信号量或者其他阻塞函数,允许相应中断。
  c.调度你自己的tasklet
     通过调用tasklet_schedule()函数并传递给它相应的tasklet_struct指针,该tasklet就会被调用执行:
     tasklet_schedule(&my_tasklet);
     在tasklet被调度以后,只要有机会它就会尽可能早的运行,如果他还能没有得到运行之前,如果有一个相同的tasklet又被调度了,
     tasklet被调度时的第一个步骤就是检查是否重复,如果重复那么就不会再调度,如果这时它已经在另外一个处理器上运行了,那么这个新的tasklet会被重新调度并再次运行。
    你可以使用tasklet_disable()函数来禁止某个指定的tasklet,如果该tasklet当前正在执行,那么这个函数会等到它执行完毕再返回,
    也可以使用tasklet_disable_nosync()函数,它可以用来禁止制定的tasklet,不过不会等待执行完毕,相应的tasklet_enable可以激活一个tasklet,
    tasklet_kill()用于去掉一个tasklet,该函数可能会引起睡眠,所以禁止在终端上下文中使用。

5.ksoftirqd.
   每个处理器都有一组辅助处理软中断的内核线程,当内核中出现大量软中断的时候,这些内核进程就会辅助处理它。
   大量软中断的时候可能会造成重复触发,导致系统一直进行软中断处理,作为改进,当大量软中断出现的时候,内核会唤醒一组
   内核线程来处理这些负载,这些线程放在最低的优先级,但是肯定会执行。

6.工作队列
   工作队列子系统是一个用于创建内核线程的接口,通过它创建的进程负载执行由内核其他部分排到队列里的任务。
   它创建的这些线程被称为工作者线程,工作队列可以让你的驱动程序创建一个专门的工作者线程来处理需要推后的工作,不过工作队列子系统提供了一个默认的工作者线程来 处理这些工作,因此,工作队列最基本的表现形式就转变成了把要推后执行的任务交给特定的通用线程这样一种接口。
   默认的工作线程叫做events/n.
四.下半部机制的选择
   a.软中断在中断上下文,tasklet在中断上下文,工作队列在进程。
   b.如果不需要休眠,tasklet是你的选择,如果需要休眠,工作队列是你的选择,如果必须专注于性能,考虑软中断。
   c.下半部之间加锁
   d.tasklet的一个好处是在于它负责执行的序列化保障:两个相同类型的tasklet不允许同时执行,即使在不同的处理器上也不行,tasklet之间需要锁同步。
   e.软中断根本不保证执行序列化,需要锁。
   f.如果进程上下文和一个下半部共享数据,在访问这些数据之前需要禁止下半部并得到锁的使用权,为了本地和smp的保护而且防止死锁出现。
   g.如果中断上下文和一个下半部共享数据,在访问数据之前,需要禁止中断并得到锁的使用权。
   h.任何在工作队列中被共享的数据也需要使用锁机制。
   i.禁止下半部,一般纯粹的禁止下半部的处理是不够的,常见的做法是先得到一个锁然后再禁止下半部的处理,驱动程序中常用的使用都是这种方法。
   j.禁止所有的下半部,可以是使用local_bh_disable(),允许下半部处理,可以调用local_bh_enable()函数,嵌套的数量要对应。
  

-----参考<linux内核设计与实现>

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值