进程和线程

🔥 高优先级
进程的相关概念 是历年操作系统选择题必考,关键在于几点:进程和线程、进程状态转换、进程内存空间的架构。

在多道程序环境下,允许多个程序并发执行,为此操作系统引入了 进程 (Process) 的概念,以便更好地描述和控制程序的并发执行,实现操作系统的并发性。

进程

进程是系统资源分配的基本单位。操作系统以进程为单位建立相对独立的资源环境,例如 虚拟地址空间、打开的文件、访问权限以及其他内核资源。

不同进程通常拥有独立的虚拟地址空间,因此一个进程不能直接访问另一个进程中的数据。同一进程中的多个线程则共享进程所拥有的虚拟地址空间和大部分系统资源。

可以将进程和线程的作用概括为:

  • 进程 主要解决“资源归谁所有”的问题,是资源拥有和保护的基本单位。
  • 线程 主要解决“由谁执行代码”的问题,是处理机调度和执行的基本单位。
进程和程序

程序是 静态的,进程是 动态的。进程可以理解为程序的一次执行过程,两者的具体区别如下:

  • 程序
    • 程序是一组指令和数据的静态集合,通常存储在磁盘等持久性存储介质中。
    • 程序本身是被动的,不会自行执行。
    • 同一个程序可以被多次运行,每次运行通常都会创建一个新的进程。
  • 进程
    • 进程是程序的一次执行过程。
    • 当程序被加载并开始执行时,操作系统会为其创建进程及相应的管理信息。
    • 进程是动态的,具有创建、运行、阻塞、终止等生命周期状态。
Disk
program
CPU
Memory
process
stack
code
static data
heap
Loading:

读取磁盘中的程序并将其加载到进程的地址空间中
code
static data
进程控制块

操作系统通过 进程控制块(PCB,Process Control Block)管理每一个进程。

可以把 PCB 看作操作系统为每个进程建立的一张 管理档案,其中记录了操作系统管理该进程所需的各种信息。只要进程存在,对应的 PCB 就始终保存在内存中,操作系统几乎所有与进程相关的操作都需要访问它。

PCB 主要包含以下几类信息:

信息类别典型内容
进程标识信息进程标识符(PID)、用户标识符(UID)
进程控制信息进程状态、进程优先级、调度信息
处理机状态信息程序计数器(PC)、通用寄存器、程序状态字(PSW)
资源信息地址空间信息、打开的文件、内存管理信息等
进程控制块 PCBPCB(进程控制块)进程标识信息PID(进程标识符)· UID(用户标识符)进程控制信息进程状态 · 进程优先级处理机状态信息PC · 通用寄存器 · PSW(程序状态字)资源信息打开的文件 · 内存指针 · 其他资源常驻内存,随进程生命周期存在① 进程创建与终止• 创建时,OS 为进程新建 PCB• PCB 常驻内存,可随时存取• 进程结束时,PCB 被删除PCB 是进程存在的唯一标志② 进程调度• OS 读取 PCB 中优先级/状态• 决定哪个进程获得 CPU• 调度结果写回 PCB 状态字段PCB 是调度决策的依据③ 进程切换(上下文切换)• 切换时保存当前进程 CPU 现场 → PCB• 从 PCB 恢复下一进程的 CPU 现场• 关键字段:PC、寄存器组、PSWPCB 保存/恢复处理机状态信息,实现进程切换四类信息说明标识信息:唯一标识进程控制信息:调度与管理处理机信息:保存 CPU 现场资源信息:记录占用资源

各类信息的作用如下:

  • 进程标识信息:用于唯一标识和区分不同进程。
  • 进程控制信息:供操作系统进行调度和管理,例如记录进程当前处于就绪态、运行态还是阻塞态,以及进程的优先级等。
  • 处理机状态信息:保存程序暂停执行时的 CPU 运行现场,例如程序计数器、寄存器和程序状态字等,使程序再次运行时能够从原来的位置继续执行。
  • 资源信息:记录进程拥有或使用的系统资源,例如虚拟地址空间、打开的文件等,便于操作系统统一管理和回收。

在传统的 单线程进程模型 中,一个进程只有一个执行流,因此程序计数器、寄存器和程序状态字等处理机现场可以直接记录在 PCB 中。

