Linux内核学习笔记:中断的下半部分


因为中断的处理过程中,同种类型的中断是被禁止的。并且中断处理应该越短越好,这样才能减少丢失的中断。所以linux将中断处理分为两部分。关键紧急的事情在中断上下文处理,不紧急或者花费时间较多的事情在所谓的下半部分中执行。中断的下半部分是一种内核机制,它运行的时候允许中断的产生,可以分为软中断与工作队列。软中断又包含:tasklet 与内核定时器。软中断是一种特殊的内核控制路径,它不属于任何进程,所以不能被抢占,不可以睡眠。而工作队列是一种内核线程,有工作的时候醒来工作,没事的时候处于睡眠状态。

相关阅读:Linux内核学习笔记:中断与异常

一. 软中断

软中断是静态分配的,这也就意味这如果想定义新的软中断就必须重新编译内核。软中断可以并发的运行在多处理器上,即使同一个软中断也是这样。所以,软中断函数必须是可重入函数,而且需要使用自选锁来保护数据结构。linux使用有限数量的软中断,一般而言不需要定义新的软中断,因为tasklet就足够用了。而且tasklet不必是可重入的。目前linux使用以下几种软中断:搞优先级的tasklet,内核定时器,网卡接收软中断,网卡发送软中断,SCSI命令处理软中断,低优先级的软中断。

软中断使用的数据结构是softirq_vec数组,该数组包含softirq_action的32个元素,也就意味着linux总共可以有32个软中断。softirq_action结构有两个域:一个是action指针一个是data。与软中断相关的进程描述符的字段是thread_info里面的preempt_count。它包含:抢占计数器,软中断计数器,中断计数器。内核用来了解进程运行的环境。in_interupt函数读取这个字段,只要有一个计数器不为0,那么就返回1.说明进程运行在中断上下文,这时是禁止内核抢占的。

内核处理软中断需要三步:(1)初始化软中断,初始化softirq_vec数组,初始化软中断处理函数以及所使用的数据结构。(2)激活软中断。raise_softirq激活软中断。(3)周期性检查,并处理软中断。内核在do_IRQ完成中断处理调用irq_exit的时候会检查未处理的软中断。或者在ksoftirqd/n线程被唤醒时检查软中断。

如果确实有未处理的软中,那么内核调用do_softirq函数处理软中断。这个函数依次处理激活的软中断,执行注册的函数。主要完成以下几步:

(1)调用in_interrupt,如果返回1说明处于中断上下文。函数返回。

(2)调用__do_softirq函数

这个函数是处理软中断的基本函数,它主要吧软中断的位掩码复制到局部变量pending中,清除本地CPU的软中断位图。根据pending的每一位执行对应的软中断处理函数。注意在软中断处理过程中可能产生新的软中断。所以,这个函数会循环执行,直到没有新的软中断。

在软中断处理过程中,__do_softirq是循环执行的,如果有软中断不停的产生新的软中断,那么会带来一个问题就是__do_softirq会一直占用CPU,用户进程没有机会运行。但是如果__do_softirq不循环检查新的软中断,那么软中断就会延迟很久执行。为了解决这个问题,linux采用ksoftirqd内核线程的办法。__do_softirq会循环有限次来处理新的软中断,如果还有新的软中断就会唤醒ksoftirqd内核线程来执行,这个内核线程的优先级比较低,所以即使有大量的软中断需要处理,对用户进程的影响也比较小。

二. tasklet

tasklet也是一种软中断,但是tasklet比软中断有着更好的并发特性。是io驱动程序首选的可延迟函数方法。tasklet有如下的特性:

(1)tasklet可以在内核运行的时候定义

(2)相同类型的tasklet不能在用一个CPU上并发运行,也不能在不同的CPU上并发运行

(1)不同类型的tasklet不能在同一个CPU上并发运行,但是可以在不同的CPU上并发运行。所以如果不同的tasklet访问相同的数据结构,需要加一定的锁保护

三. 工作队列

工作队列由内核线程来执行。主要的数据结构是workqueue_struct结构。这个结构是是一个cpu_workqueue_struct的数组。cpu_workqueue_struct结构是工作哦队列的基本结构。主要有此工作队列工作的链表,还有内核线程的进程描述符的指针。工作队列的工作是由work_struct结构组成,这个结构有需要执行的函数,以及传输的数据等字段。建立工作队列是一项非常耗时的操作,因为它会建立一个内核线程。所以linux默认建立了一个工作队列来供使用。每一个CPU一个这样的工作队列。

相关内容