信号捕捉底层机制-进程与OS

文章目录

进程和OS

进程基础

在操作系统中,每个进程都由一个核心数据结构------进程控制块(PCB,Process Control Block)进行描述。在 task_struct 中,PCB 内部包含一个重要成员:

  • mm_struct(内存描述符)
    该结构用于描述进程的虚拟地址空间(Virtual Address Space)

虚拟地址空间与物理内存

每个进程都拥有独立的虚拟地址空间,该空间通过**页表(Page Table)**与物理内存建立映射关系。其核心过程基于:

  • Virtual Memory

  • Paging
    映射过程如下:

    虚拟地址(Virtual Address)

    页表(Page Table)

    物理地址(Physical Address)


地址空间的作用

  • 进程访问的代码段、数据段、堆、栈等,均位于虚拟地址空间中
  • 实际访问时,通过页表完成地址转换,最终定位到物理内存中的具体位置
  • 不同进程的虚拟地址可以相同,但映射到的物理地址不同,从而实现进程隔离

写时拷贝(Copy-On-Write)

在进程创建(如 fork)时,操作系统通常采用:

  • Copy-On-Write
    其机制为:
  • 初始阶段,父子进程共享相同的物理内存页(只读)
  • 当任一进程尝试写入时,才会触发页复制
  • 从而实现:
    • 延迟分配内存
    • 减少不必要的数据复制
    • 提高系统性能

每个进程通过 PCB(如 Linux 中的 task_struct)中的 mm_struct 描述其虚拟地址空间。该地址空间借助页表机制,将虚拟地址映射到物理内存,实现进程对代码和数据的访问。同时,操作系统通过写时拷贝(Copy-On-Write)等技术优化内存使用,提高系统整体效率。


内核级页表

全局页表与内核空间一致性 (Kernel Space Consistency)

在 Linux 等采用宏内核(Monolithic Kernel)架构的系统中,地址空间被划分为用户空间 (User Space)内核空间 (Kernel Space)

  • 内核级页表 (Kernel Page Table): 对应你提到的"内核级页表"。这是一种全局共享的数据结构,负责将虚拟地址空间的内核区域(在 32 位系统上通常是 3GB - 4GB ,即 High Memory Region)映射到物理内存中操作系统驻留的区域。
  • 全局共享 (Global Sharing): 由于物理内存中只驻留一份操作系统内核(内核镜像),因此所有进程的内核空间映射是完全一致的。在进程切换时,虽然用户态页表会发生翻转,但内核态映射部分在所有进程的页表中保持幂等(Idempotent)

内核映射机制:物理内存的唯一性 (Physical Uniqueness)

  • 内核加载与初始化 (Kernel Bootstrapping): 当计算机执行 Bootloader 并加载 OS 核心时,内核代码与静态数据被读入物理内存。由于内核是系统唯一的仲裁者,它在物理内存中具有单例性(Singleton)
  • 线性映射区 (Direct Mapping Region): 内核通常采用偏移映射(Offset Mapping)的方式,将物理内存地址直接加一个固定的偏移量映射到虚拟地址的内核空间。这保证了内核访问硬件资源的高效性。

硬件支撑:CR3 寄存器与 TLB 全局位

  • CR3 寄存器行为: 在上下文切换(Context Switch)期间,CR3 寄存器被重载以指向新进程的用户态页表。但由于内核页表项通常被标记为 Global (G位) ,这些映射在切换时不会从 TLB (快表) 中刷新。
  • 特权级保护: 即使内核代码映射在每个进程的地址空间中,用户态执行流也无法访问。必须通过 CPL (当前特权级) 为 0 的指令,配合页表项中的 U/S (User/Supervisor) 属性检查,才能合法进入内核空间。

进程地址空间与内核映射机制

在现代操作系统中,每个进程都拥有独立的虚拟地址空间(Virtual Address Space),该地址空间由其对应的页表进行管理与映射。

地址空间的结构划分

以典型的 32 位系统为例,进程虚拟地址空间通常划分为两部分:

  • 用户空间(User Space)
    • 范围:0 ~ 3GB
    • 特点:
      • 每个进程独立拥有
      • 存储进程的代码、数据、堆、栈等
      • 通过各自的页表映射到不同的物理内存区域
  • 内核空间(Kernel Space)
    • 范围:3GB ~ 4GB
    • 特点:
      • 在所有进程中映射一致
      • 指向操作系统内核的代码和数据
      • 在物理内存中仅存在一份

页表与映射关系

