【Linux】Linux 开发必备:信号处理时机 + 中断向量表 + 系统调用表,内核态切换核心知识点
本文深入解析Linux信号处理机制与内核态/用户态的交互原理。信号处理的"合适时机"是进程从内核态返回用户态的瞬间,此时OS会检查未决信号并执行处理。详细剖析了四种信号处理动作的差异,重点说明自定义信号处理函数涉及的用户态/内核态多次切换流程。同时,从硬件视角揭示中断机制与信号设计的相似性,包括中断控制器工作原理和中断向量表的映射逻辑,并强调时钟中断作为OS调度核心驱动力对进程
前言:欢迎各位光临本博客,这里小编带你直接手撕**,文章并不复杂,愿诸君**耐其心性,忘却杂尘,道有所长!!!!

《C语言》
《C++深度学习》
《Linux》
《数据结构》
《数学建模》
文章目录
Linux信号处理时机与内核态/用户态深度解析
一、信号处理的“合适时机”是什么?
进程收到信号后不会立即处理,而是等待“合适的时机”——这个时机的核心是进程从内核态返回到用户态的瞬间。此时OS会触发信号检查,决定是否处理未决信号。
合适的实际?
当进程处理信号的时候会选择在合适的时候处理,但什么是合适的时机?
结合课件

1.1 信号处理的触发时机
- 核心规则:只有当进程从内核态切换回用户态时,OS才会检查并处理信号。
- 内核态:进程执行OS代码(如系统调用、中断处理)时的状态,拥有最高权限。
- 用户态:进程执行自己代码时的状态,权限受限。
- 常见场景:进程执行
read、write等系统调用后,或被中断处理完成后,都会从内核态返回用户态,此时就是信号处理的“合适时机”。
1.2 信号检查与处理流程
当进程从内核态返回用户态时,OS会通过do_signal函数完成信号检查,流程如下:
- 检查未决与阻塞信号:
do_signal会对比pending(未决信号集)和block(阻塞信号集),筛选出“已产生且未阻塞”的信号。 - 执行处理动作:根据
handler表(信号处理函数表)中该信号的处理方式,执行对应逻辑。- 若为自定义处理函数:OS会切换到用户态执行该函数,执行完成后通过
sigreturn系统调用再次回到内核态,继续后续流程。
- 若为自定义处理函数:OS会切换到用户态执行该函数,执行完成后通过
1.3 不同处理动作的差异
信号的处理动作分为默认、忽略、暂停、自定义捕捉四种,除自定义捕捉外,其他动作均在内核态完成,流程更简单:
- 忽略(SIG_IGN):直接将
pending表中对应信号的比特位从1改为0,不做其他操作。 - 默认(SIG_DFL):若默认动作为“终止”,OS会直接回收进程资源;若为“Core Dump”,则生成核心转储文件后终止。
- 暂停(如SIGSTOP):找到进程的PCB(
task_struct),将进程状态从“运行态(R)”改为“暂停态(S)”,暂停调度。
1.4 自定义捕捉的身份切换细节
自定义信号处理函数由用户编写,OS必须以“用户态身份”执行,避免用户代码非法操作内核资源,整个过程涉及多次身份切换:
- 核心疑问:进程执行自己的代码时为何会进入内核态?即使是死循环,也会因CPU调度(如时间片耗尽)被中断,从而进入内核态。
- 栈帧与返回逻辑:自定义处理函数执行时,OS会在用户栈中构造特殊栈帧,将
sigreturn系统调用的地址写入栈中。当处理函数返回时,会自动触发sigreturn,从用户态再次切换回内核态,完成后续清理(如清零pending表对应位)。 - 身份切换流程:用户态执行代码 → 触发系统调用/中断进入内核态 → 内核态检查信号 → 切换回用户态执行自定义处理函数 → 处理完成后通过
sigreturn回到内核态 → 最终返回用户态继续执行原代码。
二、硬件中断:OS运行的“驱动力”
操作系统的核心是“响应事件”,而硬件中断是OS感知外部设备(如键盘、磁盘)状态的关键机制,相当于外部设备向OS“打招呼”的方式。
2.1 硬件中断的基本原理
- OS如何感知外部设备?:外部设备(如键盘)与CPU通过“中断控制器”(如8259芯片)间接连接。当设备就绪(如键盘按下)时,会向中断控制器发送高电平信号,中断控制器再向CPU的特定针脚发送中断请求。
- 操作系统是怎么运行的
- 结构图:
- 当我们按下电源键的时候,操作系统要加载到内存,而操作系统要不断响应外部事件

- 当我们按下电源键的时候,操作系统要加载到内存,而操作系统要不断响应外部事件
- 结构图:
- OS怎么知道键盘上有数据了
- 外部设备和CPU会建立某种间接连接
- 外部设备只要输入就会向帧脚发起中断(相当于黑夜中用手电筒交流)
- 这个就是阵脚

- 中断控制器:板子8259专门用来做中断控制器,一旦接收到中断信号,就会给cpu的针脚发信息。
- 在控制器内接收到中断,就会在内部生成一个中断号
- 磁盘的例子:
- 寄存器不只在CPU内有,在设备里也有。

- 当外设就绪,向中断控制器发送高电平,然后中断控制器会通知cpu,cpu会得知中断,然后再去访问中断控制器,得到中断号。中断号就相当于CPU的“设备编号”,告诉CPU是哪个设备就绪了。
- 在控制器内接收到中断,就会在内部生成一个中断号
- 操作系统是怎么运行的
2.2 中断向量表:硬件中断的“处理手册”
CPU收到中断请求后,需要知道如何处理——这就依赖中断向量表(IDT),它本质是一个“函数指针数组”,下标为中断号,元素为该中断对应的处理函数地址。
- 处理流程:CPU拿到中断号 → 查找中断向量表 → 执行对应的中断处理函数(由OS编写)。
- 软件部分
- 这时,CPU只知道设备就绪了,但是不知道如何处理这个东西,而软件部分就是处理这个的
- 操作系统提供了一个中断向量表的东西
- 中断向量表大概的意思就是一个函数指针数组
- 这个数组的下标就相当于提取出的中断号
- CPU会拿着中断号查找中断向量表,找到了就会执行对应的代码

- IDT(中断向量表)就是操作系统的一部分,不需要分辨谁写的
- 软件部分
2.3 信号与硬件中断的相似性
信号的设计借鉴了硬件中断的“异步通知”思想:硬件中断是外部设备向OS发通知,信号是OS向进程发通知;两者都是“异步触发”,且都有对应的“处理函数”(中断向量表 vs 信号handler表)。
- 信号和中断的相似

- 这时候,OS再也不用关注外部设备是否准备好,而是它准备好就会“叫”OS处理。
- 其实在冯诺依曼体系中,输入设备和处理器之间连着发送信号的线
三、时钟中断:OS调度的“节拍器”
如果没有外部设备中断,OS会处于“暂停”状态吗?不会——因为有时钟中断,它以固定频率触发,是OS进程调度、时间管理的核心驱动力。
3.1 时钟中断的作用
- 时钟源:主板上的时钟芯片以固定频率(如100Hz,即每秒触发100次)向CPU发送中断,这个频率就是CPU的“主频”基础。
- OS的调度依赖:每次时钟中断触发时,OS会执行中断处理函数,检查进程时间片是否耗尽、更新系统时间戳等,从而实现进程的切换与调度。
- 没有外部中断
- 如果没有操作系统,外部中断, os在做什么
- OS在不断暂停
- main

- 在main函数内部我们发现
- 所以操作系统一直在暂停
- 如果没有操作系统,外部中断, os在做什么
- 在中断向量表中有个方法:进程调度
- 时钟源:以固定的,特定的频率,向CPU发送特定的中断
- 以固定的,特定的频率,向CPU发送特定的中断。
- 操作系统,就在硬件时钟中断的驱动下,进行调度了

- 操作系统是什么
- 是基于中断进行工作的软件
- 主频:
- 指得就是时钟源的频率

- 指得就是时钟源的频率
- 没有外部中断
3.2 时间片耗尽的调度流程
当进程的时间片(如10ms)耗尽时,时钟中断触发的处理流程如下:
- 时钟中断触发,CPU进入内核态,执行时钟中断处理函数。
- 处理函数更新当前进程的运行时间,判断是否耗尽时间片。
- 若耗尽,OS调用调度算法(如CFS),选择下一个要运行的进程。
- 切换进程上下文(PCB、寄存器等),将CPU使用权交给新进程。
- 调度本身就是基于中断来进行的
- 时间片耗尽
- 流程

- 流程
3.3 时间戳与离线计时
- 系统时间戳:时钟中断每次触发时,OS会更新一个全局时间戳(记录从开机到现在的总滴答数),通过这个时间戳可计算出当前时间(如秒、分钟)。
- 离线计时:即使计算机离线,时间戳仍会通过时钟芯片持续更新,确保重启后能恢复正确时间。
- 时间->时间戳->历史总频率
- total:让我的计算机,离线的情况,也就知道几点了
- 时间在离线下的计时

- 时间->时间戳->历史总频率
3.4 时钟中断处理的底层实现
时钟中断的处理函数(如timer_interrupt)通常用汇编写,因为需要快速操作寄存器、切换上下文,同时也支持与C语言混编(C语言处理复杂逻辑)。
- timer_terrupt是用汇编写的
- C语言和汇编是能混编的
- 时钟中断处理
- 细节:

- 中断向量表

3.5 OS的本质:中断驱动的死循环
操作系统的核心逻辑是一个“死循环”(如while(1)),但这个循环并非空转——它依赖中断触发“唤醒”,执行具体任务(如调度、处理设备请求)。
- 类比军训:OS像军训中的教官,进程像学生;时钟中断像“哨声”,每次哨声响起(中断触发),教官就会检查学生状态(进程时间片),安排下一个动作(调度)。
- 操作系统的本质就是一个死循环。
- 举个例子:军训
- 操作系统在硬件的推动下,自动调度
- 操作系统的本质就是一个死循环。
四、从硬件异常到软中断:中断的扩展类型
除了硬件中断,CPU还会处理“内部异常”(如除零、野指针)和“软中断”(如系统调用),这些都被统一归为“中断”范畴,由中断向量表管理。
4.1 硬件异常:CPU内部的“错误通知”
当进程执行错误操作(如除零、访问非法内存)时,CPU会触发“内部异常”,并将其转化为特定中断号,通过中断向量表执行OS的异常处理函数(如发送SIGFPE、SIGSEGV信号)。
- 当进程在执行自己代码遇到错误时,规定成为一种CPU内部触发的中断

- 所有的硬件异常都会被转化成中断
- 系统会自动的注册中断处理方法
4.2 缺页中断:内存管理的“关键中断”
当进程访问的虚拟地址未映射到物理内存时,会触发“缺页中断”,OS会执行中断处理函数,将对应的内存页从磁盘加载到物理内存,再更新页表,让进程继续执行——这是虚拟内存机制的核心。
- 缺页中断?
4.3 软中断:用户主动触发的“中断”
用户进程可以通过CPU指令主动触发中断(软中断),最典型的场景是系统调用。不同架构的CPU有不同的软中断指令:
- x86架构:
int 0x80(早期)。 - x86_64架构:
syscall(现代)。- cpu内部,自己可以让软件触发中断吗?
让CPU通过CPU主动触发中断?- 在cpu内的指令集中
- x86:int
- x86_64:sycall

- 主动中断

