Linux Kernel 7

发布于:2025-04-16 ⋅ 阅读:(31) ⋅ 点赞:(0)

任务抢占机制(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)

内核线程是操作系统内部的一种“影子使者” —— 它们不归属于任何用户空间任务,却在幕后承担着繁重的使命。

  • 内核线程具备完整的进程上下文,但不拥有用户空间资源,例如地址空间或文件描述符;
  • 它们常用于驱动程序、后台守护任务,或者调度管理工作;
  • 它们的存在,是内核体系结构灵活性与模块化的体现。

🧠 举例:如 ksoftirqdkworkerkthreadd 等,都是在不同内核子系统中运行的内核线程,它们为系统提供基础服务,默默无闻,却至关重要。


什么是中断(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),根据系统关键性可能为 MaskableNonmaskable
  • 例子:嵌入式系统中如果主循环未在设定时间内喂狗,硬件触发复位中断。

📌 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 0x80syscall 触发,属于 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)、除零错误。