每个进程的页表不仅描述用户空间的映射关系,还包含对内核空间的映射:

  • 用户空间部分:

    复制代码
    用户虚拟地址 → 进程私有物理内存
  • 内核空间部分:

    复制代码
    内核虚拟地址 → 内核物理内存(全局唯一)

因此:

虽然每个进程拥有独立页表,但其内核空间映射部分是共享且一致的


内核共享映射的意义

由于操作系统在物理内存中只有一份,其代码和数据需要被所有进程访问。因此:

  • 内核空间被映射到每个进程的地址空间中
  • 所有进程"看到"的内核是同一份内容
  • 这保证了系统调用可以在任意进程上下文中执行

系统调用的执行本质

当进程需要访问操作系统提供的功能(即系统调用)时:

  1. 进程发起系统调用请求(如 readwrite 等)
  2. CPU 发生:
    • 特权级切换(用户态 → 内核态)
    • 控制流跳转到内核空间中的入口函数
  3. 在内核态执行对应的内核代码
  4. 执行完成后返回用户态,继续执行原进程代码

与动态库调用的关系

  • 动态库函数调用:
    • 本质是用户空间内的函数跳转
    • 不涉及特权级变化
  • 系统调用:
    • 本质是:
      • 地址空间内的跳转(因为内核已映射)
        • 特权级切换(关键区别)

进程访问操作系统接口时,并不需要切换到"另一个进程"或"进入内核实体",而是在自身地址空间中,通过页表映射访问内核空间,并在 CPU 的控制下完成从用户态到内核态的特权级切换,从而执行内核代码。整个过程始终发生在当前进程的执行上下文中

每个进程的虚拟地址空间同时包含用户空间和内核空间,其中用户空间是进程私有的,而内核空间在所有进程中共享映射。系统调用的本质是在当前进程上下文中,通过特权级切换进入内核态,并跳转到内核空间执行操作系统代码,执行完成后再返回用户态继续执行。


进程调度与地址空间一致性

在操作系统中,进程的执行与调度由内核统一管理。进程的切换主要由两种方式触发:

进程切换的两种触发方式

(1)被动调度(Preemptive Scheduling)

操作系统通过**中断机制(Interrupt)**强制剥夺当前进程的 CPU 使用权,例如:

  • 时钟中断(Time Slice 用尽)
  • 外部设备中断
  • 系统调度触发
    此时:

内核保存当前进程上下文,并切换到其他可运行进程


(2)主动让出 CPU(Cooperative Yield)

进程可以通过系统调用主动放弃 CPU,例如:

  • yield()(让出 CPU 时间片)
    该调用本质上是:
  • 一种系统调用(System Call)
  • 会触发:
    • 用户态 → 内核态切换
    • 调度器重新选择进程

系统调用与地址空间访问机制

当进程执行系统调用时,其本质流程为:

  1. 触发系统调用入口(trap / syscall)
  2. CPU 发生:
    • 特权级切换(用户态 → 内核态)
  3. 内核通过当前进程的:
    • task_struct
    • mm_struct
    • 页表(Page Table)
      定位其虚拟地址空间
  4. 执行对应内核服务
  5. 返回用户态继续执行

内核空间共享机制

在虚拟内存体系中:

  • 每个进程拥有独立的用户空间
  • 所有进程共享相同的内核空间映射
    其本质是:

每个进程的页表中都包含一段相同的内核映射区域(3GB~4GB)

该映射由操作系统统一维护。


内核页表与共享性

系统中存在一套统一的内核映射机制:

  • 所有进程共享同一份内核映射内容
  • 内核空间对应的物理内存只有一份
  • 该映射在所有进程页表中保持一致
    因此:

进程切换时,只需要切换用户空间映射,内核空间映射保持不变


进程切换对地址空间的影响

当发生进程调度时:

  • 用户空间:
    • 切换页表(不同进程不同映射)
  • 内核空间:
    • 保持一致映射(不变)
  • 内核页表映射:
    • 在所有进程中共享
    • 不随调度发生变化

在现代操作系统中,每个进程都拥有独立的用户虚拟地址空间,但其内核空间在所有进程中是统一映射的。因此,在进程切换过程中,操作系统仅需切换用户空间的页表映射,而内核空间映射保持不变,从而保证系统调用和内核执行的一致性与高效性

进程切换时只切换用户态页表映射,所有进程共享同一内核空间映射,因此内核空间在调度过程中保持不变。


基于硬件令牌的内核访问控制

在现代操作系统中,进程采用虚拟地址空间隔离机制,通常划分为:

  • 用户空间(User Space):0 ~ 3GB(或更高比例划分)
  • 内核空间(Kernel Space):3GB ~ 4GB