引入线程后,一个进程中可能存在多个执行流,每个线程都需要分别保存自己的程序计数器、寄存器、栈和调度状态。这些线程级信息通常记录在线程控制块或相应的内核线程结构中,而 PCB 主要保存进程级的地址空间和资源信息。

PCB 在进程的整个生命周期中都会被操作系统使用,主要体现在以下几个方面:

  • 进程创建:操作系统为新进程创建对应的 PCB,并初始化其中的各项信息。
  • 进程管理:操作系统通过 PCB 记录和维护进程的状态、优先级、资源等信息。
  • 进程切换:在传统单线程模型中,操作系统保存当前进程的运行现场,并恢复下一进程的运行现场。
  • 进程终止:操作系统释放进程占用的资源,并删除对应的 PCB。

在线程模型中,处理机调度器实际选择的是某个可运行线程,并将其调度到某个逻辑核心上执行。

父子进程

在操作系统中,父子进程 是通过进程创建机制形成的一种层级关系:

  • 父进程 :创建新进程的进程。
  • 子进程 :由父进程创建的进程。

在 UNIX 和 Linux 系统中,子进程通常由父进程通过 fork 系统调用 创建。

fork 创建子进程后,子进程获得与父进程内容近似相同、但逻辑上独立的虚拟地址空间,包括代码段、数据段、堆和栈。父子进程拥有各自独立的执行状态,一方修改自己地址空间中的数据,通常不会直接影响另一方。

现代操作系统通常采用 写时复制(Copy On Write)机制实现 fork。在 fork 刚完成时,父子进程可以暂时共享相同的物理内存页;只有当父进程或子进程尝试修改某个内存页时,操作系统才真正复制该页。

子进程还会继承父进程打开的文件描述符等部分资源。父子进程也可以通过进程间通信(IPC)机制,例如管道、信号和共享内存,进行数据交换或同步。

fork 系统调用的声明为:

pid_t fork(void);

其返回值可以用于判断当前执行的是父进程还是子进程:

  • 父进程 中,返回值为子进程的 PID,即返回值大于 0
  • 子进程 中,返回值为 0
  • 如果创建失败,则在父进程中返回 -1,不会创建子进程。
main(){pid = fork();if (pid == 0) {} else {············}}parentmain(){pid = fork();if (pid == 0) {} else {············}}child

fork 调用成功后,父进程和子进程都会从 fork 返回处继续执行,但二者获得的返回值不同,并且可能按照不同的顺序被调度运行。

fork()
fork()
exit()
wait()
child (pid = 0)
parent (pid > 0)
parent resumes

除了 fork 外,还需要理解 exec 系列调用。两者功能不同:

  • fork:创建一个新的子进程。子进程获得父进程地址空间和部分资源的副本,父子进程继续执行原来的程序。
  • exec:在当前进程中装入并执行一个新程序,用新程序的代码段、数据段、堆和栈替换原有的进程映像,但通常保留 PID 等进程身份信息。

exec 不创建新的进程,因此不会增加新的并发执行流。exec 调用成功后,原程序不会继续执行;只有调用失败时,exec 才会返回。

此外,父进程可以通过 waitwaitpid 系统调用等待子进程终止,并读取子进程的退出状态,从而完成对子进程的回收。

注意

僵尸进程 (Zombie Process)是指一个已经终止,但其父进程尚未通过 waitwaitpid 读取其退出状态的子进程。

僵尸进程已经停止运行,其用户地址空间、打开的文件等大部分资源已经被释放,只在内核进程表中保留 PID、退出状态和少量统计信息,等待父进程回收。

孤儿进程 (Orphan Process)是指父进程在子进程终止前退出,使子进程失去了原来的父进程。

孤儿进程本身通常仍在正常运行。它一般会被 initsystemd 或其他子进程收割者接管,并在终止后由新的父进程负责回收。

下表给出了两者的对比:

项目僵尸进程(Zombie Process)孤儿进程(Orphan Process)
状态已经终止仍在运行
原父进程状态仍然存在,但尚未回收子进程已经终止
是否占用 CPU可能占用
主要保留资源PID、退出状态和少量进程表信息正常运行所需的各种资源
处理方式父进程调用 wait()waitpid() 回收通常由其他进程接管

线程

线程是处理机调度和执行的基本单位

