前言:欢迎各位光临本博客,这里小编带你直接手撕**,文章并不复杂,愿诸君**耐其心性,忘却杂尘,道有所长!!!!

在这里插入图片描述


IF’Maxue个人主页

 🔥 个人专栏:
《C语言》
《C++深度学习》
《Linux》
《数据结构》
《数学建模》

⛺️生活是默默的坚持,毅力是永久的享受。不破不立!

Linux信号处理时机与内核态/用户态深度解析

一、信号处理的“合适时机”是什么?

进程收到信号后不会立即处理,而是等待“合适的时机”——这个时机的核心是进程从内核态返回到用户态的瞬间。此时OS会触发信号检查,决定是否处理未决信号。

合适的实际?

当进程处理信号的时候会选择在合适的时候处理,但什么是合适的时机?
image.png

结合课件

image.png

1.1 信号处理的触发时机

  • 核心规则:只有当进程从内核态切换回用户态时,OS才会检查并处理信号。
    • 内核态:进程执行OS代码(如系统调用、中断处理)时的状态,拥有最高权限。
    • 用户态:进程执行自己代码时的状态,权限受限。
  • 常见场景:进程执行readwrite等系统调用后,或被中断处理完成后,都会从内核态返回用户态,此时就是信号处理的“合适时机”。

1.2 信号检查与处理流程

当进程从内核态返回用户态时,OS会通过do_signal函数完成信号检查,流程如下:

  1. 检查未决与阻塞信号do_signal会对比pending(未决信号集)和block(阻塞信号集),筛选出“已产生且未阻塞”的信号。
  2. 执行处理动作:根据handler表(信号处理函数表)中该信号的处理方式,执行对应逻辑。
    • 若为自定义处理函数:OS会切换到用户态执行该函数,执行完成后通过sigreturn系统调用再次回到内核态,继续后续流程。
      • image.png

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表对应位)。
    • image.png
  • 身份切换流程:用户态执行代码 → 触发系统调用/中断进入内核态 → 内核态检查信号 → 切换回用户态执行自定义处理函数 → 处理完成后通过sigreturn回到内核态 → 最终返回用户态继续执行原代码。
    • image.png

二、硬件中断:OS运行的“驱动力”

操作系统的核心是“响应事件”,而硬件中断是OS感知外部设备(如键盘、磁盘)状态的关键机制,相当于外部设备向OS“打招呼”的方式。

2.1 硬件中断的基本原理

  • OS如何感知外部设备?:外部设备(如键盘)与CPU通过“中断控制器”(如8259芯片)间接连接。当设备就绪(如键盘按下)时,会向中断控制器发送高电平信号,中断控制器再向CPU的特定针脚发送中断请求。
    • 操作系统是怎么运行的
      • 结构图:
        • 当我们按下电源键的时候,操作系统要加载到内存,而操作系统要不断响应外部事件image.png
    • OS怎么知道键盘上有数据了
      • 外部设备和CPU会建立某种间接连接
      • 外部设备只要输入就会向帧脚发起中断(相当于黑夜中用手电筒交流)
        • 这个就是阵脚
    • 中断控制器:板子8259专门用来做中断控制器,一旦接收到中断信号,就会给cpu的针脚发信息。
      • 在控制器内接收到中断,就会在内部生成一个中断号
        • 磁盘的例子:
        • 寄存器不只在CPU内有,在设备里也有。image.png
        • 当外设就绪,向中断控制器发送高电平,然后中断控制器会通知cpu,cpu会得知中断,然后再去访问中断控制器,得到中断号。中断号就相当于CPU的“设备编号”,告诉CPU是哪个设备就绪了。
          • image.png

2.2 中断向量表:硬件中断的“处理手册”

CPU收到中断请求后,需要知道如何处理——这就依赖中断向量表(IDT),它本质是一个“函数指针数组”,下标为中断号,元素为该中断对应的处理函数地址。

  • 处理流程:CPU拿到中断号 → 查找中断向量表 → 执行对应的中断处理函数(由OS编写)。
    • 软件部分
      • 这时,CPU只知道设备就绪了,但是不知道如何处理这个东西,而软件部分就是处理这个的
      • 操作系统提供了一个中断向量表的东西
        • 中断向量表大概的意思就是一个函数指针数组
        • 这个数组的下标就相当于提取出的中断号
      • CPU会拿着中断号查找中断向量表,找到了就会执行对应的代码
      • image.png
      • IDT(中断向量表)就是操作系统的一部分,不需要分辨谁写的