虚拟地址空间与页表机制

操作系统为每个进程维护独立的页表(Page Table) ,通过 CPU 的 CR3 寄存器 指向当前进程的页表基址。

页表的设计保证:

  • 每个进程的用户空间是独立的
  • 所有进程的内核空间在虚拟地址上是统一映射的
  • 切换进程时,只需切换 CR3,即切换页表
    因此:

内核空间在所有进程之间是共享映射的,但用户空间是隔离的。


特权级(Privilege Level)

CPU 提供特权级机制(Ring 0 ~ Ring 3):

  • Ring 3:用户态(User Mode)
  • Ring 0:内核态(Kernel Mode)
    用户程序默认运行在 Ring 3(用户态),不能直接访问内核资源。

用户态访问内核的限制

用户态程序不能直接访问内核空间(3GB~4GB),原因是:

  • CPU 会检查当前特权级(CPL)
  • 用户态(Ring 3)访问内核地址会触发异常(Page Fault / General Protection Fault)
    因此必须通过:

系统调用(System Call)接口


系统调用与特权级切换

当用户程序执行系统调用时,例如 read() / write()

CPU 会执行以下过程:

  1. 用户态程序通过 syscall / int 0x80 等指令发起系统调用
  2. CPU 触发特权级切换
  3. 从 Ring 3 切换到 Ring 0(内核态)
  4. 操作系统内核接管执行
  5. 内核完成资源访问后返回用户态

CR3与地址空间切换本质

CR3寄存器保存当前进程页表基地址:

  • 进程切换时:
    • 操作系统切换 CR3 → 切换页表
  • 内核空间映射不变:
    • 所有进程共享同一份内核虚拟映射
      因此:

无论进程如何切换,内核空间始终保持一致映射,从而保证系统调用的低开销。


权限控制本质

操作系统通过以下机制实现安全控制:

  • CPU 特权级(Ring 3 → Ring 0)
  • 页表访问权限位(U/S bit)
  • CR3控制进程地址空间
  • 系统调用入口统一收敛

操作系统的隔离机制本质上是 "硬件层面的门禁系统"

通过在 虚拟地址空间 (VAS) 的页表项中标记 U/S 位 ,硬件在物理层面上为内核空间修筑了一道围墙。而 CPL (当前特权级) 则是进程随身携带的身份令牌。

所谓的"系统调用",其本质并不是简单的函数跳转,而是一个**"通过硬件陷阱换取特权身份"的过程。只有在 CPU 硬件确认执行流已通过合法渠道(陷阱门)进入内核态,并将 CPL 翻转为 0 后,原本锁定的三到 4G 空间映射才会在 MMU 的过滤下变为"可访问"状态。这种基于硬件状态机的权限模型**,是现代计算机系统实现多租户隔离与内核安全的最底层保障。

系统调用与用户态→内核态切换机制

用户态如何进入内核态

在操作系统中,用户进程默认运行在用户态(User Mode,Ring 3) ,无法直接访问内核资源(Kernel Space)。

问题核心是:

用户程序在调用系统调用(System Call)时,是如何从用户态切换到内核态的?


系统调用的执行阶段划分

系统调用并不是"直接执行在内核中",而是分为两个阶段:

第一阶段:用户态准备阶段

系统调用入口函数(如 read() / write())首先在用户态执行,其主要作用是:

  • 准备系统调用参数
  • 将系统调用号写入寄存器
  • 触发陷入内核的指令
    此阶段仍属于:

User Space / Ring 3 执行


第二阶段:陷入内核(Trap / Interrupt)

用户态通过特定指令触发软中断(Software Interrupt)或系统调用指令

  • Linux x86 经典方式:int 0x80
  • 现代方式:syscall / sysenter
    例如:

int 0x80 = Software Interrupt(软件中断)


特权级切换机制

当 CPU 执行系统调用陷入指令时,会发生:

CPU 自动完成:
  • Ring 3(用户态) → Ring 0(内核态)
  • 保存用户态上下文(PC、寄存器等)
  • 切换栈到内核栈(Kernel Stack)
  • 跳转到系统调用处理入口函数

内核态执行系统调用