进程主要负责拥有虚拟地址空间、打开的文件等系统资源,线程则表示进程内部的一条执行流。操作系统调度时,实际选择的是某个可运行线程,并将其调度到某个 CPU 逻辑核心上执行。

多核处理器可以将不同线程调度到不同的 逻辑核心 上,因此多个线程可以在同一时刻并行运行。

每个进程启动时,至少包含一个线程,通常称为 主线程。主线程可以继续创建其他线程,从而形成多线程进程。

Code
Data
Files
Stack
Registers
Processes
Code
Data
Files
Stack
Multi-Threaded Process
Register
Stack
Register
Stack
Register
T1
T2
T3
T1
T2
T3

同一进程中的线程共享进程级资源,主要包括:

  • 虚拟地址空间;
  • 代码段;
  • 全局变量和静态变量;
  • 堆;
  • 打开的文件;
  • 其他进程级系统资源。

每个线程则拥有自己独立的执行现场,主要包括:

  • 线程标识符;
  • 程序计数器;
  • 寄存器组;
  • 栈;
  • 线程状态;
  • 调度优先级等调度信息。

线程拥有独立的栈,是指每个线程在进程虚拟地址空间中具有自己的栈区域。由于同一进程中的线程共享虚拟地址空间,一个线程原则上仍可能通过指针访问其他线程栈中的数据,因此线程之间并不存在像进程之间那样严格的地址空间隔离。

在一个进程中没有额外创建线程时,该进程为 单线程进程;如果一个进程中包含多个线程,则称为 多线程进程

Stack
Heap
Program + Data
Stack
Heap
Program + Data
Stack
Stack
单线程的进程
包含多个线程的进程
进程的
内存空间
单线程进程

单线程进程只有一个执行线程,因此进程的虚拟地址空间和打开的文件等进程级资源只由该线程使用,不存在进程内部的线程同步问题。

单线程进程具有以下特点:

  • 结构简单,程序设计和调试相对容易。
  • 不需要处理多个线程之间的共享数据竞争。
  • 当唯一线程因等待 I/O 或其他事件而阻塞时,进程中没有其他线程可以继续执行。
  • 任一时刻最多只有一个线程在一个逻辑核心上运行,因此不能依靠进程内部的多个线程同时利用多个逻辑核心。

单线程虽然可能在不同时间被调度到不同逻辑核心上运行,但在任一时刻只能占用一个逻辑核心执行。

多线程进程

多线程进程包含多个线程,这些线程共享进程的虚拟地址空间和系统资源,但分别拥有自己的程序计数器、寄存器和栈。

多线程进程具有以下特点:

  • 多个线程可以直接访问共享的全局变量、堆和其他进程级资源,通信效率较高。
  • 多个线程可以被调度到不同逻辑核心上并行执行,提高处理器利用率。
  • 当一个线程阻塞时,其他线程仍有机会继续运行。
  • 多个线程同时访问共享数据时,可能发生竞态条件,需要使用互斥锁、信号量等同步机制。
  • 同一进程中的线程隔离性较弱,一个线程发生严重错误可能导致整个进程终止。

完整的进程虚拟地址空间布局,以及线程视角下共享区域和私有区域的关系,可参考下图:

进程虚拟地址空间布局(32 位 Linux 典型视图)内核空间 (Kernel)所有进程共享,用户态不可访问栈 (Stack)函数调用帧、局部变量向下增长 ↓ ,ESP/SP 指向栈顶未分配空间内存映射区 (mmap)动态库 .so / 文件映射匿名映射、共享内存未分配空间堆 (Heap)malloc / new 动态分配向上增长 ↑ ,brk 移动边界未初始化数据 (BSS)未赋初值的全局/静态变量已初始化数据 (Data)带初值的全局/静态变量代码段 (Text)只读、可执行的机器指令高地址0xFFFFFFFF0xC0000000brk →0x08048000低地址 0栈向下堆向上线程视角同一进程内的多个线程:• 共享:Text、Data、BSS、Heap、mmap• 私有:每线程一份 Stack、寄存器用户态 vs 内核态边界用户态只能访问下半段;通过系统调用陷入内核态后才能访问内核空间。PCB / 内核栈每进程在内核空间还有 PCB 与内核栈。栈与堆相向生长,中间是 mmap / 未分配区;32 位下 3:1 分界(0xC0000000)将用户/内核分开