2.3 信号与硬件中断的相似性

信号的设计借鉴了硬件中断的“异步通知”思想:硬件中断是外部设备向OS发通知,信号是OS向进程发通知;两者都是“异步触发”,且都有对应的“处理函数”(中断向量表 vs 信号handler表)。

  • 信号和中断的相似image.png
  • 这时候,OS再也不用关注外部设备是否准备好,而是它准备好就会“叫”OS处理。
  • 其实在冯诺依曼体系中,输入设备和处理器之间连着发送信号的线
    • image.png

三、时钟中断:OS调度的“节拍器”

如果没有外部设备中断,OS会处于“暂停”状态吗?不会——因为有时钟中断,它以固定频率触发,是OS进程调度、时间管理的核心驱动力。

3.1 时钟中断的作用

  • 时钟源:主板上的时钟芯片以固定频率(如100Hz,即每秒触发100次)向CPU发送中断,这个频率就是CPU的“主频”基础。
  • OS的调度依赖:每次时钟中断触发时,OS会执行中断处理函数,检查进程时间片是否耗尽、更新系统时间戳等,从而实现进程的切换与调度。
    • 没有外部中断
      • 如果没有操作系统,外部中断, os在做什么
        • OS在不断暂停
        • main
        • image.png
        • 在main函数内部我们发现
          • image.png
        • 所以操作系统一直在暂停
    • 在中断向量表中有个方法:进程调度
    • 时钟源:以固定的,特定的频率,向CPU发送特定的中断
      • 以固定的,特定的频率,向CPU发送特定的中断。
      • 操作系统,就在硬件时钟中断的驱动下,进行调度了
      • image.png
    • 操作系统是什么
      • 是基于中断进行工作的软件
    • 主频:
      • 指得就是时钟源的频率image.png

3.2 时间片耗尽的调度流程

当进程的时间片(如10ms)耗尽时,时钟中断触发的处理流程如下:

  1. 时钟中断触发,CPU进入内核态,执行时钟中断处理函数。
  2. 处理函数更新当前进程的运行时间,判断是否耗尽时间片。
  3. 若耗尽,OS调用调度算法(如CFS),选择下一个要运行的进程。
  4. 切换进程上下文(PCB、寄存器等),将CPU使用权交给新进程。
  • 调度本身就是基于中断来进行的
  • 时间片耗尽
    • 流程image.png

3.3 时间戳与离线计时

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

3.4 时钟中断处理的底层实现

时钟中断的处理函数(如timer_interrupt)通常用汇编写,因为需要快速操作寄存器、切换上下文,同时也支持与C语言混编(C语言处理复杂逻辑)。

  • timer_terrupt是用汇编写的
    • image.png
  • C语言和汇编是能混编的
  • 时钟中断处理
    • image.png
  • 细节:
    image.png
  • 中断向量表
    image.png

3.5 OS的本质:中断驱动的死循环

操作系统的核心逻辑是一个“死循环”(如while(1)),但这个循环并非空转——它依赖中断触发“唤醒”,执行具体任务(如调度、处理设备请求)。

  • 类比军训:OS像军训中的教官,进程像学生;时钟中断像“哨声”,每次哨声响起(中断触发),教官就会检查学生状态(进程时间片),安排下一个动作(调度)。
    • 操作系统的本质就是一个死循环。
      • image.png
    • 举个例子:军训
      image.png
      • image.png
    • 操作系统在硬件的推动下,自动调度
      • image.png

四、从硬件异常到软中断:中断的扩展类型

除了硬件中断,CPU还会处理“内部异常”(如除零、野指针)和“软中断”(如系统调用),这些都被统一归为“中断”范畴,由中断向量表管理。

4.1 硬件异常:CPU内部的“错误通知”

当进程执行错误操作(如除零、访问非法内存)时,CPU会触发“内部异常”,并将其转化为特定中断号,通过中断向量表执行OS的异常处理函数(如发送SIGFPE、SIGSEGV信号)。

  • 当进程在执行自己代码遇到错误时,规定成为一种CPU内部触发的中断
    • image.png
    • 所有的硬件异常都会被转化成中断
    • 系统会自动的注册中断处理方法

4.2 缺页中断:内存管理的“关键中断”

当进程访问的虚拟地址未映射到物理内存时,会触发“缺页中断”,OS会执行中断处理函数,将对应的内存页从磁盘加载到物理内存,再更新页表,让进程继续执行——这是虚拟内存机制的核心。

  • 缺页中断?
    • image.png

