Linux驱动器中断服务程序技巧softirq/tasklet/workq
什么是上下文切换
上下文切换,有时也称为进程切换或任务切换,是CPU中央处理单元)从一个进程,或线程切换到另一个进程或线程。
进程,有时也称为任务,是程序的执行实例。在 Linux 中,线程是轻量级进程,可以并行运行,并与其父进程(即创建它们的进程)共享地址空间和其他资源。
上下文是指CPU寄存器和程序计数器在任何时间点的内容。寄存器是 CPU 内部的少量,非常快的内存,用于通过提供对常用值的快速访问,来加速计算机程序的执行,通常是计算的中间值。程序计数器是一种专用寄存器,用于指示 CPU 在其指令序列中的位置,并根据特定系统保存正在执行的指令的地址,或要执行的下一条指令的地址。
上下文切换可以更详细地描述为操作系统的核心对CPU上的进程(包括线程)执行以下活动:
- 暂停一个进程的进程,并存储内存中某个进程的CPU状态(即上下文)
- 从内存中检索下一个进程的上下文,并将其恢复到CPU的寄存器中
- 返回到程序计数器指示的位置(即,返回到进程被中断的代码行)以恢复进程。
上下文切换有时被描述为内核暂停 CPU 上一个进程的执行,并恢复先前暂停的某个其他进程的执行。
上下文切换和模式切换
上下文切换只能在内核模式下发生。内核模式是CPU的一种特权模式,其中只有内核运行,并提供对所有内存位置和所有其他系统资源的访问。其他程序,包括应用程序,最初在用户模式下运行,但它们可以通过系统调用,运行部分内核代码。系统调用是类 Unix 操作系统中活动进程(即当前在 CPU 中运行的进程)对内核执行的服务(例如输入/输出 (I/O) 或进程创建)的请求(即,创建一个新进程)。 I/O 可以定义为进出 CPU 和主存储器(即 RAM)组合的任何信息移动,即该组合与计算机用户之间的通信(例如,通过键盘或鼠标),其存储设备(例如,磁盘或磁带驱动器)或其他计算机。
这两种模式在类 Unix 操作系统中的存在,意味着当系统调用导致 CPU 切换到内核模式时,类似但更简单的操作是必要的。这被称为模式切换,而不是上下文切换,因为它不会更改当前进程。
上下文切换是多任务操作系统的基本特征。多任务操作系统是多个进程在单个 CPU 上看似同时执行,且互不干扰的操作系统。这种并发错觉是通过快速连续(每秒数十或数百次)发生的上下文切换来实现的。这些上下文切换的发生是由于进程自愿放弃其在 CPU 中的时间,或调度程序在进程用完其CPU时间片时进行切换。
上下文切换也可以作为硬件中断的结果发生,硬件中断是从硬件设备(例如键盘、鼠标、调制解调器或系统时钟)到内核的信号,表明事件(例如,按键、鼠标移动)或来自网络连接的数据到达)发生。
Intel 80386 和更高版本的 CPU 包含对上下文切换的硬件支持。然而,大多数现代操作系统执行软件上下文切换,它可以在任何 CPU 上使用,而不是硬件上下文切换,以试图获得改进的性能。软件上下文切换首先在 Linux 中,为具有 2.4 内核的 Intel 兼容处理器实现。
软件上下文切换的一个主要优点是,虽然硬件机制保存了几乎所有的 CPU 状态,但软件可以更有选择性,只保存实际需要保存和重新加载的部分。然而,对于这在提高上下文切换效率方面究竟有多重要,存在一些问题。它的拥护者还声称,软件上下文切换允许改进切换代码的可能性,从而进一步提高效率,并允许更好地控制正在加载的数据的有效性。
运行上下文
在任何时候,系统中的每个 CPU 都可以:
- 与任何进程无关,服务于硬件中断
- 与任何进程都不相关联,服务于软中断或小任务
- 与进程(用户上下文)相关联,在内核空间中运行
在用户空间运行进程
这之间有一个排序。最底层的两个可以互相抢占,但在其之上是一个严格的等级制度:每个只能被它上面的人抢占。
当一个软中断在 CPU 上运行时,没有其他软中断会抢占它,但硬件中断可以。但是,系统中的任何其他 CPU 都是独立执行的。
我们将看到用户上下文可以通过多种方式阻止中断,从而成为真正不可抢占的。
硬件中断(硬 IRQ)
计时器滴答声、网卡和键盘是可随时产生中断的真实硬件示例。内核运行中断处理程序,为硬件提供服务。内核保证此处理程序永远不会重新进入:如果相同的中断到达,则将其排队(或丢弃)。因为它禁用中断,所以这个处理程序必须很快:它经常简单地确认中断,标记一个“软件中断”以供执行并退出。
您可以判断您处于硬件中断状态,因为 in_irq() 返回 true。
软件中断/Tasklet上下文
- 系统调用即将返回用户空间。
- 硬件中断处理程序退出时,
任何标记为挂起(通常由硬件中断)的“软件中断”都会运行(kernel/softirq.c)。
许多真正的中断处理工作都在这里完成。在过渡到 SMP 的早期,只有“下半部分”(BH),它们没有利用多个CPU。
include/linux/interrupt.h 列出了不同的软中断。一个非常重要的软中断是,计时器软中断(包括/linux/timer.h)。
软中断通常很难处理,因为同一个软中断会在多个CPU上同时运行。出于这个原因,tasklet (include/linux/interrupt.h) 更常用:它们是可动态注册的(意味着您可以拥有任意数量的),并且它们还保证任何 tasklet 只能在一个 CPU 上运行任何时候,尽管不同的小任务可以同时运行。
Softirqs
Softirq | Index (priority) | Description |
---|---|---|
HI_SOFTIRQ | 0 | 处理高优先级的tasklet |
TIMER_SOFTIRQ | 1 | 与定时器中断相关的tasklet |
NET_TX_SOFTIRQ | 2 | 将数据包发送到网卡 |
NET_RX_SOFTIRQ | 3 | 从网卡接收数据包 |
SCSI_SOFTIRQ | 4 | SCSI 命令的中断后处理 |
TASKLET_SOFTIRQ | 5 | 处理常规tasklet |
HI_SOFTIRQ/TASKLET_SOFTIRQ
void __init softirq_init(void)
{
int cpu;
for_each_possible_cpu(cpu) {
per_cpu(tasklet_vec, cpu).tail =
&per_cpu(tasklet_vec, cpu).head;
per_cpu(tasklet_hi_vec, cpu).tail =
&per_cpu(tasklet_hi_vec, cpu).head;
}
open_softirq(TASKLET_SOFTIRQ, tasklet_action);
open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}
TIMER_SOFTIRQ
计时器APIs
void add_timer(struct timer_list *timer)
void add_timer_on(struct timer_list *timer, int cpu)
int mod_timer(struct timer_list *timer, unsigned long expires)
int timer_reduce(struct timer_list *timer, unsigned long expires)
int del_timer_sync(struct timer_list *timer)
int try_to_del_timer_sync(struct timer_list *timer)
int del_timer(struct timer_list *timer)
在I2C驱动器中的应用
这是PNX010x/PNX4008板上的I2C驱动器的计时器应用。