引入线程后,一个进程可能同时包含多个执行流,每个线程都拥有各自独立的程序计数器、寄存器和栈,因此这些与执行和调度相关的信息不能再统一保存在 PCB 中,而需要为每个线程分别维护一份管理信息,通常称为 TCB(Thread Control Block,线程控制块)

因此,在现代操作系统中,可以将 PCBTCB 的职责概括如下:

  • PCB(进程控制块):管理整个进程拥有的资源,例如虚拟地址空间、页表、打开的文件、权限等进程级信息。
  • TCB(线程控制块):管理单个线程的执行现场,例如程序计数器、寄存器、栈、线程状态以及调度信息等。

二者的关系如下图所示:

PCB与TCB的关系PCB作为进程控制块管理虚拟地址空间、页表、打开的文件等资源,并持有线程列表;线程列表下连接多个TCB,每个TCB管理单个线程的PC、寄存器、栈、线程状态和调度信息PCB(进程控制块)PID虚拟地址空间 / 页表打开的文件 / 权限......线程列表TCB(线程1)PC寄存器线程状态 / 调度信息TCB(线程2)PC寄存器线程状态 / 调度信息

可以看出,一个 PCB 对应一个 进程,而一个 进程 可以包含多个 线程,因此一个 PCB 通常会关联多个 TCB

操作系统在进行资源管理时,主要访问 PCB;而进行线程调度和上下文切换时,则主要访问对应线程的 TCB,保存和恢复该线程的程序计数器、寄存器等运行现场。

进程和线程对比
对比项目进程线程
基本作用建立资源拥有和保护边界建立执行流
基本单位系统资源分配的基本单位处理机调度和执行的基本单位
虚拟地址空间不同进程通常相互独立同一进程中的线程共享
打开的文件等资源由进程拥有同一进程中的线程共享
程序计数器和寄存器单线程模型下只有一套每个线程各自拥有一套
进程中至少有一个线程栈每个线程拥有独立的栈
通信方式通常需要使用 IPC可以直接访问共享地址空间
切换开销通常较大,可能涉及地址空间切换同一进程内的线程切换通常较小
隔离性较强较弱
并行能力依赖进程中可运行线程的数量多个线程可在多个逻辑核心上并行执行

因此,可以将进程和线程之间的关系概括为:

进程是资源的拥有者和保护边界,线程是进程内部的执行流和处理机调度对象。

进程的状态

进程状态 是指在操作系统中,一个 进程 在执行过程中的不同阶段。它反映了 进程 当前正在做什么,以及是否可以被 CPU 执行。

状态种类

不同的操作系统对 进程状态 的划分可能不同,但常见的包括以下几种:

  1. 创建状态(New):当 进程 被创建但还未分配资源或执行时,它处于 创建状态
  2. 就绪状态(Ready):在 就绪状态 中,进程 已准备好执行,但由于操作系统调度算法或其他原因,尚未获得 CPU 时间片。
  3. 运行状态(Running):在 运行状态 中,进程 正在执行指令并占用 CPU
  4. 阻塞状态(Blocked):当 进程 在等待某些事件发生时,如等待 I/O 操作完成或等待其他资源时,它会进入 阻塞状态。在 阻塞状态 下,进程 暂停执行,直到等待的事件发生。
  5. 终止状态(Terminated):当 进程 执行完毕或被操作系统终止时,它进入 终止状态

状态转化

新建
就绪
运行
终止
阻塞
创建
调度
时间到
事件等待
事件发生
退出

1. 就绪态 → 运行态

触发条件详细说明
调度器选择进程调度器从就绪队列中选出进程,并分配 CPU。
CPU 空闲CPU 空闲时,调度器立即选择就绪进程运行。

2. 运行态 → 就绪态

触发条件详细说明
时间片耗尽进程用完时间片,被中断并放回就绪队列。
抢占更高优先级进程就绪,当前进程被抢占。
自愿放弃 CPU进程主动让出 CPU,重新回到就绪队列。
非阻塞系统调用返回系统调用未阻塞,进程仍可运行,但可能被调度器切换出去。

3. 运行态 → 阻塞态