4.3 软中断:用户主动触发的“中断”

用户进程可以通过CPU指令主动触发中断(软中断),最典型的场景是系统调用。不同架构的CPU有不同的软中断指令:

  • x86架构:int 0x80(早期)。
  • x86_64架构:syscall(现代)。
    • cpu内部,自己可以让软件触发中断吗?
      让CPU通过CPU主动触发中断?
      • 在cpu内的指令集中
        • x86:int
        • x86_64:sycall
        • image.png
      • 主动中断image.png
      • 软中断

五、系统调用:用户态进入内核态的“桥梁”

系统调用是用户进程请求OS服务的唯一方式,本质是通过“软中断”(如syscall)从用户态切换到内核态,执行OS提供的核心功能(如forkopen)。

5.1 系统调用的底层逻辑

  • 系统调用表:OS将所有系统调用的处理函数(如sys_forksys_open)组织成一个数组(系统调用表),每个系统调用对应唯一的“系统调用号”(数组下标)。
    • 当我们系统调用的时候,具体是怎么进入操作系统,完成调用过程的,毕竟CPU只有一个
      • 所有的系统调用都被写在的系统调用表里面
      • image.png
    • 从此往后,每一个系统调用,都有唯一的下标
    • 这个下标,我们叫做,系统调用号
      image.png

5.2 用户态如何触发系统调用?

用户进程无法直接调用OS的函数,需通过库函数封装(如glibc库)间接触发:

  1. 用户调用库函数(如fork()),库函数会将对应的“系统调用号”存入指定寄存器(如x86_64的rax)。
  2. 执行软中断指令(如syscall),CPU从用户态切换到内核态。
  3. 内核根据寄存器中的系统调用号,查找系统调用表,执行对应的内核函数(如sys_fork)。
  4. 执行完成后,通过sysret指令返回用户态,将结果返回给库函数,最终传递给用户进程。
  • 用户层面,怎么调用系统调用,
  • 常用的系统调用转汇编
    • image.png
  • move拿到系统调用号
    • image.png
  • open就是OS提供的吗?
    • OS不提供任何系统掉用,OS只提供系统调用号?
  • 而 open fork->本质是glibc封装的
    • image.png
    • image.png
  • system _ call也是用c封装的
    • image.png
    • image.png
  • _ NR是系统调用号image.png
    - image.png

5.3 系统调用的语言层级

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

  • 追准fopen
    • 所有的语言都跟c语言语言
    • 因为要使用系统调用
    • image.png

六、中断的分类总结

根据触发源和场景,中断可分为三类,对应不同的处理逻辑:

  • 硬件中断:由外部设备触发(如键盘、磁盘),对应“中断”(Interrupt)。
  • 软中断/陷阱:由用户主动触发(如系统调用),对应“陷阱”(Trap),如int 0x80syscall
  • 异常:由CPU内部错误触发(如除零、野指针、缺页),对应“异常”(Exception)。
    • 缺页中断和一些错误都会转换为中断
    • 操作系统就是躺在中断处理历程的代码块
    • CPU内部的软中断,int 0x80 或者syscall,叫做陷阱
    • 而除零和野指针,叫做异常
      image.png

七、用户态与内核态:权限的“边界”

用户态和内核态是CPU的两种运行状态,核心区别是权限不同——内核态拥有访问所有硬件资源的权限,用户态仅能访问自己的地址空间,确保系统安全。

7.1 状态切换的触发条件

  • 用户态→内核态:只有通过“中断/异常/系统调用”才能切换,这是唯一途径。
  • 内核态→用户态:当中断/异常/系统调用处理完成后,通过iret(中断返回)或sysret(系统调用返回)指令切换回用户态。
    • image.png

7.2 系统调用的地址空间视角

系统调用的执行过程仍在当前进程的地址空间中进行——进程地址空间的“内核区”(高地址部分)映射了OS的代码和数据,当切换到内核态时,CPU就可以访问这部分区域,执行系统调用函数(如sys_fork)。

  • 内核图:
    -有一个sys_fork()的系统调用 image.png
  • 未来在代码区
    • image.png
    • 系统调用的过程,也是在进程地址空间上进行的
    • 所有的函数调用,都是地址之间的跳转
Logo

葡萄城是专业的软件开发技术和低代码平台提供商,聚焦软件开发技术,以“赋能开发者”为使命,致力于通过表格控件、低代码和BI等各类软件开发工具和服务

更多推荐