- 中断和中断处理
- 中断:
- 一种特殊的电信号,由硬件设备发向处理器。(处理器收到中断后,通知操作系统,由操作系统负责处理这些数据)中断随时可以产生,无需考虑与处理器的时钟同步。
- 中断控制器:
- 连接硬件和处理器的简单电子芯片。(接收中断,给处理器发送电信号)
- 中断请求线(IRQ):
- 不同的设备对应不同的中断,每个中断都有一个唯一的中断值,称为中断请求线。
- 这样操作系统才能给不同的中断提供对应的中断处理程序。
- 异常:
- 又称为同步中断,必须考虑与处理器时钟同步。
- 中断处理程序:
- 响应特定中断的时候,内核会执行一个函数,称为中断处理程序或中断服务例程。
- 是对应设备的设备驱动程序的一部分。
- 设备驱动程序:是用于对设备进行 管理的内核代码。
- 运行在中断上下文中,可以随时执行。必须保证在尽量短的时间内执行完。
- 内核提供的接口 包括:注册、注销 中断处理程序、禁止中断、屏蔽中断线,以及检查中断系统的状态。
- 中断处理
- 上半部:中断处理程序(需要立刻完成的)
- 下半部:允许稍微延后执行的。
- 例如:网卡在接收到来自网络数据包时,需通知内核数据包到了(优化网络的吞吐量和传输周期,避免超时)。内核执行网卡中断处理程序应答,拷贝最新的网络数据包到内存。(紧急的事情,一旦拷贝延迟,网卡内存会溢出,数据包丢失)其他后续数据包的处理,将在下半部执行。
- 中断上下文:
- 所谓的“ 中断上下文”,其实也可以看作就是硬件传递过来的这些参数和内核需要保存的一些其他环境(主要是当前被打断执行的进程环境)。
- 中断上下文不可以 睡眠,有严格的时间限制。
- 中断处理机制的实现:
- 中断:
- 下半部处理
- 中断处理程序的局限性:
- 异步执行,有可能打断其他重要代码执行(包括其他中断处理程序)。避免打断太久,应越快越好。
- 最好情况:IRQF_DISABLED没有被设置,与该中断同级的其他中断会被屏蔽;最坏情况:IRQF_DISABLED设置了,当前处理器下的其他中断都会被屏蔽。导致硬件与操作系统无法通行。因此,应该执行越快越好。
- 需要对硬件进行操作,时限要求很高。
- 不在进程上下文中运行,因此不能阻塞。
- 综合上述的局限性,中断处理程序(上半部 )必须:快速、异步、简单。其他可以延后的操作,都放到下半部进行。
- 下半部任务:
- 执行与中断处理密切相关,但中断处理程序本身不执行的工作。
- 下半部只是延迟一点点,通常在中断处理程序一返回会马上运行。关键在于它们运行的时候,允许响应所有的中断。
- 上半部、下半部划分建议:
- 任务对时间非常敏感,可将其放在中断处理程序中执行。
- 任务和硬件相关,可将其放在中断处理程序中执行。
- 任务必须保证不被其他中断(特别是相同的中断)打断,可将其放在中断处理程序中执行。
- 其他任务,考虑放在 下半部执行。
- 下半部的多种方法:
- 软中断
- 编译期间静态分配的,由softirq_action结构表示。有32个该结构体的数组,每个被注册的软中断都占据该数组的一项,因此最多可能有32个软 中断。
- 保留给系统中对时间要求最严格以及最重要的下半部使用。目前只有:网络、SCSI子系统直接使用软中断;内核定时器、tasklet都是建立 在软中断之上的。
- 优先级号越小,优先级越高。
- 软中断处理程序
- 多处理器可以并行,软中断禁止仅针对当前处理器。因此必须严格使用锁保护共享数据。大部分软中断处理程序,都通过采用单处理器数据,来避免加锁,提高性能。
- 允许中断,但是自己不能休眠。
- 一个软中断不会抢占另外一个软中断。唯一可以抢占软中断的是中断处理程序。其他软中断(甚至同类软中断)可以再其他CPU中同时执行。
- 执行软中断
- 触发软中断:一个注册的软中断必须在被标记后才会执行。(通常在中断处理程序返回前标记,使其在稍后执行)
- 以下情况,待处理的软中断会被检查执行:
- 从一个硬件中断代码处返回时。
- 在ksoftirqd内核线程中。
- 在那些显示检查和执行待处理的软中断的代码中;如:网络子系统 。
- do_softirq()循环遍历每个待处理的软中断,调用对应处理程序。
- 内核在执行完中断处理程序后,会马上调用do_softirq();软中断开始执行中断处理程序留给它去完成的剩余任务。
- tasklet
- tasklet是利用软中断实现的一种下半部机制,与进程无关;同一个处理程序的多个实例不能再多个处理器上同时运行。实现下半部的最佳选择。
- tasklet由两种软中断代表:
- HI_SOFTIRQ:高优先级
- TASKLET_SOFTIRQ:正常优先级
- tasklet由tasklet_struct结构表示。每个结构体单独代表一个tasklet。
- status 取值范围:0、TASKLET_STATE_SCHED(已调度,准备投入运行)、TASKLET_STATE_RUN(正在运行)。
- 调度tasklet
- tasklet_vec:高优先级的tasklet的tasklet_struct组成链表,由tasklet_schedule调度
- tasklet_vi_vec:正常优先级的tasklet的tasklet_struct组成链表,由tasklet_hi_schedule调度
- tasklet_schedule执行步骤
- 检查tasklet的状态是否为TASKLET_STATE_SCHED,是则说明tasklet已被调度过,函数立即返回。
- 调用_tasklet_schedule。
- 保存中断状态,然后禁止本地中断。
- 把需要调度的tasklet按优先级加到每个处理器的tasklet_XXX链表的表头。
- 唤起对应优先级的软中断,这样下次调用do_softirq就会执行该tasklet。
- 恢复中断到原状态并返回。
- tasklet_action()、tasklet_hi_action()是tasklet处理核心
- 禁止中断,并且为当前处理器检索tasklet链表。
- 重复执行下一个tasklet,直至没有剩余的待处理的tasklet。
- tasklet运行完毕,清除tasklet的state域的TASKLET_STATE_RUN状态标志。
- 知道tasklet没有在其他地方执行,且被设置成执行状态,引用计数值为0,现在可以执行tasklet的处理程序了。
- 检查count值是否为0,确保tasklet没有被禁止;如果被禁止,则跳到下一个挂起的tasklet去。
- 如果当前的tasklet没有执行,将其状态设置为TASKLET_STATE_RUN,这样别的处理器就不会再去执行它了。
- 如果是多处理器系统,检查TASKLET_STATE_RUN来判断tasklet是否正在其他处理器上运行。如正在运行,则不执行,跳到下一个待处理的tasklet去。(同一时间,相同类型的tasklet只能有一个执行)
- 循环遍历获得链表上的每个待处理的tasklet。
- 允许响应中断。
- 将当前处理器上的该链表设置为NULL,达到清空的效果。
- tasklet不能睡眠,不能使用信号量或者其他阻塞式的函数。允许响应中断,因此针对共享数据需要加锁保护。
- 每个处理器都有一组辅助处理软中断(和tasklet)的内核线程。
- 当内核出现大量软中断,则内核进程会辅助处理。这些线程设置最低优先级(nice 19),避免它们跟其他重要的任务抢夺资源。也保证了最终的执行。
- 线程名:ksoftirqd/n。一旦初始化,则进入死循环。循环调用:do_softirq。
- 工作队列
- 每个队列都包含一个由等待调用的函数组成的链表(每个处理器每种类型的队列都有一个链表)。
- 工作队列交由内核进程去执行,在进程上下文中执行。允许重新调度甚至睡眠。
- 工作队列子系统是一个用于创建内核线程的接口,通过它创建的进程负责执行由其他部分排到队列里的任务。它创建的这些内核线程称作工作者线程(events/n)。
- 工作者线程需要执行:work_thread()函数,初始化后,执行死循环并开始休眠;当有操作被插入队列里的时候,线程就会被唤醒,以便执行这些操作;没有剩余的操作时,又会继续休眠。
- work_thread()函数的核心流程:
- 线程讲自己设置为休眠状态(state设成TASK_INTERRUPTIBLE),并把自己加入到等待队列中。
- 如果工作链表是空的,线程调用shcedule()函数进入睡眠状态。
- 如果链表中有对象,线程不会睡眠。相反,它将自己设置成TASK_RUNNING,脱离等待队列。
- 如果链表非空,调用run_workqueue()函数执行被推后的工作。
- run_workqueue()函数遍历每个待处理的工作,执行链表每个节点的workqueue_struct中的func成员函数:
- 链表不为空时,选取下一个节点对象。
- 获取需要执行的func函数及其参数data.
- 把节点从链表中移除,讲处理标志位pending清零。
- 调用函数。
- 重复执行。
-
- 软中断
- 下半部机制的选择
-
- 中断处理程序的局限性: