Linux2.4.x内核软中断机制[3]

[入库:2005年9月19日] [更新:2007年3月24日]

本文简介:

  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上并发。
  
  六. 使用示例
  
  softirq作为一种底层机制,很少由内核程序员直接使用,因此,这里的使用范例仅对其余几种软中断机制。
  
  1.bottom half
  
  原有的bottom half用法在drivers/char/serial.c中还能看到,包括三个步骤:
  
  init_bh(SERIAL_BH,do_serial_bh); //在串口设备的初始化函数rs_init()中,do_serial_bh()是处理函数
  mark_bh(SERIAL_BH); //在rs_sched_event()中,这个函数由中断处理例程调用
  remove_bh(SERIAL_BH); //在串口设备的结束函数rs_fini()中调用
  
  尽管逻辑上还是这么三步,但在do_serial_bh()函数中的动作却是启动一个task queue:run_task_queue(&tq_serial),而在rs_sched_event()中,mark_bh()之前调用的则是queue_task(...,&tq_serial),也就是说串口bottom half已经结合task queue使用了。而那些更通用一些的bottom half,比如IMMEDIATE_BH,更是必须要与task queue结合使用,而且一般情况下,task queue也很少独立使用,而是与bottom half结合,这在下一节task queue使用示例中可以清楚地看到。
  
  2.task queue
  
  一般来说,程序员很少自己定义task queue,而是结合bottom half,直接使用系统预定义的tq_immediate等,尤以tq_immediate使用最频繁。看以下代码段,节选自drivers/block/floppy.c:
  
  static struct tq_struct floppy_tq; //定义一个tq_struct结构变量floppy_tq,不需要作其他初始化动作
  
  static void schedule_bh( void (*handler)(void*) )
  {
   floppy_tq.routine = (void *)(void *) handler;
   //指定floppy_tq的调用函数为handler,不需要考虑floppy_tq中的其他域
   queue_task(&floppy_tq, &tq_immediate);
   //将floppy_tq加入到tq_immediate中
   mark_bh(IMMEDIATE_BH);
   //激活IMMEDIATE_BH,由上所述可知,
   这实际上将引发一个软中断来执行tq_immediate中挂接的各个函数
  }
  
  当然,我们还是可以定义并使用自己的task queue,而不用tq_immediate,在drivers/char/serial.c中提到的tq_serial就是串口驱动自己定义的:
  
  static DECLARE_TASK_QUEUE(tq_serial);
  
  此时就需要自行调用run_task_queue(&tq_serial)来启动其中的函数了,因此并不常用。
  
  3.tasklet
  
  这是比task queue和bottom half更加强大的一套软中断机制,使用上也相对简单,见下面代码段:
  
  1: void foo_tasklet_action(unsigned long t);
  2: unsigned long stop_tasklet;
  3: DECLARE_TASKLET(foo_tasklet, foo_tasklet_action, 0);
  4: void foo_tasklet_action(unsigned long t)
  5: {
  6: //do something
  7:
  8: //reschedule
  9: if(!stop_tasklet)
  10: tasklet_schedule(&foo_tasklet);
  11: }
  12: void foo_init(void)
  13: {
  14: stop_tasklet=0;
  15: tasklet_schedule(&foo_tasklet);
  16: }
  17: void foo_clean(void)
  18: {
  19: stop_tasklet=1;
  20: tasklet_kill(&foo_tasklet);
  21: }
  
  这个比较完整的代码段利用一个反复执行的tasklet来完成一定的工作,首先在第3行定义foo_tasklet,与相应的动作函数foo_tasklet_action相关联,并指定foo_tasklet_action()的参数为0。虽然此处以0为参数,但也同样可以指定有意义的其他参数值,但需要注意的是,这个参数值在定义的时候必须是有固定值的变量或常数(如上例),也就是说可以定义一个全局变量,将其地址作为参数传给foo_tasklet_action(),例如:
  
  int flags;
  DECLARE_TASKLET(foo_tasklet,foo_tasklet_action,&flags);
  void foo_tasklet_action(unsigned long t)
  {
   int flags=*(int *)t;
  ...
  }
  
  这样就可以通过改变flags的值将信息带入tasklet中。直接在DECLARE_TASKLET处填写flags,gcc会报"initializer element is not constant"错。
  
  第9、10行是一种RESCHEDULE的技术。我们知道,一个tasklet执行结束后,它就从执行队列里删除了,要想重新让它转入运行,必须重新调用tasklet_schedule(),调用的时机可以是某个事件发生的时候,也可以是像这样在tasklet动作中。而这种reschedule技术将导致tasklet永远运行,因此在子系统退出时,应该有办法停止tasklet。stop_tasklet变量和tasklet_kill()就是干这个的。
  
  参考资料:
  
  《Linux内核源代码情景分析》,毛德操、胡希明著,2001年9月浙江大学出版社
  《Linux内核2.4版源代码分析大全》,李善平等著,2002年1月机械工业出版社
  《Linux Device Drivers》,Alessandro Rubini & Jonathan Corbet,2001年8月 O'Reilly

本文关键:Linux2.4.x内核软中断机制
  相关方案
Google
 

本站最佳浏览方式为 分辨率 1024x768 IE 6.0(或更高版本的 IE浏览器)

go top