触发条件详细说明
I/O 请求进程等待 I/O 完成,暂停运行。
等待资源进程请求的锁、信号量等资源暂不可用。
等待事件进程等待信号、消息或条件满足。
主动休眠进程调用 sleep() 等接口,主动进入睡眠。
等待子进程进程调用 wait() / waitpid() 等待子进程结束。
内存页缺失访问未在内存中的页面,需等待页面换入。

4. 阻塞态 → 就绪态

触发条件详细说明
I/O 完成I/O 完成后,进程被唤醒并进入就绪队列。
资源获得等待的资源可用,进程被唤醒。
事件发生等待的信号、消息或条件已经出现。
定时器超时睡眠或定时等待到期,进程恢复就绪。
资源释放其他进程释放资源,等待进程被唤醒。
信号唤醒信号中断阻塞状态,使进程重新就绪。

进程内存空间

  • 用户空间(User Space):包含 进程 执行的用户程序代码和数据。在 用户空间 中,进程 可以执行各种任务,如运行应用程序、访问文件系统等。用户空间 对于应用程序是可见的,但对于操作系统中的核心功能是不可见的。
    1. 代码区(Text Segment):也称为“可执行代码区”,存储了 进程 的可执行代码,包括程序的指令和只读数据。这个区域通常是只读的,因为程序的指令在运行时不应被修改。
    2. 数据区(Data Segment),数据区分为两个子区域:
      • 初始化数据区(Initialized Data Segment):存储全局和静态变量以及初始化的数据。这些变量在程序运行前就已经分配了内存并初始化。
      • 未初始化数据区(Uninitialized Data Segment),也称为 BSS(Block Started by Symbol)段,存储全局和静态变量,但这些变量没有显式的初始化值。操作系统会在程序启动时自动将这个区域初始化为零。
    3. 堆区(Heap):堆区动态分配内存 的地方,用于存储程序运行时需要的变量和数据结构。在 中分配的内存需要手动释放,以避免内存泄漏。
    4. 栈区(Stack):栈区 用于存储函数调用和局部变量。每个函数调用都会在栈上创建一个栈帧,栈帧包含了函数的参数、局部变量以及函数返回地址。 是一种后进先出(LIFO)的数据结构,它的大小通常有限,由操作系统或编程语言定义。
    5. 内存映射区域(Memory Mapped Region):这是一些操作系统或运行时库的扩展,用于存储动态链接库(DLL)和共享库的信息以及其他系统数据结构。
  • 内核空间(Kernel Space):内核空间 包含了操作系统的核心代码和数据结构,如页表、调度程序和系统调用接口等。内核空间 具有更高的特权级别,可以执行特权指令并且访问系统的各种资源,内核空间 对于用户程序是不可见的。
Process-specific data
structures
(e.g. page tables, tasks and kernel stack)
Physical Memory
Kernel code and data
User stack
Memory-mapped region
for shared libraries
Run-time heap
Uninitialized data (.bss)
Initialized data (.data)
Code (.text)
Different for
each process
Identical for
each process
Kernel
virtual
memory
Process
virtual
memory
low
address
space
high
address
space

函数调用时内存结构

上一个栈帧
上一个栈帧的 EBP
局部变量
callee 的参数列表 n ~ 1
caller 返回地址
caller 栈帧的 EBP
局部变量
下一个函数的参数列表 n ~ 1
返回地址
下一个栈帧
调用函数(caller)的栈帧
被调用函数(callee)的栈帧
高地址,栈底
低地址,栈顶
caller 的 EBP
callee 的 EBP

EBP (base pointer) 指向函数栈(栈帧)的底部(高地址),函数执行过程中在栈帧中分配局部变量,栈帧由高地址向低地址增长,ESP(stack pointer)一直指向栈顶。

每个函数的栈帧中包含如下内容:

  • 上一个函数的 EBP
  • 该函数的局部变量
  • 如果函数内有 call 指令的话,还需要保存额外信息:
    • 下一个函数的参数:依次存储从第 n 个到第 1 个,从高地址到低地址
    • 返回地址:当前 PC 指向的位置,即 call 指令的下一条指令的地址

在函数的调用过程中,调用函数叫做 caller,被调用函数叫做 callee。当我们从 caller 中调用 callee 时,calleeEBP 指向的物理地址的上下存储单元分别包含 caller 的返回地址以及 caller 所在栈帧的 EBP,当我们在 callee 中执行 ret 指令时,计算机可以跳转到 callercall 指令的下一条并开始执行,同时 caller 的栈帧也会被恢复。

