linux软中断的读书笔记

原创 2006年06月06日 16:02:00
        一. 软中断概况
  
  软中断是利用硬件中断的概念,用软件方式进行模拟,实现宏观上的异步执行效果。很多情况下,软中断和"信号"有些类似,同时,软中断又是和硬中断相对应的,"硬中断是外部设备对CPU的中断","软中断通常是硬中断服务程序对内核的中断","信号则是由内核(或其他进程)对某个进程的中断"(《Linux内核源代码情景分析》第三章)。软中断的一种典型应用就是所谓的"下半部"(bottom half),它的得名来自于将硬件中断处理分离成"上半部"和"下半部"两个阶段的机制:上半部在屏蔽中断的上下文中运行,用于完成关键性的处理动作;而下半部则相对来说并不是非常紧急的,通常还是比较耗时的,因此由系统自行安排运行时机,不在中断服务上下文中执行。bottom half的应用也是激励内核发展出目前的软中断机制的原因,因此,我们先从bottom half的实现开始。
  
  二. bottom half
  
  在Linux内核中,bottom half通常用"bh"表示,最初用于在特权级较低的上下文中完成中断服务的非关键耗时动作,现在也用于一切可在低优先级的上下文中执行的异步动作。最早的bottom half实现是借用中断向量表的方式,在目前的2.4.x内核中仍然可以看到:
  
  static void (*bh_base[32])(void); /* kernel/softirq.c */
  系统如此定义了一个函数指针数组,共有32个函数指针,采用数组索引来访问,与此相对应的是一套函数:
  
  void init_bh(int nr,void (*routine)(void));
  为第nr个函数指针赋值为routine。
  
  void remove_bh(int nr);
  动作与init_bh()相反,卸下nr函数指针。
  
  void mark_bh(int nr);
  标志第nr个bottom half可执行了。
  
  由于历史的原因,bh_base各个函数指针位置大多有了预定义的意义,在v2.4.2内核里有这样一个枚举:
  
  enum {
   TIMER_BH = 0,
   TQUEUE_BH,
   DIGI_BH,
   SERIAL_BH,
   RISCOM8_BH,
   SPECIALIX_BH,
   AURORA_BH,
   ESP_BH,
   SCSI_BH,
   IMMEDIATE_BH,
   CYCLADES_BH,
   CM206_BH,
   JS_BH,
   MACSERIAL_BH,
   ISICOM_BH
   };
  
  并约定某个驱动使用某个bottom half位置,比如串口中断就约定使用SERIAL_BH,现在我们用得多的主要是TIMER_BH、TQUEUE_BH和IMMEDIATE_BH,但语义已经很不一样了,因为整个bottom half的使用方式已经很不一样了,这三个函数仅仅是在接口上保持了向下兼容,在实现上一直都在随着内核的软中断机制在变。现在,在2.4.x内核里,它用的是tasklet机制。
  
  三. task queue
  
  在介绍tasklet之前,有必要先看看出现得更早一些的task queue机制。显而易见,原始的bottom half机制有几个很大的局限,最重要的一个就是个数限制在32个以内,随着系统硬件越来越多,软中断的应用范围越来越大,这个数目显然是不够用的,而且,每个bottom half上只能挂接一个函数,也是不够用的。因此,在2.0.x内核里,已经在用task queue(任务队列)的办法对其进行了扩充,这里使用的是2.4.2中的实现。
  
  task queue是在系统队列数据结构的基础上建成的,以下即为task queue的数据结构,定义在include/linux/tqueue.h中:
  
  struct tq_struct {
   struct list_head list; /* 链表结构 */
   unsigned long sync; /* 初识为0,入队时原子的置1,以避免重复入队 */
   void (*routine)(void *); /* 激活时调用的函数 */
   void *data; /* routine(data) */
  };
  
  typedef struct list_head task_queue;
  
  在使用时,按照下列步骤进行:
  
  DECLARE_TASK_QUEUE(my_tqueue); /* 定义一个my_tqueue,实际上就是一个以tq_struct为元素的list_head队列 */
  说明并定义一个tq_struct变量my_task;
  queue_task(&my_task,&my_tqueue); /* 将my_task注册到my_tqueue中 */
  run_task_queue(&my_tqueue); /* 在适当的时候手工启动my_tqueue */
  大多数情况下,都没有必要调用DECLARE_TASK_QUEUE()定义自己的task queue,因为系统已经预定义了三个task queue:
  
  tq_timer,由时钟中断服务程序启动;
  tq_immediate,在中断返回前以及schedule()函数中启动;
  tq_disk,内存管理模块内部使用。
  一般使用tq_immediate就可以完成大多数异步任务了。
  
  run_task_queue(task_queue *list)函数可用于启动list中挂接的所有task,可以手动调用,也可以挂接在上面提到的bottom half向量表中启动。以run_task_queue()作为bh_base[nr]的函数指针,实际上就是扩充了每个bottom half的函数句柄数,而对于系统预定义的tq_timer和tq_immediate的确是分别挂接在TQUEUE_BH和IMMEDIATE_BH上(注意,TIMER_BH没有如此使用,但TQUEUE_BH也是在do_timer()中启动的),从而可以用于扩充bottom half的个数。此时,不需要手工调用run_task_queue()(这原本就不合适),而只需调用mark_bh(IMMEDIATE_BH),让bottom half机制在合适的时候调度它。
  
  四. tasklet
  
  由上看出,task queue以bottom half为基础;而bottom half在v2.4.x中则以新引入的tasklet为实现基础。
  
  之所以引入tasklet,最主要的考虑是为了更好的支持SMP,提高SMP多个CPU的利用率:不同的tasklet可以同时运行于不同的CPU上。在它的源码注释中还说明了几点特性,归结为一点,就是:同一个tasklet只会在一个CPU上运行。
  
  struct tasklet_struct
  {
   struct tasklet_struct *next; /* 队列指针 */
   unsigned long state; /* tasklet的状态,按位操作,目前定义了两个位的含义:
   TASKLET_STATE_SCHED(第0位)或TASKLET_STATE_RUN(第1位) */
   atomic_t count; /* 引用计数,通常用1表示disabled */
   void (*func)(unsigned long); /* 函数指针 */
   unsigned long data; /* func(data) */
  };
  
  把上面的结构与tq_struct比较,可以看出,tasklet扩充了一点功能,主要是state属性,用于CPU间的同步。
  
  tasklet的使用相当简单:
  
  定义一个处理函数void my_tasklet_func(unsigned long);
  DECLARE_TASKLET(my_tasklet,my_tasklet_func,data); /*
  定义一个tasklet结构my_tasklet,与my_tasklet_func(data)函数相关联,相当于DECLARE_TASK_QUEUE() */
  tasklet_schedule(&my_tasklet); /*
  登记my_tasklet,允许系统在适当的时候进行调度运行,相当于queue_task(&my_task,&tq_immediate)和mark_bh(IMMEDIATE_BH) */
  可见tasklet的使用比task queue更简单,而且,tasklet还能更好的支持SMP结构,因此,在新的2.4.x内核中,tasklet是建议的异步任务执行机制。除了以上提到的使用步骤外,tasklet机制还提供了另外一些调用接口:
  
  DECLARE_TASKLET_DISABLED(name,function,data); /*
  和DECLARE_TASKLET()类似,不过即使被调度到也不会马上运行,必须等到enable */
  tasklet_enable(struct tasklet_struct *); /* tasklet使能 */
  tasklet_disble(struct tasklet_struct *); /* 禁用tasklet,只要tasklet还没运行,则会推迟到它被enable */
  tasklet_init(struct tasklet_struct *,void (*func)(unsigned long),unsigned long); /* 类似DECLARE_TASKLET() */
  tasklet_kill(struct tasklet_struct *); /* 清除指定tasklet的可调度位,即不允许调度该tasklet,但不做tasklet本身的清除 */
  
  前面提到过,在2.4.x内核中,bottom half是利用tasklet机制实现的,它表现在所有的bottom half动作都以一类tasklet的形式运行,这类tasklet与我们一般使用的tasklet不同。
  
  在2.4.x中,系统定义了两个tasklet队列的向量表,每个向量对应一个CPU(向量表大小为系统能支持的CPU最大个数,SMP方式下目前2.4.2为32)组织成一个tasklet链表:
  
  struct tasklet_head tasklet_vec[NR_CPUS] __cacheline_aligned;
  struct tasklet_head tasklet_hi_vec[NR_CPUS] __cacheline_aligned;
  
  另外,对于32个bottom half,系统也定义了对应的32个tasklet结构:
  
  struct tasklet_struct bh_task_vec[32];
  在软中断子系统初始化时,这组tasklet的动作被初始化为bh_action(nr),而bh_action(nr)就会去调用bh_base[nr]的函数指针,从而与bottom half的语义挂钩。mark_bh(nr)被实现为调用tasklet_hi_schedule(bh_tasklet_vec+nr),在这个函数中,bh_tasklet_vec[nr]将被挂接在tasklet_hi_vec[cpu]链上(其中cpu为当前cpu编号,也就是说哪个cpu提出了bottom half的请求,则在哪个cpu上执行该请求),然后激发HI_SOFTIRQ软中断信号,从而在HI_SOFTIRQ的中断响应中启动运行。
  
  tasklet_schedule(&my_tasklet)将把my_tasklet挂接到tasklet_vec[cpu]上,激发TASKLET_SOFTIRQ,在TASKLET_SOFTIRQ的中断响应中执行。HI_SOFTIRQ和TASKLET_SOFTIRQ是softirq子系统中的术语,下一节将对它做介绍。
  
  五. softirq
  
  从前面的讨论可以看出,task queue基于bottom half,bottom half基于tasklet,而tasklet则基于softirq。
  
  可以这么说,softirq沿用的是最早的bottom half思想,但在这个"bottom half"机制之上,已经实现了一个更加庞大和复杂的软中断子系统。
  
  struct softirq_action
  {
   void (*action)(struct softirq_action *);
   void *data;
  };
  static struct softirq_action softirq_vec[32] __cacheline_aligned;
  这个softirq_vec[]仅比bh_base[]增加了action()函数的参数,在执行上,softirq比bottom half的限制更少。
  
  和bottom half类似,系统也预定义了几个softirq_vec[]结构的用途,通过以下枚举表示:
  
  enum
  {
   HI_SOFTIRQ=0,
   NET_TX_SOFTIRQ,
   NET_RX_SOFTIRQ,
   TASKLET_SOFTIRQ
  };
  HI_SOFTIRQ被用于实现bottom half,TASKLET_SOFTIRQ用于公共的tasklet使用,NET_TX_SOFTIRQ和NET_RX_SOFTIRQ用于网络子系统的报文收发。在软中断子系统初始化(softirq_init())时,调用了open_softirq()对HI_SOFTIRQ和TASKLET_SOFTIRQ做了初始化:
  
  void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
  
  open_softirq()会填充softirq_vec[nr],将action和data设为传入的参数。TASKLET_SOFTIRQ填充为tasklet_action(NULL),HI_SOFTIRQ填充为tasklet_hi_action(NULL),在do_softirq()函数中,这两个函数会被调用,分别启动tasklet_vec[cpu]和tasklet_hi_vec[cpu]链上的tasklet运行。
  
  static inline void __cpu_raise_softirq(int cpu, int nr)
  
  这个函数用来激活软中断,实际上就是第cpu号CPU的第nr号软中断的active位置1。在do_softirq()中将判断这个active位。tasklet_schedule()和tasklet_hi_schedule()都会调用这个函数。
  
  do_softirq()有4个执行时机,分别是:从系统调用中返回(arch/i386/kernel/entry.S::ENTRY(ret_from_sys_call))、从异常中返回(arch/i386/kernel/entry.S::ret_from_exception标号)、调度程序中(kernel/sched.c::schedule()),以及处理完硬件中断之后(kernel/irq.c::do_IRQ())。它将遍历所有的softirq_vec,依次启动其中的action()。需要注意的是,软中断服务程序,不允许在硬中断服务程序中执行,也不允许在软中断服务程序中嵌套执行,但允许多个软中断服务程序同时在多个CPU上并发。
版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

linux读书笔记

  • 2015-08-22 11:15
  • 677KB
  • 下载

《Linux内核设计与实现》读书笔记(八)- 中断下半部的处理

在前一章也提到过,之所以中断会分成上下两部分,是由于中断对时限的要求非常高,需要尽快的响应硬件。 主要内容: 中断下半部处理实现中断下半部的机制总结中断下半部的实现中断实现示例   ...

《Linux内核设计与实现》读书笔记(七)- 中断处理

中断处理一般不是纯软件来实现的,需要硬件的支持。通过对中断的学习有助于更深入的了解系统的一些底层原理,特别是驱动程序的开发。 主要内容: 什么是中断中断类型中断相关函数中断处理机制中断控制...

《Linux内核设计与实现》读书笔记(七)- 中断处理

中断处理一般不是纯软件来实现的,需要硬件的支持。通过对中断的学习有助于更深入的了解系统的一些底层原理,特别是驱动程序的开发。 主要内容: 什么是中断中断类型中断相关函数中断处理机制中断控制...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)