任务抢占机制(Preempting Tasks)
在操作系统调度的宏大叙事中,线程间的切换不仅是调度器有序奏响的乐章,更是系统响应性的核心命脉。此前我们主要探讨的是 线程之间基于自愿(voluntary)发生的上下文切换 —— 即当前线程主动交出 CPU 使用权,甘为整体系统调度的节奏所用。然而,现代操作系统的高实时性要求远不止于“谦让”,于是我们走入了 抢占式调度(preemptive scheduling) 的深水区。
非抢占式内核(Non-preemptive Kernel)
在这种模式下,内核的调度逻辑在每一个时钟周期(tick)发生中断时,会审视当前进程的运行时长是否已经耗尽。如果是,则在中断处理函数中设置一个标志位。值得注意的是,这个设置动作是发生在 中断上下文(interrupt context) 中,也就是说,它并不打断当前的执行流程,而只是为即将到来的调度做下铺垫。
随后,在内核准备返回用户空间前,会检查这个标志,并在必要时调用调度器 schedule()
来挑选新的任务。
这一模型的关键特征是:只要当前进程仍然运行在内核态(比如正在执行系统调用),它就不会被抢占。这避免了并发操作中的数据竞争问题,使得某些临界操作无需显式加锁即可完成。
🔍 概念拓展:非抢占内核的优势与局限
- 优势:减少加锁复杂度,提高关键区的执行效率;
- 局限:系统响应性差,若某一进程在内核态执行时间过长,可能造成其它任务长时间无法获得 CPU。
抢占式内核(Preemptive Kernel)
抢占式内核则是在效率与响应性之间取得更动态的平衡。在这种机制中,即便当前任务正在执行系统调用,只要系统判定其时间片已经用尽,也可能被强行打断,从而切换至优先级更高的任务。这种能力是现代多任务操作系统“灵活调度”的基石。
然而,随之而来的代价是同步与保护的复杂性提升。为此,Linux 提供了 抢占控制原语:
preempt_disable()
:暂时关闭抢占;preempt_enable()
:重新启用抢占。
此外,在多核处理器架构(SMP)下,由于多个核心可能并发执行临界资源访问,Linux 自动在使用 自旋锁(spinlock) 时禁用抢占,以避免内核态任务在未完成锁定操作时被打断,导致状态不一致。
当一个任务的时间片用尽,系统同样设置标志位,但此时的触发点是在执行 spin_unlock()
或退出临界区时。此刻会检查该标志,若发现当前任务应被抢占,便调用调度器进行任务切换。
🔍 概念拓展:为什么抢占式调度更现代?
- 它能在毫秒级别内响应系统事件;
- 适用于高并发服务器和实时系统;
- 要求对内核临界区有精确的保护机制。
进程上下文(Process Context)
在 Linux 的内核世界中,“上下文”是一种运行的视角。当内核因某一系统调用而代表用户进程运行时,我们称其为处于“进程上下文”。
- 在此上下文中,系统可以安全地访问当前进程的资源(通过
current
指针),并可以阻塞自身(比如等待某个事件)。 - 同时,也能够访问用户空间的数据——这意味着内核具备将用户态请求与内核态操作衔接的能力。
- 注意:如果是内核线程上下文(如系统自启动的守护线程),则不会涉及用户空间访问。
🔍 概念拓展:进程上下文与中断上下文的根本区别
- 进程上下文可以睡眠(sleep),适合做阻塞式等待;
- 中断上下文则禁止睡眠,要求快速完成,不允许调度切换。
内核线程(Kernel Threads)
内核线程是操作系统内部的一种“影子使者” —— 它们不归属于任何用户空间任务,却在幕后承担着繁重的使命。
- 内核线程具备完整的进程上下文,但不拥有用户空间资源,例如地址空间或文件描述符;
- 它们常用于驱动程序、后台守护任务,或者调度管理工作;
- 它们的存在,是内核体系结构灵活性与模块化的体现。
🧠 举例:如
ksoftirqd
、kworker
、kthreadd
等,都是在不同内核子系统中运行的内核线程,它们为系统提供基础服务,默默无闻,却至关重要。
什么是中断(Interrupt)?
中断是一种 打破程序正常执行流程 的事件,它可以由外部硬件设备触发,也可以由 CPU 自身生成。当中断发生时,当前程序的执行会被暂停,CPU 转而执行一段称为“中断处理程序(Interrupt Handler)”的特定代码。中断处理完成后,系统会恢复被打断的程序流程,继续执行原本的任务。
⏰ 中断
✅ 按触发源:
同步中断(Synchronous):
- 在指令执行过程中由 CPU 检测异常而触发,例如除以零、页错误或系统调用。
- 在 Linux 中通常称为 异常(Exception)。
异步中断(Asynchronous):
- 来自外部设备,例如网卡接收到数据包时会触发中断。
- 这类中断是真正意义上的“中断”,常用于外设事件通知。
✅ 按可屏蔽性:
可屏蔽中断(Maskable):
- 可通过设置中断屏蔽位暂时禁用,常见于大部分外设中断。
- 在内核关键区可以“延迟”处理,防止竞态条件。
不可屏蔽中断(Non-Maskable Interrupt, NMI):
- 不能被屏蔽,主要用于处理关键事件(如硬件故障检测)。
- 通过 CPU 的 NMI 引脚触发,优先级极高,必须立即响应。
🔍 解释:为什么分同步/异步、可屏蔽/不可屏蔽?
这四类交叉组合让中断机制具备高度的灵活性与层级控制。例如,页错误是同步的但不可延迟;网卡中断是异步的但可屏蔽;而硬件故障则属于异步且不可屏蔽。
⚠️ 异常(Exceptions)
异常是中断的一个子类,通常是指在 CPU 执行指令时,内部检测到某种异常状态而触发 的事件。异常有两个来源:
1. 由处理器自动检测产生(Processor-detected)
Fault(故障):
- 在异常发生的指令执行之前被报告,例如缺页异常(Page Fault)。
- EIP 保存的是出错指令的地址,修复后可以重新执行。
Trap(陷阱):
- 在指令执行完成后报告,通常用于调试。
- EIP 保存的是下一条指令的地址,代表已执行完触发指令。
Abort(终止):
- 严重错误,无法恢复,例如硬件级别一致性错误。
- 通常导致程序或内核崩溃。
2. 由程序主动触发(Programmed)
- 使用
int n
指令,如int 0x80
用于触发系统调用。
🔍 概念延伸:EIP 是什么?
EIP(Extended Instruction Pointer)是指令指针寄存器,保存当前或将要执行的指令地址。在处理异常时,EIP 的保存位置决定了异常处理返回后的执行位置,是实现“可恢复异常处理”机制的关键。
🌐 异步中断的典型应用 —— 外设通知
当 I/O 设备完成某项任务,例如接收到网络数据包、磁盘读写完成,它们会通过 外部中断机制 通知 CPU。CPU 在接收到中断信号后,立即跳转至相应的中断处理程序处理该事件。
这种方式极大提升了系统效率,因为 CPU 不需要轮询设备状态,而是以事件驱动方式响应硬件请求。
Watchdog | Interrupt, Maskable / Nonmaskable |
Demand paging | Exception, Fault |
Division by zero | Exception, Fault |
Timer | Interrupt, Maskable |
System call | Exception, Trap |
Breakpoint | Exception, Trap |
Exception | Trap, Fault, Abort |
Interrupt | Maskable, Nonmaskable |
Maskable | Interrupt |
Nonmaskable | Interrupt |
Trap | Exception |
Fault | Exception |
📌 Watchdog(看门狗)
- 定义:用于检测系统是否卡死或未响应,若长时间未复位看门狗,系统将被强制复位。
- 类型:通常通过外设触发 中断(Interrupt),根据系统关键性可能为 Maskable 或 Nonmaskable。
- 例子:嵌入式系统中如果主循环未在设定时间内喂狗,硬件触发复位中断。
📌 Demand Paging(请求分页)
- 定义:虚拟内存技术,程序访问尚未加载入内存的页时触发缺页中断。
- 类型:由 CPU 检测出页不存在,触发 Exception,属于 Fault。
- 例子:访问一个尚未映射到物理内存的地址,触发页错误(Page Fault)。
📌 Division by Zero(除以零)
- 定义:CPU 执行除法指令时,除数为 0。
- 类型:由 CPU 检测,属于 Fault 类型的 Exception。
- 例子:执行
int x = a / b;
且 b 为 0 时,程序会因非法操作被中断。
📌 Timer(定时器)
- 定义:周期性触发的硬件时钟,用于时间片轮转等任务调度。
- 类型:典型 Maskable Interrupt,可通过禁用中断暂时忽略。
- 例子:Linux 的时钟中断,用于触发
schedule()
进行任务调度。
📌 System Call(系统调用)
- 定义:用户程序主动请求内核服务的一种机制。
- 类型:通过指令
int 0x80
或syscall
触发,属于 Exception → Trap。 - 例子:调用
write()
向文件写入内容,会陷入内核处理写操作。
📌 Breakpoint(断点)
- 定义:用于调试的中断点,由调试器设置。
- 类型:执行完指令后触发,属于 Trap Exception。
- 例子:在 GDB 中设置断点,程序运行到此处会暂停。
📌 Exception(异常)
- 定义:CPU 在执行指令过程中自行检测出的错误或事件。
- 类型:统称,包含 Fault(如页错误)、Trap(如调试陷阱)、Abort(不可恢复错误)。
- 例子:访问非法地址 → 页错误(Fault);执行调试断点 → Trap。
📌 Interrupt(中断)
- 定义:外部事件触发 CPU 响应并中断当前程序的机制。
- 类型:
- Maskable:可通过 CLI/STI 禁用/启用;
- Nonmaskable:高优先级中断,永远不能屏蔽。
- 例子:
- 网卡收到数据包触发中断(Maskable);
- 电源异常触发 NMI 中断(Nonmaskable)。
📌 Maskable(可屏蔽中断)
- 定义:可以被程序主动屏蔽(如 CLI 指令)的一类中断。
- 例子:USB 插拔事件、定时器中断等,关键代码段可临时关闭中断防止竞态。
📌 Nonmaskable(不可屏蔽中断)
- 定义:无法被屏蔽的高优先级中断。
- 例子:硬件错误检测,如内存校验错误、看门狗超时。
📌 Trap(陷阱)
- 定义:在指令执行完毕后报告的异常,通常用于系统调用或调试。
- 例子:
int 0x80
、断点调试。
📌 Fault(故障)
- 定义:在指令执行前检测到的异常,可以在修复后重试。
- 例子:页错误(Page Fault)、除零错误。