内核的心脏起搏与大脑切换:Linux 中断、时钟与进程调度深度解密

当一个普通用户敲击键盘,或者一个程序在后台默默计算时,Linux 内核在做什么?这不再是苍白的理论,而是几个真正意义上“指挥电脑硬件工作”的环节。

一、硬件到 CPU 的“紧急呼叫”:中断机制(8259A 芯片)

CPU 不能时刻盯着所有的硬件(键盘、鼠标、网卡、硬盘)。为了让它们能主动联系 CPU,硬件设计了**中断(Interrupt)**系统。

1. 谁在负责中转?—— 8259A 可编程中断控制器

在早期的 PC 架构中,有一块专门负责“接电话”的芯片——8259A。它的作用是收集所有设备发来的信号,排队后统一传给 CPU。
在这里插入图片描述

如图 2-5(你上传的第一张图)所示,为了扩展能力,工程师用了两块 8259A 芯片级联

  • 主片(Master): 直接连 CPU,地址在 0x20,管理 IRQ0 ~ IRQ7
  • 从片(Slave): 连在主片的 IRQ2 上,地址在 0xA0,管理 IRQ8 ~ IRQ15

这样,PC 总共可以处理 15 个独立的中断向量号

2. 重要设备的中断分配

书中清晰地列出了每个 IRQ 对应的设备,这几乎是早期 PC 的“标准配置”:

  • IRQ0:时钟(最核心、最准时的那个)
  • IRQ1:键盘(你敲下的每一个键)
  • IRQ3 / 4:串行口(COM1/2)
  • IRQ6:软盘(时代的眼泪)
  • IRQ12:PS2 鼠标
  • IRQ14:硬盘

当这些设备有动作时,8259A 会通过 INTR 引脚向 CPU 发出“紧急呼叫”,CPU 暂停手头工作,转而处理这个中断。

3. 两种中断:硬件与软件

  • 硬件中断(IRQ0~IRQ15): 来自外部设备。
  • 软件中断(int 0x80): 书中特别提到,Linux 用 int 0x80 作为系统调用(System Call)的入口。也就是说,当你的程序想请求内核帮它干活(比如读写文件),它会通过这一行代码主动“违规闯入”内核,申请服务。

二、系统的“心脏起搏器”:定时机制(8253 芯片)

有了中断,系统还得有一个基准时间,用来控制多任务的公平运行。这就引出了 8253 可编程定时器。

1. 100 次/秒的“滴答”声

在 Linux 0.11 中,8253 被设置成每 10 毫秒(10ms) 触发一次时钟中断(IRQ0)。这看起来很短,但意味着 1 秒钟之内,系统会收到 100 次 脉冲。

每一次脉冲,都会让全局计数器 jiffies(系统滴答数)增加 1。这个 jiffies 就像是系统的生命计时器

2. 时钟中断的“灵魂”:do_timer()

每次 10ms 的脉冲,都会触发 timer_interrupt 函数。这个函数会立即调用 do_timer()
书中第二张图里写道:“根据特权级对当前进程运行时间作累计。”

  • 用户态(CPL=0): 此时是普通程序在跑,它的时间片会减少。
  • 内核态(CPL=1): 此时是内核在跑,通常不减少时间片(内核不应因时间中断而抢占)。

3. 时间片用完了怎么办?

书中最关键的一句话是:“如果此时进程时间片已经递减为0,表示该进程已经用完本次使用CPU的时间片,于是程序就会根据被中断程序的级别…确定进一步的处理,进而调用 schedule() 切换到其他进程去运行。

看懂了吗?时钟中断不仅是在“计时”,它是进程调度的核心触发器。


三、进程的“身份证”与“换岗”:进程控制(task_struct)

schedule() 决定要换一个进程运行时,光有决定是不够的,它必须知道当前进程的状态,并把新进程“唤醒”。

1. 任务数据结构:task_struct

Linux 为每个进程都发了一张“身份证”——进程控制块(PCB),在代码中被称为 task_struct
这个结构体包含:

  • PID(进程 ID)
  • 状态(运行、等待、暂停等)
  • CPU 寄存器状态(这是所有现场信息)
  • 优先级、信号、时间片剩余量
  • 堆栈指针

2. 进程上下文与切换

书中第三张图解释道:“当内核需要切换(switch)至另一个进程时,它需要保存当前进程的所有状态,即保存当前进程的上下文。”

这就像是运动员交接棒

  1. 保存上下文: 当前进程的 CPU 寄存器(EAX, EBX, ESP, EIP…)全部存入它自己的 task_struct 中。
  2. 恢复上下文: 从新进程的 task_struct 中,把寄存器值重新加载进 CPU。
  3. 继续执行: CPU 从新进程暂停的地方,继续往下执行。

3. 内核栈与用户栈的分离

书中特别提到:进程在内核态(系统调用时)和用户态(普通运行)使用的是不同的堆栈。内核栈专门用于处理系统调用、中断等内核代码,而用户栈存你的局部变量、函数参数。这种隔离设计极大地提高了安全性。


四、全景图:把散落的拼图拼在一起

最后,我们把这三节的内容组合起来,你能清晰地看到 Linux 内核运转的核心闭环

进程控制块 (PCB) 定时器芯片 (8253) CPU (内核态) 中断控制器 (8259A) 硬件 (键盘/硬盘) 进程控制块 (PCB) 定时器芯片 (8253) CPU (内核态) 中断控制器 (8259A) 硬件 (键盘/硬盘) 每10ms触发一次 更新 jiffies 滴答数 递减当前进程时间片 alt [进程时间片用完] [时间片未用完] 发送IRQ0中断请求 通过INTR引脚通知CPU 保存当前进程上下文 (入内核栈) 跳转至 timer_interrupt 处理函数 调用 do_timer() 将当前进程状态设为“就绪/等待” 调用 schedule() 调度程序 查找下一个待运行进程的PCB 恢复新进程的上下文 (从PCB取回寄存器) 恢复当前进程上下文 返回继续执行用户代码

这就是 Linux 0.11 “心跳-中断-调度” 的铁三角。硬件发出声音(中断),时钟负责节拍(定时),而 task_struct 负责保存每个程序演出的剧本(上下文)。明白了这三个环节,你就掌握了操作系统调度的最核心原理。接下来,就可以真正开始一行一行读 sched.c 的代码了!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