穿插话题-操作系统是怎么运行的
操作系统之所以能让 CPU 高效地同时处理多任务、响应外设请求、管理内存资源,核心依赖于一套完整的中断机制体系。硬件中断是所有中断的基础,而时钟中断、软中断、缺页中断则分别从 “时间调度”“异步处理”“内存管理” 三个维度,支撑起了操作系统的核心功能。下面我们就按从基础到应用的顺序,逐一拆解这些关键机制。
目录
操作系统之所以能让 CPU 高效地同时处理多任务、响应外设请求、管理内存资源,核心依赖于一套完整的中断机制体系。硬件中断是所有中断的基础,而时钟中断、软中断、缺页中断则分别从 “时间调度”“异步处理”“内存管理” 三个维度,支撑起了操作系统的核心功能。下面我们就按从基础到应用的顺序,逐一拆解这些关键机制。
一、硬件中断
外设就绪后,会通过中断控制器向 CPU 发起请求;CPU 在当前指令执行完毕后响应中断,先保存当前的运行现场,再根据中断号在中断向量表中找到对应的中断服务程序并执行,处理完成后恢复现场,继续执行被打断的原任务。这一整套流程,实现了外设的异步通知与 CPU 的高效调度,避免了 CPU 低效的轮询等待。
中断的完整处理,离不开外设、中断控制器与 CPU 的协同工作。但计算机中存在种类繁多、数量庞大的外设,如果每一种外设的处理逻辑都用硬件直接实现,会带来极其复杂的电路设计和高昂的成本,也缺乏灵活性。
因此,操作系统接管了这部分工作,将不同外设的中断处理逻辑交由软件实现。为此,操作系统设计了中断向量表(IDT)这一核心数据结构,它就像一张 “索引表”,为每一种外设的中断号都预存了对应的中断服务程序入口地址。当 CPU 识别出中断号后,就能快速在中断向量表中定位到对应的处理程序,执行相应的硬件动作,完成中断处理。
硬件中断的流程如下图所示:

当理解了硬件中断的过程和机制后,我们会发现,硬件中断和信号的机制十分相似,二者都是基于 “异步打断 - 处理事件 - 恢复执行” 的核心思想,只是分别在软件和硬件层面实现了事件驱动的处理逻辑。
其实,在计算机世界中,是先有硬件中断,由硬件完成处理。后来发现,进程也需要类似的中断机制,所以信号机制也就应运而生。信号机制是用纯软件的方式,模拟中断来完成特定的任务处理的信号中断机制与硬件中断的原理类似,但是两者的本质完全不同!
二、时钟中断
进程可以在操作系统的指挥下,被调度,被执行,那么操作系统自己被谁指挥,被谁推动执行呢?
外部设备可以触发硬件中断,但是这个是需要用户或者设备自己触发,有没有自己可以定期触发的设备?---这个设备就是外部晶振
处理由外部晶振产生的周期性时钟信号所绑定并触发的中断,我们把它称为时钟中断。时钟中断不需要用户或外部设备主动发起,而是以固定频率自动、持续地触发,成为推动操作系统内核运行、实现进程调度、时间管理和系统计时的核心动力。当我们知道了时钟中断的固定频率和外部晶振周期性产生时钟信号的次数,就能算出系统运行的时间、进程执行时长,以及实现精准的时间片调度与计时管理。
在进程 PCB 中定义了一个long counter变量,而这个 counter 就是进程的时间片。在进程内部,这个所谓的时间片本质上就是一个计数器。知道了时钟中断的固定频率和这个 counter 的初始个数,就能算出进程的执行时长。
假设时钟中断的固定频率是 1ms 触发一次,counter 初始设置为 10。进程每运行一个时钟周期,这个计数器 counter 就会减 1(counter--)。操作系统会每隔 1ms 由时钟中断触发检测一次:
当计数器还不为 0 时,继续让当前进程运行,什么都不做;当计数器的值减为 0 时,说明该进程的时间片已用完,操作系统才会暂停当前进程,对进程进行重新调度。

