软中断
软中断是内核提供的一种延迟机制,完全由软件触发。虽然是延迟机制,实际上,在大多数情况下,它比普通进程能够得到更快的响应。软中断也是内核其他机制的基础,如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, /* Preferable RCU should always be the last softirq */
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(struct task_struct *, ksoftirqd);
2.
3. static struct smp_hotplug_thread softirq_threads = {
4. .store = &ksoftirqd,
5. .thread_should_run = ksoftirqd_should_run, /*判断是否应该运行处理软中断*/
6. .thread_fn = run_ksoftirqd, /*运行处理软中断*/
7. .thread_comm = "ksoftirqd/%u",
8. };
9.
10. static void run_ksoftirqd(unsigned int cpu)
11. {
12. /*关闭本地cpu中断*/
13. local_irq_disable();
14. if (local_softirq_pending()) { /*检查本地cpu上是否有软中断挂起*/
15. /*
16. * We can safely run softirq on inline stack, as we are not deep
17. * in the task stack here.
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. typedef struct {
2. unsigned int __softirq_pending;
3. } ____cacheline_aligned irq_cpustat_t;
4.
5. irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;
1. struct softirq_action
2. {
3. void (*action)(struct softirq_action *);
4. };
5. static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
__softirq_pending中每个bit,对应某一个软中断,某个Bit被置位,说明有相应的软总段等待处理。因此最多只能定义32个软中断类型。
3、 软中断触发
想触发软中断,只需要调用raise_softirq即可,它的实现简单先关闭本地cpu中断,然后调用raise_softirq_irqoff,再打开本地cpu中断。
1. void raise_softirq(unsigned int nr)
2. {
3. unsigned long flags;
4.
5. local_irq_save(flags);
6. raise_softirq_irqoff(nr);
7. local_irq_restore(flags);
8. }
再来看raise_softirq_irqoff
1. inline void raise_softirq_irqoff(unsigned int nr)
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. * Exit an interrupt context. Process softirqs if needed and possible:
3. */
4. void irq_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(); /* must be last! */
24. }
代码最终都会进入到__do_softirq中,执行软中断的重点都在该函数中。
1. asmlinkage void __do_softirq(void)
2. {
3. unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
4. unsigned long old_flags = current->flags;
5. int max_restart = MAX_SOFTIRQ_RESTART;
6. struct softirq_action *h;
7. bool in_hardirq;
8. __u32 pending;
9. int softirq_bit;
10. int cpu;
11.
12. /*
13. * Mask out PF_MEMALLOC s current task context is borrowed for the
14. * softirq. A softirq handled such as network RX might set PF_MEMALLOC
15. * again if the socket is related to swap
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. /* Reset the pending bitmask before enabling irqs */
34. set_softirq_pending(0);/*清空pending*/
35.
36. local_irq_enable(); /*打开本地中断*/
37.
38. /*
39. 到这里已经打开了本地中断,下面在软中断处理执行过程中可能会被硬件中断抢占
40. */
41. /*
42. 根据软中断标志位处理软中断
43. */
44. /* softirq_vec 存放action的结构体*/
45. h = softirq_vec;
46.
47. while ((softirq_bit = ffs(pending))) {
48. unsigned int vec_nr;
49. int prev_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, entered softirq %u %s %p with preempt_count %08x, exited with %08x?\n",
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. goto restart;
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 *))