- 软中断
- 在cpu内的指令集中
- cpu内部,自己可以让软件触发中断吗?
五、系统调用:用户态进入内核态的“桥梁”
系统调用是用户进程请求OS服务的唯一方式,本质是通过“软中断”(如syscall)从用户态切换到内核态,执行OS提供的核心功能(如fork、open)。
5.1 系统调用的底层逻辑
- 系统调用表:OS将所有系统调用的处理函数(如
sys_fork、sys_open)组织成一个数组(系统调用表),每个系统调用对应唯一的“系统调用号”(数组下标)。- 当我们系统调用的时候,具体是怎么进入操作系统,完成调用过程的,毕竟CPU只有一个
- 所有的系统调用都被写在的系统调用表里面

- 从此往后,每一个系统调用,都有唯一的下标
- 这个下标,我们叫做,系统调用号

- 当我们系统调用的时候,具体是怎么进入操作系统,完成调用过程的,毕竟CPU只有一个
5.2 用户态如何触发系统调用?
用户进程无法直接调用OS的函数,需通过库函数封装(如glibc库)间接触发:
- 用户调用库函数(如
fork()),库函数会将对应的“系统调用号”存入指定寄存器(如x86_64的rax)。 - 执行软中断指令(如
syscall),CPU从用户态切换到内核态。 - 内核根据寄存器中的系统调用号,查找系统调用表,执行对应的内核函数(如
sys_fork)。 - 执行完成后,通过
sysret指令返回用户态,将结果返回给库函数,最终传递给用户进程。
- 用户层面,怎么调用系统调用,
- 常用的系统调用转汇编
- move拿到系统调用号
- open就是OS提供的吗?
- OS不提供任何系统掉用,OS只提供系统调用号?
- 而 open fork->本质是glibc封装的
- system _ call也是用c封装的
- _ NR是系统调用号

-
5.3 系统调用的语言层级
所有高级语言(如C++、Python)的I/O、进程管理等功能,最终都依赖C语言的库函数(如fopen),而库函数又通过系统调用与OS交互——这也是“所有语言都依赖C语言”的底层原因。
- 追准fopen
- 所有的语言都跟c语言语言
- 因为要使用系统调用

六、中断的分类总结
根据触发源和场景,中断可分为三类,对应不同的处理逻辑:
- 硬件中断:由外部设备触发(如键盘、磁盘),对应“中断”(Interrupt)。
- 软中断/陷阱:由用户主动触发(如系统调用),对应“陷阱”(Trap),如
int 0x80、syscall。 - 异常:由CPU内部错误触发(如除零、野指针、缺页),对应“异常”(Exception)。
- 缺页中断和一些错误都会转换为中断
- 操作系统就是躺在中断处理历程的代码块
- CPU内部的软中断,int 0x80 或者syscall,叫做陷阱
- 而除零和野指针,叫做异常

七、用户态与内核态:权限的“边界”
用户态和内核态是CPU的两种运行状态,核心区别是权限不同——内核态拥有访问所有硬件资源的权限,用户态仅能访问自己的地址空间,确保系统安全。
7.1 状态切换的触发条件
- 用户态→内核态:只有通过“中断/异常/系统调用”才能切换,这是唯一途径。
- 内核态→用户态:当中断/异常/系统调用处理完成后,通过
iret(中断返回)或sysret(系统调用返回)指令切换回用户态。
7.2 系统调用的地址空间视角
系统调用的执行过程仍在当前进程的地址空间中进行——进程地址空间的“内核区”(高地址部分)映射了OS的代码和数据,当切换到内核态时,CPU就可以访问这部分区域,执行系统调用函数(如sys_fork)。
- 内核图:
-有一个sys_fork()的系统调用
- 未来在代码区

- 系统调用的过程,也是在进程地址空间上进行的
- 所有的函数调用,都是地址之间的跳转
更多推荐






















所有评论(0)