softirq软中断线程和tasklet队列原理分析
软中断线程创建
在smpboot_register_percpu_thread函数中会将softirq_threads结构体添加到hotplug_threads全局链表。然后在smpboot_create_threads函数中会创建内核线程。
即smpboot_thread_fn函数为软中断线程的主循环函数,在此主循环函数中与其他内核线程类似为while(1)死循环,且通过kthread_should_stop控制内核线程的退出。然后通过switch (td->status)状态机根据不同的回调函数。
如果ht->thread_should_run(td->cpu)返回0即ksoftirqd_should_run()函数返回0表示不存在软中断进行schedule()切换,如果存在软中断需要执行则调用ht->thread_fn(td->cpu);即run_ksoftirqd函数。
软中断线程执行
在run_ksoftirqd函数中通过读取local_softirq_pending读取标致位的方式确定哪些类型的软中断需要执行。
在__do_softirq函数中首先通过local_softirq_pending读取哪些软中断的比特位被置位1即需要执行。然后通过while ((softirq_bit = ffs(pending)))循环的方式得到具体的比特位softirq_bit,随后h += softirq_bit - 1;即得到该比特位对应的软中断描述符struct softirq_action。
上述的h++;和pending >>= softirq_bit;都是为了while循环找到具体被置位1的软中断标志位。
通过open_softirq函数设置对应软中断类型的回调函数action。上诉枚举即为目前已定义的软中断类型。
软中断标志位的设置
在or_softirq_pending(1UL << nr);进行软中断标志位的设置,即对对应的比特位置位为1。下次执行软中断run_ksoftirqd函数时会根据此标志位执行对应软中断类型的工作函数。
tasklet工作队列的创建
在softirq_init初始化函数中首先会调用open_softirq设置低优先级TASKLET_SOFTIRQ和高优先级HI_SOFTIRQ的tasklet工作队列的回调函数。
tasklet工作队列的运行
通过调用__tasklet_schedule接口进而调用raise_softirq_irqoff函数置位对应的软中断标志位。进而在软中断被调度运行时能执行软中断的回调函数tasklet_action。
在tasklet_action_common函数中会依次遍历struct tasklet_struct工作队列链表,执行其回调函数t->func(t->data);
此处的while循环执行每个tasklet时,有可能会有新的tasklet加入队列尾部。循环结束后,链表已经执行完成,但新加的tasklet没有执行。直接返回会导致这些新tasklet无法执行。所以需要调用__raise_softirq_irqoff,强制触发一次softirq。这样softirq会再次运行,从头部重新取tasklet链表,包含新加的tasklet。所以__raise_softirq_irqoff的目的,就是为了保证可能加入的新tasklet也能得到执行,不会遗漏。