main
main
f1
main
f1
f2
main
f1
main
高地址
低地址
main(f1(f2()))
时间
函数的栈帧在内存中的结构变化

进程间通信

💡 低优先级
偶尔在选择题考察,了解基本 IPC 方式,选择题看到能辨识即可。

进程间通信(Inter-Process Communication,IPC)是指在同一计算机系统中,两个或多个 进程 之间交换数据或信号的机制。常见的 进程间通信 方式主要包含 共享内存管道消息队列信号套接字信号量

Process  A
Shared Memory
Process B
Kernel
M
M
M
Kernel
1
2
1
2
Process  A
Pipe
Process B
Kernel
消息传递
管道
共享内存
Process A
Process B

下图汇总了常见 IPC 方式的数据流向、效率与同步需求差异:

五类进程间通信(IPC)对比① 管道 / FIFOP1P2字节流(内核缓冲)单向、FIFO、亲缘关系(FIFO 不需要)效率较低,适合简单流② 共享内存P1P2同一物理内存段效率最高、零拷贝需配合信号量/锁防竞争③ 消息队列P1P2[msg|msg|msg]内核队列按消息单元,有类型/优先级结构化、独立性强④ 信号 (Signal)P1P2SIGINT / SIGUSR1 ...异步通知,无数据载荷用于事件、异常、终止⑤ 套接字 (Socket)P1P2网络协议栈本机或跨机器双向通信分布式系统常用⑥ 信号量 (Semaphore)P1P2count = N (内核维护)不传数据,做同步/互斥P/V 操作维护计数关键属性对比属性管道共享内存消息队列信号套接字信号量数据载荷字节流任意结构消息字节流/包效率较低最高高(无数据)方向单向双向双向单向双向-同步需求内核保证需用户同步内核保证异步内核保证本身就是同步

管道

管道(Pipe) 是一种最基本的 进程间通信(IPC) 机制,本质上是由操作系统内核维护的一块 内存缓冲区。进程并不是直接把数据写给另一个进程,而是:

写进程 → 管道内核缓冲区 → 读进程

管道通常具有 固定大小 的缓冲区,数据只存在于内存中,不以普通磁盘文件的形式保存。管道中的数据按照 先进先出(FIFO) 的顺序被读取,因此先写入的数据会先被读出。

可以把管道理解成一个由内核维护的缓冲区:

管道(Pipe)机制示意图展示写进程通过内核维护的管道缓冲区将数据传递给读进程,缓冲区内数据按FIFO顺序存放用户空间内核空间用户空间写进程调用 write()读进程调用 read()内核管道缓冲区FIFO 先进先出已写入数据(按顺序排列)write()read()
  • 读端:用于从管道中读取数据
    • 读进程执行 read() 时,操作系统从管道缓冲区中取出数据,并将数据复制到读进程的用户空间。
  • 写端:用于向管道中写入数据
    • 写进程执行 write() 时,数据并不是直接进入读进程的地址空间,而是先被复制到 内核维护的管道缓冲区 中。

可以直接类比 生产者消费者问题

  • 管道 未满:写进程可以继续写;管道 已满 时,写进程会被阻塞,等待读进程取走数据。
  • 管道 非空:读进程可以继续读;管道 为空 时,读进程会被阻塞,等待写进程写入数据。

因此,管道的读和写操作都可能发生阻塞

管道中的数据以字节流形式传输,并按 FIFO(先进先出) 顺序读取。经典管道是单向通信(半双工),若要实现双向通信,通常需要建立两个管道。

管道只在内存中存在,容量受 内核缓冲区大小 限制,而不是磁盘容量限制。

管道可以分为:

  • 无名管道(anonymous pipe)
  • 有名管道(named pipe / FIFO)

无名管道 没有文件系统中的名字,通常用于具有亲缘关系的进程,例如父子进程、兄弟进程。

有名管道(FIFO) 在文件系统中具有一个名字,因此不要求通信进程之间具有亲缘关系。

共享内存

共享内存 是效率很高的一种 进程间通信(IPC) 方式。多个进程可以将同一组 物理内存页 映射到各自的 虚拟地址空间 中,从而直接读写这片共享区域。

例如:

