linux内核的软中断实现教程
软中断是内核提供的一种延迟机制,完全由软件触发。虽然是延迟机制,实际上,在大多数情况下,它比普通进程能够得到更快的响应。软中断也是内核其他机制的基础,如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 *))