进入内核后:

  • 内核根据系统调用号(syscall number)
  • 查找系统调用表(sys_call_table)
  • 执行对应内核函数(如 sys_read
  • 访问内核资源(文件系统、内存、设备等)

返回用户态(Return from Kernel)

系统调用执行完成后:

  • 内核恢复用户态上下文
  • 执行 iret / sysret
  • CPU 从 Ring 0 切回 Ring 3
  • 返回用户程序继续执行

页表与内核空间共享机制

在 Linux 等操作系统中:

虚拟内存结构:
  • 用户空间:进程独立
  • 内核空间:所有进程共享映射
页表特点:
  • 每个进程拥有独立页表(CR3指向)
  • 内核空间在所有页表中高地址区域保持一致映射
  • 切换进程只切换 CR3,不改变内核映射

用户态函数调用 → 触发软中断/系统调用指令 → CPU陷入内核态 → 内核执行系统调用 → 返回用户态

系统调用并不是用户程序"直接进入内核执行",而是通过 int 0x80 / syscall 等陷入指令触发 CPU 特权级切换,由硬件自动完成从用户态(Ring 3)到内核态(Ring 0)的转换,并由内核根据系统调用号执行对应服务,完成后再恢复用户态。


小结:基于受控陷阱的模式切换

系统调用分层模型:用户态存根与内核入口 (Layered SCI Model)

系统调用的执行是一个从非特权指令流特权执行流演进的过程:

  • 用户态封装存根 (User-mode Stub/Wrapper): 在应用层调用接口(如 open)时,执行流仍处于 Ring 3 (用户态)。此阶段由 C 运行库(CRT)执行,完成**系统调用号(System Call ID)**的寄存器载入与参数压栈。
  • 指令边界 (The Instruction Boundary): 此时,执行流到达关键的自陷指令(Trap Instruction),这是权限边界的物理分割点。

硬件驱动的原子模式切换 (Atomic Mode Transition)

当 CPU 执行 INT 0x80SYSCALL 指令时,处理器内部的状态机将触发一系列**硬件硬连线(Hard-wired)**的原子操作:

  • 向量化中断触发 (Vectorized Interrupt Triggering): 处理器根据指令提供的向量号(如 0x80),检索内存中的中断描述符表 (IDT, Interrupt Descriptor Table)
  • 权限位原子翻转 (Atomic CPL Escalation): 硬件逻辑自动将 CS 寄存器 中的 CPL (当前特权级) 位由 11 (Ring 3) 清零为 00 (Ring 0)。
  • 上下文序列化 (Hardware Context Saving): 硬件自动将当前用户态的关键寄存器快照(如 RIP, RSP, EFLAGS)压入该进程关联的内核栈 (Kernel Stack)

基于内核空间统一映射的同步执行 (In-process Synchronous Execution)

系统调用利用了虚拟地址空间 (VAS) 的一致性映射,避免了跨进程切换的开销:

  • 全对称内核投影 (Symmetric Kernel Projection): 由于内核镜像已通过内核级页表 (Kernel Page Table) 映射至所有进程的高位地址空间(如 3GB - 4GB),因此跳转发生在当前地址空间内部
  • 页表权限解锁 (PTE Permission Unlocking): 随着 CPL 归零,MMU 自动解除对页表项中 Supervisor 标志位 的拦截,允许指令流跨越 3GB 边界进入内核代码区。
  • 进程上下文相关性 (Process-Context Affinity): 此时,CPU 虽在执行内核代码,但其所处的逻辑上下文 (包括页表基地址 CR3 和进程控制块 task_struct 指针)依然属于发起请求的进程。

操作系统的系统调用机制是 "硬件强制约束下的受控权限借调"

它并非简单的函数重定向,而是通过硬件自陷(Hardware Trapping)机制,在保持地址空间标识符 (ASID) 不变的前提下,完成了 CPU 执行状态的非对称性翻转 。这种设计确保了内核既能作为进程视野内的"特权层"提供高效同步服务,又能通过 IDT 中预设的唯一入口(Entry Point)防止用户态代码对内核空间的任意非法渗透。
系统调用是基于 硬件原子操作实现的,在 当前进程地址空间内跨越 Ring 3 与 Ring 0 边界**的受控执行流转移。


系统调用返回与特权级恢复机制

系统调用返回机制(Return from System Call)

当系统调用执行完成后,内核通过特定的返回指令将控制权交还给用户程序,例如:

  • iret(Interrupt Return)
  • sysret(快速系统调用返回)
    该过程属于:

从内核态(Ring 0)恢复到用户态(Ring 3)


CPU 状态恢复过程(关键机制)

在返回用户态时,CPU 会自动完成以下操作:

恢复执行上下文:
  • 恢复用户态程序计数器(PC / RIP)
  • 恢复通用寄存器(EAX、EBX 等)
  • 恢复标志寄存器(EFLAGS / RFLAGS)
  • 切换回用户栈(User Stack)

特权级恢复:
  • CPU 将当前特权级(CPL)从 Ring 0 → Ring 3
  • 恢复用户态执行权

特权级不可由用户直接修改

CPU 的特权级控制属于硬件级安全机制

  • CPL(Current Privilege Level)由 CPU 自动维护
  • 用户程序无法直接修改 CPL
  • 特权级变化只能通过:
    • 中断(Interrupt)
    • 异常(Exception)
    • 系统调用(System Call)
      因此:

用户态程序无法通过指令直接提升权限(防止非法访问内核)


内核空间访问限制机制

即使返回用户态后:

  • 用户程序仍运行在 Ring 3
  • 仍无法直接访问内核空间(3GB~4GB 或高地址内核映射区)
    访问规则由以下机制共同保证:
CPU 特权级检查(Ring Check)
  • 用户态访问内核地址 → CPU 触发异常
页表权限位(U/S bit)
  • User/Supervisor bit 控制访问权限
  • 用户页无法访问内核页

系统调用的完整生命周期

完整过程如下:

用户态阶段

  • 调用系统调用接口(libc wrapper)
  • 设置系统调用号 + 参数
    陷入内核
  • 触发 int 0x80 / syscall
  • CPU 切换到 Ring 0
    内核执行
  • 执行系统调用处理函数
  • 访问内核资源
    返回用户态
  • 执行 iret/sysret
  • 恢复用户态上下文
  • CPU 切回 Ring 3

系统调用返回时,CPU 通过 iret/sysret 恢复用户态上下文,并将特权级从内核态(Ring 0)切换回用户态(Ring 3),用户程序继续执行,但仍受页表权限与特权级机制限制,无法直接访问内核空间。


信号机制与内核态/用户态切换关系

基本前提:用户态与内核态的本质

在操作系统中,进程始终运行在某一特权级状态下:

  • 用户态(User Mode / Ring 3)
  • 内核态(Kernel Mode / Ring 0)
    两种状态的本质区别在于:
  • 当前 CPU 执行特权级(CPL)
  • 当前访问的虚拟地址空间权限(用户页 / 内核页)
  • 当前执行的代码权限(用户程序 / 内核代码)

状态切换的开销来源

用户态与内核态之间的切换会涉及一系列开销操作,包括:

  • CPU 特权级切换(Ring 3 ↔ Ring 0)
  • 保存/恢复上下文(寄存器、PC、栈等)
  • 切换内核栈(Kernel Stack)
  • 可能的页表切换或权限检查
  • 跳转到内核入口函数执行
    因此该过程属于:

高开销的上下文切换(Context Switch / Mode Switch)


进入内核态的典型场景

用户进程进入内核态通常由以下机制触发:

系统调用(System Call)

用户程序主动请求内核服务(如 read/write/fork


异常/缺页/陷阱(Exception / Trap)

例如:

  • Page Fault
  • Divide by zero
  • Illegal instruction

中断与调度(Interrupt / Scheduling)

例如:

  • 时钟中断触发进程调度
  • I/O 中断唤醒进程

进程调度与内核态关系

当发生进程调度时:

进程被挂起:
  • 当前进程进入内核态
  • 被放入运行队列 / 等待队列
  • 操作系统保存其上下文(PCB)
进程被唤醒:
  • 由内核调度器重新选中
  • 在内核态恢复执行上下文
  • 最终返回用户态继续执行
相关推荐
青瓦梦滋2 小时前
Linux线程
linux·运维·c++
oLLI PILO2 小时前
在linux(Centos)中Mysql的端口修改保姆级教程
linux·mysql·centos
小张成长计划..2 小时前
【C++】23:封装set和map
c++
埃伊蟹黄面2 小时前
网络层 IP 协议
服务器·网络·tcp/ip
满天星83035772 小时前
【Linux/多路复用】select
linux·运维·服务器·c语言·c++
t***5442 小时前
如何验证Clang是否在Dev-C++中正常工作
开发语言·c++
cyber_两只龙宝2 小时前
【Oracle】Oracle之SQL的集合运算符
linux·运维·数据库·sql·云原生·oracle
Hello.Reader2 小时前
Ubuntu 安装 Miniconda 完整从零开始把 Conda 环境搭起来
linux·ubuntu·conda
charlie1145141912 小时前
嵌入式C++开发第17篇:C++23特性收尾 —— 属性、链接与零开销抽象的最终证明
开发语言·c++·stm32·学习·c++23