共享区
未初始化数据
初始化数据
代码段
内核区
用户空间
页表
进程 A
虚拟地址空间
共享区
未初始化数据
初始化数据
代码段
内核区
用户空间
进程 B
虚拟地址空间
页表
物理地址空间
共享内存区域
进程 A
进程 B

注意,不同进程的虚拟页号(VPN)可以不同,但可以映射到同一个物理页号(PPN)

因此,A 和 B 虽然使用不同的虚拟地址访问,但最终访问的是同一块物理内存

反过来,一个进程的一个虚拟页只能在某一时刻映射到一个物理页:

而不能:

因为同一个虚拟地址必须具有确定的地址转换关系,否则 CPU 无法确定访问哪个物理地址。

共享内存的核心思想可以概括为:

多个进程的不同虚拟地址 → 同一个物理内存区域。

由于数据直接在共享内存中读写,通信过程中不需要像管道那样反复在进程与内核之间复制数据,因此数据传输效率很高。但多个进程可能同时读写同一数据,所以仍然需要借助 信号量、互斥锁等同步机制 防止竞争条件。

消息队列

消息队列 是一种基于内核的数据结构,允许多个进程以消息为单位进行通信,具有良好的结构化和独立性。进程通过系统调用将消息发送到队列中或从中接收消息,可以实现同步与异步的通信模式。

相比 管道消息队列 的通信更加灵活,支持优先级管理,适用于需要有序、分类传输数据的场景。但由于涉及内核操作,性能开销相对 共享内存 较大。

用户级线程和内核级线程

💡 低优先级
偶尔在选择题考察,了解几种线程模型和映射关系,选择题看到能辨识即可。
  • 用户级线程 (ULT,User Level Thread)
    • 用户级线程 是由应用程序通过线程库来实现的,操作系统内核并不直接感知到这些 线程 的存在。
    • 线程的创建、调度和管理都由应用程序在 用户空间 完成。
  • 内核级线程 (KLT,Kernel Level Thread)
    • 内核级线程 是由操作系统内核直接支持的线程。
    • 线程的创建、调度和管理都由 操作系统内核 完成。
用户级线程 (ULT)用户空间内核空间用户 / 内核边界进程线程库创建 / 调度 / 切换(用户态完成)线程 T1线程 T2线程 T3• 线程实体位于用户空间• 切换不陷入内核,开销小、速度快• 一个线程阻塞会导致整个进程阻塞• 无法被内核调度到多核并行执行• 可跨 OS 移植(依赖线程库)PCB(进程)内核只看到 1 个执行实体对线程完全无感知内核调度以进程为单位同一时刻只有 1 个线程能运行内核级线程 (KLT)用户空间内核空间用户 / 内核边界进程线程 T1线程 T2线程 T3• 用户程序通过系统调用请求内核创建/管理线程• 每个线程都有内核对应实体内核线程调度器KTCB 1对应 T1KTCB 2对应 T2KTCB 3对应 T3CPU 0CPU 1CPU 2• 线程的创建/切换需陷入内核,开销较大• 一个线程阻塞不影响进程中其他线程• 内核可将线程调度到不同 CPU 并行执行

线程模型

操作系统中的 线程实现方式 称为 线程模型,不同的 线程模型用户级线程(ULT)和 内核级线程(KLT)的设计上各有不同。

系统中的 线程模型 可以分为如下三种:

  • 纯用户态:所有的线程操作都在用户空间中进行,内核对线程的存在一无所知。
  • 纯系统态:线程由操作系统内核直接支持和管理,内核负责线程的创建、调度和管理。
  • 混合方案:应用程序可以在用户空间管理多个用户级线程,这些线程映射到较少数目的内核级线程。
Thread
Library
P
User
Space
Kernel
Space
P
User
Space
Kernel
Space
P
User
Space
Kernel
Space
P
Thread
Library
a) Pure user-level
b) Pure kernel-level
c) combined
user-level thread
kernel-level thread
P
Process

混合方案中的映射关系

如果使用 混合方案线程模型 的话,用户级线程内核级线程 的映射方式也可以分为如下几种:

  • 一对一:每一个用户级线程都对应一个内核级线程。
  • 多对一:多个用户级线程映射到同一个内核级线程上。
  • 多对多:多个用户级线程映射到多个内核级线程上。