进程的检测,切换,调度的过程,都是在时钟中断的背景下完成的。
在当代cpu中已经集成了晶振设备。CPU 内部负责产生时钟中断的硬件由时钟发生器与时钟计数器两部分构成,时钟发生器会持续输出高频时钟脉冲,但该频率速度过快,无法直接用来触发中断。于是时钟计数器对高频脉冲进行累加计数,当计数达到设定阈值时产生溢出信号,以此周期性触发时钟中断。时钟发生器与时钟计数器共同构成了cpu的时钟源。
所以操作系统是在时钟源的周期性产生信号的推动下以中断的方式进行时钟处理。
当时钟这类硬件中断产生时,CPU 捕获中断信号并暂停当前进程,随后操作系统会根据中断号检索中断描述符表,自动调用内核中预先注册的时钟中断服务函数,完成对应的中断处理工作:

三、死循环
操作系统本质上是一个永不退出的调度死循环,它没有执行完就结束的终点。只要计算机通电、系统未关机,这个循环就会无限持续运行。它的核心使命不是为了完成某一个任务,而是永不停歇地管理硬件、调度程序、响应用户操作,直到主动关机 /或断电强制终止循环。
四、软中断
上面所谈及的中断都属于硬件中断。cpu未来为了进行其他处理,cpu内置了一些特殊的软件指令集,32 位 x86 通过int指令、64 位 x86 通过syscall指令实现触发,二者常绑定固定中断号0x80。一旦操作系统识别到代码中存在这些指令,cpu将会自动触发中断处理流程。而程序员可在代码中加入int、syscall这类指令,C/C++ 编译器也可通过一定方式使用这类指令来触发中断。这类由程序代码主动执行指令所触发的中断,统一称为软中断
那为什么要有软中断呢?
软中断的典型的应用场景--系统调用
在Linux内核中,为系统调用构建了一张系统调用表(sys_call_table[]),所有的系统调用都放在这个系统调用表中。每一个系统调用都有自己对应的系统调用号,这个系统调用号本质上是数组下标。
内核不仅会预先为时钟等各类硬件中断注册对应的中断服务函数,同样也会为软中断(系统调用)提前设置好专门的处理函数与绑定关系:

但是我们会发现,我们在使用系统调用的时候,好像从未使用过int或syscall这样的指令,都是直接调用对应的函数。原因是Linux的c标准库帮我们把几乎所有的系统调用都封装了!
例如:#define SYS_ify(syscall_name) __NR_##syscall_name:是一个宏定义,用于将系统调用的名称转换为对应的系统调用号。比如:SYS_ify(open) 会被展开为 __NR_open。
系统调用号并非由 glibc 提供,而是由内核定义,内核同时提供了系统调用入口函数、int 或 syscall 这类汇编级软中断指令,以及配套的头文件与开发入口,供上层开发者借助系统调用号完成系统调用;在执行 int 0x80 或 syscall 指令触发软中断前,程序需先将系统调用号存入约定寄存器(x86‑32 使用 eax,x86‑64 使用 rax),再把参数按固定顺序放入指定寄存器(如 x86‑32 使用 ebx、ecx、edx 等),最后执行中断指令进入内核态,而这些底层准备工作已被 glibc 封装为 open()、read() 等标准库函数,用户在用户态直接调用即可自动完成设置并触发软中断。
所以在软中断之前的所有工作主要是由 glibc(C 标准库)来完成,也可以由用户直接手写汇编代码来完成。总的来说,操作系统内核会预先定义好系统调用号,并将软中断机制与这些调用号绑定。用户无需关心底层实现,只需调用 open()、read() 等封装好的接口,底层就会自动触发软中断,让 CPU 切换到内核态执行对应的系统服务。
五、异常中断处理
除了前面讨论的硬件中断与软中断外,CPU 还会响应一类特殊的异常中断。这类中断主要用来处理程序运行过程中出现的错误和特殊异常情况,常见类型包括缺页中断、除零错误、非法指令执行、野指针访问以及非法内存访问等。
异常中断由程序自身错误行为触发,属于 CPU 自动检测、主动响应的特殊中断类型,拥有独立的中断响应与处理流程,整体统称为异常中断处理。
操作系统本身不会独立主动循环执行,本质上是一整套基于中断机制运行的功能软件集合。系统平时处于等待状态,依靠 CPU 响应各类中断与异常时被动切入内核代码,借此完成进程调度、内存管理、设备管理、文件管理等全部核心系统功能。无论是外设 IO 请求、时间片轮换,还是虚拟内存缺页、程序运行报错、用户态系统调用,最终都需要通过中断或异常陷入内核,由操作系统进行统一处理与调度。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)