欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

linux内核的软中断实现教程

程序员文章站 2022-03-27 09:52:14
...

  软中断是内核提供的一种延迟机制,完全由软件触发。虽然是延迟机制,实际上,在大多数情况下,它比普通进程能够得到更快的响应。软中断也是内核其他机制的基础,如tasklet、高分辨率timer等。

  软中断资料有限,目前内核中实现了10中类型的软中断

  1. enum

  2. {

  3. HI_SOFTIRQ=0,

  4. TIMER_SOFTIRQ,

  5. NET_TX_SOFTIRQ,

  6. NET_RX_SOFTIRQ,

  7. BLOCK_SOFTIRQ,

  8. BLOCK_IOPOLL_SOFTIRQ,

  9. TASKLET_SOFTIRQ,

  10. SCHED_SOFTIRQ,

  11. HRTIMER_SOFTIRQ,

  12. RCU_SOFTIRQ,/*PreferableRCUshouldalwaysbethelastsoftirq*/

  13.

  14. NR_SOFTIRQS

  15. };

  内核开发者不建议擅自增加软中断数量,如果需要新的软中断,尽可能它实现为基于tasklet形式

  1、ksoftirqd

  early_initcall(spawn_ksoftirqd);---初始化的时候调用spawn_ksoftirqd函数。

  smpboot_register_percpu_thread(&softirq_threads)---热插拔阶段为每个percpu上创建一个ksoftirqd守护进程

  1.DEFINE_PER_CPU(structtask_struct*,ksoftirqd);

  2.

  3. staticstructsmp_hotplug_threadsoftirq_threads={

  4. =&ksoftirqd,

  5. .thread_should_run=ksoftirqd_should_run,/*判断是否应该运行处理软中断*/

  6. .thread_fn=run_ksoftirqd,/*运行处理软中断*/

  7. .thread_comm="ksoftirqd/%u",

  8. };

  9.

  10. staticvoidrun_ksoftirqd(unsignedintcpu)

  11. {

  12. /*关闭本地cpu中断*/

  13. local_irq_disable();

  14. if(local_softirq_pending()){/*检查本地cpu上是否有软中断挂起*/

  15. /*

  16. *Wecansafelyrunsoftirqoninlinestack,aswearenotdeep

  17. *inthetaskstackhere.

  18. */

  19. __do_softirq();/*处理软中断*/

  20. local_irq_enable();/*使能本地cpu中断*/

  21. cond_resched();/*有条件的重新调度*/

  22.

  23. preempt_disable();

  24. rcu_note_context_switch(cpu);

  25. preempt_enable();

  26.

  27. return;

  28. }

  29. local_irq_enable();

  30. }

  2、结构

  Irq_cpustat_t,多个软中断可以同时在多个cpu运行,就算是同一个软中断,也有可能同时在多个cpu上运行。内核为每个cpu都管理着一个待决软中断pedding,他就是Irq_cpustat_t

  1. typedefstruct{

  2. unsignedint__softirq_pending;

  3. }____cacheline_alignedirq_cpustat_t;

  4.

  5.irq_cpustat_tirq_stat[NR_CPUS]____cacheline_aligned;

  1. structsoftirq_action

  2. {

  3. void(*action)(structsoftirq_action*);

  4. };

  5. staticstructsoftirq_actionsoftirq_vec[NR_SOFTIRQS]__cacheline_aligned_in_smp;

  __softirq_pending中每个bit,对应某一个软中断,某个Bit被置位,说明有相应的软总段等待处理。因此最多只能定义32个软中断类型。

  3、软中断触发

  想触发软中断,只需要调用raise_softirq即可,它的实现简单先关闭本地cpu中断,然后调用raise_softirq_irqoff,再打开本地cpu中断。

  1. voidraise_softirq(unsignedintnr)

  2. {

  3. unsignedlongflags;

  4.

  5. local_irq_save(flags);

  6. raise_softirq_irqoff(nr);

  7. local_irq_restore(flags);

  8. }

  再来看raise_softirq_irqoff

  1. inlinevoidraise_softirq_irqoff(unsignedintnr)

  2. {

  3. __raise_softirq_irqoff(nr);

  4.

  5. ......

  6. if(!in_interrupt())

  7. wakeup_softirqd();

  8. }

  先通过__raise_softirq_irqoff设置cpu的软中断pending标志位(irq_stat(NR_CPUS)),然后通过in_interrupt判断是否在中断上下文中,如果不成立,则唤醒软中断守护进程,在守护进程中执行软中断的回调函数。

  4、软中断的执行

  软中断有两种执行方式,一种是在中断调用结束时,一种是在ksoftirqd守护进程中

  1. /*

  2. *Exitaninterruptcontextcesssoftirqsifneededandpossible:

  3. */

  4. voidirq_exit(void)

  5. {

  6. #ifndef__ARCH_IRQ_EXIT_IRQS_DISABLED

  7. local_irq_disable();

  8. #else

  9. WARN_ON_ONCE(!irqs_disabled());

  10. #endif

  11.

  12. account_irq_exit_time(current);

  13. preempt_count_sub(HARDIRQ_OFFSET);

  14. /*

  15. 在中断发生嵌套时,通过in_interrupt能确保在最外层的中断Irq_exit阶段

  16. invoke_softirq才会被调用

  17. */

  18. if(!in_interrupt()&&local_softirq_pending())

  19. invoke_softirq();

  20.

  21. tick_irq_exit();

  22. rcu_irq_exit();

  23. trace_hardirq_exit();/*mustbelast!*/

  24. }

  代码最终都会进入到__do_softirq中,执行软中断的重点都在该函数中。

  1. asmlinkagevoid__do_softirq(void)

  2. {

  3. unsignedlongend=jiffies+MAX_SOFTIRQ_TIME;

  4. unsignedlongold_flags=current->flags;

  5. intmax_restart=MAX_SOFTIRQ_RESTART;

  6. structsoftirq_action*h;

  7. boolin_hardirq;

  8. __u32pending;

  9. intsoftirq_bit;

  10. intcpu;

  11.

  12. /*

  13. *MaskoutPF_MEMALLOCscurrenttaskcontextisborrowedforthe

  14. *softirq.AsoftirqhandledsuchasnetworkRXmightsetPF_MEMALLOC

  15. *againifthesocketisrelatedtoswap

  16. */

  17. current->flags&=~PF_MEMALLOC;

  18. /*

  19. 复制软中断掩码到局部变量,这是必要的

  20. 因为local_softirq_pending中的值在开中断后将不再可靠,必须先保存

  21. */

  22. pending=local_softirq_pending();

  23. account_irq_enter_time(current);

  24.

  25. /*

  26. 标志下面的代码正在处理softirq

  27. */

  28. __local_bh_disable_ip(_RET_IP_,SOFTIRQ_OFFSET);

  29. in_hardirq=lockdep_softirq_start();

  30.

  31. cpu=smp_processor_id();

  32. restart:

  33. /*Resetthependingbitmaskbeforeenablingirqs*/

  34. set_softirq_pending(0);/*清空pending*/

  35.

  36. local_irq_enable();/*打开本地中断*/

  37.

  38. /*

  39. 到这里已经打开了本地中断,下面QQ靓号买号平台在软中断处理执行过程中可能会被硬件中断抢占

  40. */

  41. /*

  42. 根据软中断标志位处理软中断

  43. */

  44. /* softirq_vec 存放action的结构体*/

  45. h=softirq_vec;

  46.

  47. while((softirq_bit=ffs(pending))){

  48. unsignedintvec_nr;

  49. intprev_count;

  50.

  51. h+=softirq_bit-1;

  52.

  53. vec_nr=h-softirq_vec;

  54. prev_count=preempt_count();

  55.

  56. kstat_incr_softirqs_this_cpu(vec_nr);

  57.

  58. trace_softirq_entry(vec_nr);

  59. h->action(h);

  60. trace_softirq_exit(vec_nr);

  61. if(unlikely(prev_count!=preempt_count())){

  62. pr_err("huh,enteredsoftirq%u%s%pwithpreempt_count%08x,exitedwith%08x

  ",

  63. vec_nr,softirq_to_name[vec_nr],h->action,

  64. prev_count,preempt_count());

  65. preempt_count_set(prev_count);

  66. }

  67. rcu_bh_qs(cpu);

  68. h++;

  69. pending>>=softirq_bit;

  70. }

  71.

  72. /*关掉本地中断*/

  73. local_irq_disable();

  74. /*

  75. 由于前面有打开过本地中断,因此这次可能会有新的软中断未处理,再检查处理,

  76. */

  77. pending=local_softirq_pending();

  78. if(pending){

  79. if(time_before(jiffies,end)&&!need_resched()&&

  80. --max_restart)

  81. gotorestart;

  82.

  83. wakeup_softirqd();

  84. }

  85.

  86.

  87. lockdep_softirq_end(in_hardirq);

  88. account_irq_exit_time(current);

  89. __local_bh_enable(SOFTIRQ_OFFSET);

  90. WARN_ON_ONCE(in_interrupt());

  91. tsk_restore_flags(current,old_flags,PF_MEMALLOC);

  92. }

  5、软中断注册

  void open_softirq(int nr,void (*action)(struct softirq_action *))