软中断、tasklet、工作队列
为了减少中断处理函数的响应时间,把那些可以推迟执行的工作交给软中断、tasklet、工作队列来执行。
软中断需要用到两个关键数据结构:
thread_info描述符中的preempt_count字段用来记录当前cpu被抢占的状态
抢占是指进程在内核状态被其它进程替换,比如A正在运行异常处理程序,这时中断发生,唤醒B进程并替换A进程。 硬中断和软中断处理程序都不能抢占。
第二个是softirq_vec数组,每个元素指向一个软中断需要执行的函数以及数据。
第三个是软中断掩码,每个cpu有一个本地掩码,用来记录软中断的激活状态。
在硬中断处理函数完成IO处理后或 softirgd/n 内核线程被唤醒时,会调用raise_softirq()来激活软中断,它会设置掩码中的相应位,如果此时preempt_count=0会唤醒 softirgd/n 线程来真正执行软中断。
每一个cpu会对应一个softirq线程,并用这个线程的上下文来执行软中断,软中断执行前也会检查一次preempt_count, 软中断执行过程中会复制一份本地掩码以便激活新的软中断并且打开硬中断,但是关闭当前cpu的其它软中断(通过preempt_count不为0)。使用softirq内核线程的好处是,虽然执行软中断时不允许抢占,但是线程内部循环的间隙可以允许抢占,可以防止软中断占用cpu过久。
tasklet是在软中断的基础上实现的,但是它维护了两个标志,TASKLET_STATE_SCHED表示当前tasklet已经被激活,TASKLET_STATE_RUN表示当前tasklet已经被某个cpu运行,准备运行tasklet的时候会检查TASKLET_STATE_RUN,防止不同cpu运行同一个tasklet。
工作队列由工作线程执行,可以支持抢占。每一个cpu有一个工作线程,对应cpu_workqueue_struct结构,内部的work_list记录了真正需要执行的函数以及数据。由于工作队列和软中断都是由内核线程执行的,所以都访问不到用户空间。