这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

操作系统

User
Application
Operating
System
Hardware

操作系统在 408 试卷中占据 35 分,是分数第三高的科目,包含十道选择题和两道大题。

需要注意的是,内存管理和 I/O 管理与计算机组成原理有很多相似之处。建议在复习时将这两部时对比学习,以深化对它们的理解。除此外,进程管理和文件管理也是重要的内容,大题常常会让你编写代码去解决一个同步问题,因此需要熟练掌握信号量的使用和同步逻辑的设计。


操作系统的考察目标包含如下内容(来自 408 考研大纲):

  1. 掌握操作系统的基本概念、方法和原理,了解操作系统的结构、功能和服务,理解操作系统所采用的策略、算法和机制。
  2. 能够从计算机系统的角度理解并描述应用程序、操作系统内核和计算机硬件协作完成任务的过程。
  3. 能够运用操作系统的原理,分析并解决计算机系统中与操作系统相关的问题。

1 - 计算机系统概述

操作系统的基本概念,可能会在选择题中考察,看到题目能够选出答案即可。
# 操作系统基础

## 操作系统的基本概念

## 操作系统发展历程

## 程序运行环境

- CPU运行模式:内核模式、用户模式
- 中断和异常的处理
- 系统调用
- 程序的链接和装入
- 程序运行时的内存映像与地址空间

## 操作系统结构

- 分层、模块化
- 宏内核、微内核
- 外核

## 操作系统引导

## 虚拟机

1.1 - 操作系统概念

💡 低优先级
需要了解操作系统的概念,还是经常考一些 基础概念辨析 的选择题的,当然这种题也比较简单。

概念

操作系统 是计算机系统中的一个核心软件组件,负责管理和控制计算机硬件,为用户和应用程序提供服务。

操作系统特征

  1. 并发(Concurrence):操作系统 具备同时处理和调度多个程序的能力。
  2. 共享(Sharing):系统中的资源可供内存中多个并发的进程共同使用。
  3. 虚拟(Virtual):将物理的实物虚拟为逻辑上的对应物。
  4. 异步(Asynchronism):任务可以不按固定的顺序执行,因此同一操作的结果可能因执行的时序而异。

目标和功能

  • 作为用户语句计算机硬件系统之间的接口。
  • 作为计算机系统资源的管理者。
  • 实现了对计算机资源的抽象。

发展历程

操作系统 的发展历程经历了 手工操作、批处理操作、分时操作、实时操作 这四个阶段:

手工操作
阶段
联机批处理
脱机批处理
执行系统
批处理(单道)
多道程序系统
多道批处理
系统
分时系统
实时系统
操作系统形成
个人计算机操作系统
网络操作系统
分布式操作系统
  1. 手工操作 阶段:
    • 时间背景:计算机的早期阶段。
    • 特点:没有 操作系统,用户直接使用计算机硬件。
    • 操作方式:用户在一个时刻将自己的程序载入计算机,然后执行。执行完毕后,将结果记录并释放计算机。
    • 问题:效率低,CPU 大部分时间处于空闲状态。
  2. 批处理操作系统:
    • 单道批处理系统:
      • 特点:系统一次只执行一个任务。完成后再执行下一个。
      • 操作方式:用户的多个作业被收集到一个作业队列中,操作系统 按照队列的顺序逐一执行。
      • 问题:CPU 利用率仍然不高,因为在 I/O 操作时,CPU 仍然处于空闲状态。
    • 多道批处理系统:
      • 特点:在内存中同时加载多个作业,并使它们共享 CPU。
      • 操作方式:当一个作业进行 I/O 操作时,CPU 可以切换到另一个作业,从而提高了 CPU 的利用率。
      • 优势:提高了系统吞吐量和 CPU 利用率。
  3. 分时操作系统:
    • 时间背景:20 世纪 60 年代。
    • 特点:允许多个用户通过终端同时访问计算机。
    • 操作方式:操作系统 为每个用户提供了一个“虚拟机”的概念,使每个用户感觉自己独占了整个系统。实际上,系统在用户之间快速切换,为每个用户提供少量的 CPU 时间。
    • 优势:提高了交互性和多用户并发访问的能力。
    • 示例:Unix 操作系统在其初期就是一个分时系统。
  4. 实时操作系统:
    • 特点:系统必须在严格的时间限制内响应外部输入。
    • 分类:
      • 硬实时系统:不满足时限会导致严重的后果,如飞行控制系统。
      • 软实时系统:不满足时限可能会降低系统的性能,但不会导致灾难,如多媒体播放器。
    • 操作方式:实时 操作系统 的任务调度是基于优先级的,且通常会预先定义每个任务的执行时间。
    • 优势:满足特定应用场景的实时需求,如嵌入式系统和工业控制系统。

操作系统引导

引导流程

OSBootProcesspower_on1. 电源开机(Power-On)bios_uefi2. BIOS/UEFI 阶段- 加电自检- 读取启动设备首扇区- 包含MBR或GPTpower_on->bios_uefibootloader3. 引导加载器(Bootloader)bios_uefi->bootloadermbr_gptMBR/GPT主引导记录/GUID分区表bios_uefi->mbr_gpt读取grubGRUB- Linux系统常用- 支持多系统引导bootloader->grubwindows_bootWindows Boot Manager- Windows系统专用bootloader->windows_bootload_kernel4. 加载操作系统内核- 从存储设备加载到内存- 交给内核控制权grub->load_kernelwindows_boot->load_kernelkernel_init5. 内核初始化- 检测配置硬件资源- 设置中断- 初始化设备驱动- 建立内存管理结构load_kernel->kernel_initstorage存储设备(硬盘/SSD)load_kernel->storage从中加载memory系统内存(RAM)load_kernel->memory加载到system_services6. 启动系统服务和守护进程- 管理各种系统任务kernel_init->system_serviceshardware硬件资源- CPU- 内存- I/O设备kernel_init->hardware检测配置title操作系统引导流程与实时系统

操作系统 引导的具体流程如下:

  1. 电源开机(Power-On):
    • 当你按下计算机的电源键时,电源开始向主板和各个硬件供电。此时,系统首先进行加电自检。这个过程是由存储在主板 ROM 芯片中的基本输入/输出系统(BIOS)或统一可扩展固件接口(UEFI)固件执行的。
  2. BIOS/UEFI 阶段:
    • BIOS/UEFI 会读取启动设备的首扇区(MBR 或 GPT),其中包含 引导加载器(Bootloader)的代码。
  3. 引导加载器(Bootloader):
    • 引导加载器 是一小段程序,它的主要任务是加载 操作系统 内核。
    • 常见的 bootloader 有
      • GRUB(Grand Unified Bootloader):常用于 Linux 系统,功能强大,支持多系统引导。
      • Windows Boot Manager:Windows 操作系统的引导管理器。
    • 引导加载器 会将 操作系统 内核从存储设备(通常是硬盘)加载到内存中。
  4. 加载操作系统内核:
    • 操作系统 内核是 操作系统 的核心部分,负责管理系统的各种资源,如内存、进程、设备等。引导加载器 将内核加载到内存后,会将控制权交给内核。
  5. 内核初始化:
    • 操作系统 内核开始运行并进行自我初始化。它会检测和配置系统上的硬件资源,例如设置中断、初始化设备驱动、建立内存管理结构等。
  6. 启动系统服务和守护进程:
    • 在内核初始化后,它会启动一系列的系统进程或服务来管理各种系统任务。

1.2 - 程序运行环境

⭐ 中优先级
这一节选择题还是经常考察的,重点在于理解 用户态和内核态 以及 系统调用 这两个概念,程序的链接和装入相对考察频率比较低。

CPU 运行模式

CPU的两种 运行模式:内核模式(又称为特权模式、系统模式或超级用户模式)和 用户模式 是操作系统设计中用于隔离系统关键任务与普通任务的机制,以提高系统的安全性和稳定性。

用户
系统程序
应用程序
用户程序
依赖库
系统调用
操作系统
计算机硬件
更加抽象
更偏底层
用户模式
内核模式
注意

用户态/内核态到底是操作系统还是 cpu 的模式?

大多数现代 CPU(如 x86、ARM)都有多级 权限(ring 0~ring 3),通常:

  • Ring 0(内核态):可执行一切指令,访问所有硬件资源。
  • Ring 3(用户态):受限,只能执行普通指令,无法直接操作硬件或访问内核数据结构。

所以,内核态/用户态 本质上 是 CPU 的 运行权限级别,是硬件提供的机制。

但操作系统是这个机制的“使用者”和“管理者”,操作系统借助这套机制实现进程隔离、安全保护、系统调用等功能。

Ring 0 与 Ring 3 在 可执行指令集 和 可访问资源 上的边界可参考下图:

CPU 特权级边界:Ring 0 (内核态) ↔ Ring 3 (用户态)Ring 3用户态Ring 2Ring 1Ring 0内核态最高权限权限:内 高 → 外 低Linux/Windows 仅使用Ring 0 与 Ring 3用户态 (Ring 3) — 受限✓ 执行普通指令 (ALU, MOV, JMP)✓ 访问自己的用户空间 (代码/数据/堆栈)✓ 调用库函数 (printf, malloc)✗ 执行特权指令 (HLT, CLI, LGDT)✗ 直接访问 I/O 端口、设备寄存器✗ 修改页表、IDT、控制寄存器 (CR0..3)✗ 访问内核空间内存→ 需要服务时必须通过 “系统调用” 陷入内核内核态 (Ring 0) — 全权限✓ 执行所有指令,含特权指令✓ 直接读写任意物理/虚拟内存✓ 操作硬件、中断、I/O、DMA✓ 修改页表、调度进程、管理资源⚠ 错误后果严重 → 整机崩溃 / 安全风险→ 内核代码、设备驱动、中断处理在此→ 完成服务后通过 iret/sysret 返回用户态穿越边界:系统调用 (int 0x80 / syscall / svc) · 硬件中断 · 异常 / 陷阱用户态 → 内核态:自动切换栈、保存上下文、跳转 ISR用户进程在 Ring 3 运行系统调用陷入 Ring 0

内核模式

内核模式(Kernel Mode)是操作系统内核运行的环境,具有 最高权限,能够直接访问硬件和所有系统资源。

特点

  • 完全权限:内核模式下的代码可以直接操作硬件(如CPU、内存、I/O 设备等),执行特权指令。
  • 核心功能:操作系统内核负责管理进程、内存、文件系统和设备驱动等核心功能,这些都在内核模式下完成。
  • 上下文切换:用户模式程序通过系统调用进入内核模式,完成操作后返回用户模式。
  • 高风险:内核模式的错误可能导致系统崩溃,因此需要极高的稳定性。
特权指令

特权指令(Privileged Instruction)是指在计算机系统中只能由操作系统 内核态下 的程序执行的特殊指令。这些指令通常涉及对硬件资源或系统关键功能的直接控制,具有较高的权限,常见的特权指令包含如下类型:

  • 硬件控制:如设置中断使能、修改处理器状态寄存器。
  • 内存管理:如修改页表、设置内存保护。
  • 进程管理:如创建或终止进程、更改进程优先级。
  • I/O 操作:直接访问硬件设备或端口。
  • 系统调用相关:如切换到内核态以执行系统服务。

用户模式

用户模式(User Mode)是普通 应用程序 运行的环境,运行在 受限的权限级别。用户程序(如浏览器、文本编辑器等)通常在此模式下运行。

特点

  • 权限受限:用户模式下的程序无法直接访问硬件或核心系统资源(如内存、CPU 寄存器等),需要通过系统调用(System Call)请求操作系统服务。
  • 隔离性:每个用户程序运行在自己的地址空间,相互隔离,防止程序直接干扰其他程序或系统。
  • 安全性:由于权限受限,用户程序的错误(如崩溃)通常不会直接影响操作系统。
  • 执行方式:用户程序通过调用库函数或API间接与操作系统交互。

用户模式和内核模式是操作系统实现安全性和稳定性的核心机制。用户模式提供隔离和安全的环境运行应用程序,而内核模式负责核心资源管理和特权操作。两者通过系统调用等机制协作,共同完成计算任务。

系统调用

系统调用(system call)是运行在用户模式的应用程序与操作系统内核之间的 接口。当应用程序需要执行一些它在用户模式下不能直接完成的任务(如文件操作、网络通信、创建进程等)时,它可以通过系统调用来请求操作系统内核在内核模式下为其执行这些操作。

用户程序执行
发起系统调用
执行系统调用
从系统调用返回
用户态
内核态
①
②
③
④

系统调用的过程如上图所示,从用户态进入内核态,完成后再返回用户态:

  • 当用户程序执行系统调用(或触发中断)时,CPU 会自动 切换到内核态,这时控制权就交给了操作系统。
  • 操作系统执行完后,再通过特定指令(如iret, sysret, eret)切回用户态。

特点

  • 特权级的转换:应用程序通常在用户模式下运行,而操作系统内核在内核模式下运行。系统调用提供了从用户模式到内核模式的一种安全的转换机制,这样内核可以代表应用程序执行特权操作。
  • 系统调用的类型:常见的系统调用类型包括进程管理(如创建、终止进程)、文件操作(如打开、读取、写入、关闭文件)、网络通信、设备控制、内存管理等。
  • 性能开销:执行系统调用涉及上下文切换,从用户模式到内核模式,然后再返回。这会带来一定的性能开销。因此,频繁地进行系统调用可能会影响应用程序的性能。

举个实际的 x86 例子

  1. 用户程序执行 int 0x80(系统调用指令);
  2. CPU 自动:
    • 切换到 Ring 0(即内核态);
    • 跳转到操作系统设定的系统调用处理函数;
  3. 操作系统执行相关服务;
  4. 执行 iret 返回用户程序;
  5. CPU 自动:
    • 切回 Ring 3(即用户态);
    • 恢复用户程序继续运行。

程序的链接

程序的链接是将编译后的代码模块(通常是目标文件)和其他所需的库组合在一起,生成一个可以执行的程序或库的过程。链接过程由 链接器(linker)完成。

根据所使用的库的链接方式,链接可以分为 静态链接 和 动态链接。

  • 静态链接:
    • 当使用静态链接时,外部代码和库在链接阶段被 整合到最终的可执行文件中。这意味着,如果程序使用了某个库的函数,那么这些函数的代码会被复制到最终的二进制文件中。
    • 结果是一个较大的可执行文件,因为它包含了所有必要的代码以独立运行。
  • 动态链接:
    • 使用动态链接时,外部库不会被直接嵌入到最终的可执行文件中。相反,程序包含了对动态链接库(如 Linux 中的.so 文件或 Windows 中的.dll 文件)的引用。当程序启动时,这些库会被 动态加载 到内存中供程序使用。
    • 动态链接的库通常称为动态链接库(Dynamic Link Libraries,DLL)或共享对象(Shared Object)。

静态链接

printf.o
strlen.o
rand.o
libc.a
foo.o
bar.o
baz.o
libstrange.a
Static
Linker
main.o
main.o
printf.o
baz.o
main.o
printf.o
baz.o
prog.exe
prog.exe
拷贝到另一台机器
Static Linking

简单来说,对于 静态链接,依赖 直接作为 二进制 被打包进最后的 可执行程序 中,所以可以直接 跨机器运行(当然操作系统得一样)。

动态链接

printf.o
strlen.o
rand.o
libc.so
foo.o
bar.o
baz.o
libstrange.so
Dynamic
Linker
main.o
prog.exe
prog.exe
拷贝到另一台机器
Dynamic Linking
main.o
libc.so
libstrange.so
main.o
libc.so
libstrange.so
printf.o
strlen.o
rand.o
libc.so
libstrange.so
? ? ?

对于 动态链接,程序只保存依赖的库地址,依赖与可执行程序 分开保存,当程序执行时再去 动态地加载依赖库。对于动态链接,拷贝可执行程序到另一台机器不一定能直接运行,这需要另外一台机器也保存有相应依赖。

两者的区别如下表所示:

特点/链接方式静态链接动态链接
文件大小通常较大,因为库代码被整合到可执行文件中通常较小,只包含对库的引用
运行依赖不需要外部库文件需要相应版本的动态链接库文件
存储效率较低,每个程序都有库的一个副本较高,多个程序共享同一个库文件
更新便利性较差,更新库需要重新链接和分发程序较好,只需更新库文件
启动性能通常更快,无需加载外部库可能稍慢,需要加载外部库
跨版本兼容性较好,因为程序包含了特定版本的库代码可能出现问题,特别是当库接口发生变化时

程序的装入

程序的装入是指将程序或进程的代码和数据从磁盘加载到主存(RAM)中的过程,使其准备好被 CPU 执行。装入过程在程序执行周期中是必不可少的一部分,通常由操作系统中的 装入器(loader)完成。

绝对装入

适用于 单道程序环境。在编译时,若知道程序驻留在内存的某个位置,则编译程序将产生绝对地址的目标地址。绝对装入程序按照装入模块的地址,将程序和数据装入内存。由于程序中的逻辑地址与实际地址完全相同,因此不需要对程序和数据的地址进行修改。

另外,程序中所用的绝对地址,可在编译或汇编时给出,也可由程序员直接赋予。而通常情况下在程序中采用的是符号地址,编译或汇编时在转换为绝对地址。

可重定位装入

在 多道程序环境 下,多个目标模块的起始地址通常都从 0 开始,程序中的其他地址都是相对于起始地址的,此时应采用可重定位装入方式。根据内存的当前情况,将装入模块装入内存的适当位置。在装入时对目标程序中指令和数据地址的修改过程称为 重定位,又因为地位变换通常是在进程装入时一次完成的,故称为 静态重定位。

当一个作业装入内存时,必须给它分配要求的全部内存空间,若没有足够的内存,则无法装入。此外,作业一旦装入内存,整个运行期间就不能在内存中移动,也不能再申请内存空间。

LOAD 1, 6
Add      1,8
Store  1,10
A               
B               
LOAD 1,106
Add   1,108
Store 1,110
A               
B               
0
2
4
6
8
100
102
104
106
108
10
110
地址空间
存储空间

动态运行时装入

也称为 动态重定位。装入程序把装入模块装入内存后,并不立即把装入模块的相对地址转换为绝对地址,而是把这种地址转换推迟到程序真正要执行时进行。因此,装入内存后的所有地址都是相对地址。这种地址需要一个重定位寄存器的支持。

LOAD      500
12345
500
1000
+
LOAD      1.500
12345
0
100
500
1000
1500
地址空间
存储空间

动态重定位的优点在于可以将程序分配到不连续的存储区;在程序运行之前可以只装入部分代码即可运行,然后在程序运行期间,根据需要动态申请分配内存。

1.3 - 操作系统结构

💡 低优先级
偶尔在选择题中考一下 宏内核和微内核 以及 虚拟机 的概念。

分层和模块化

操作系统的设计和结构在历史上经历了多种不同的方法和技巧,为了增加 可维护性、灵活性 和 可扩展性。分层 和 模块化 是操作系统设计中两种主要的技术方法。

  • 分层结构:
    • 概念:在分层的操作系统中,系统被划分为多个层次或“层”,每层都为其上的层提供服务,并依赖于其下的层。
    • 优点:
      • 每层只需与其直接的上下层进行交互,简化了设计和调试。
      • 提高了灵活性,因为改变某一层的实现不会影响到其他层。
      • 有助于保护和安全性,因为较低的层(如硬件访问层)被封装起来,上层代码不能直接访问。
LayeredOScluster_os分层操作系统结构app应用程序层(用户程序)syscall系统调用接口(API)app->syscall调用filesystem文件系统管理(文件操作)syscall->filesystem文件操作process进程管理(调度、同步)syscall->process进程控制memory内存管理(虚拟内存)syscall->memory内存分配device设备管理(驱动程序)syscall->device设备访问hal硬件抽象层(HAL)filesystem->hal硬件访问process->hal硬件访问memory->hal硬件访问device->hal硬件访问hardware硬件层(CPU、内存、I/O设备)hal->hardware直接访问
  • 模块化结构:
    • 概念:模块化操作系统基于模块的概念,每个模块都有一个特定的功能,各个模块之间的交互通过定义良好的接口进行。模块化与面向对象编程中的封装和抽象概念类似。
    • 优点:
      • 易于维护和更新。如果某个模块需要更改或修复,可以独立于其他模块进行。
      • 提高了可扩展性。新的功能或模块可以相对容易地添加到系统中。
      • 增加了系统的可靠性和稳定性,因为每个模块的功能都被限定在明确的边界内。
操作系统
进程管理
磁盘管理
文件管理
进程控制
进程调度
内存分配
内存回收
磁盘管理
目录管理
操作系统模块

内核架构

微内核

微内核(microkernel)是一种最小化的内核设计,只保留最核心的功能(如线程管理、虚拟内存、进程间通信等)在内核态运行,其他功能(如文件系统、设备驱动、网络协议栈等)以用户态服务的形式运行。

  • 特点:
    • 只有最基本的服务(如基本的进程和线程管理、地址空间和 IPC)运行在 内核态。
    • 其他服务,如设备驱动、文件系统等,作为 用户空间 的独立进程运行。
    • 内核和服务间通过消息传递进行通信。
  • 优点:
    • 更高的系统稳定性。用户空间的服务(如驱动程序)如果崩溃,不会影响整个系统。
    • 更加灵活,允许在运行时更改或添加服务。
    • 更容易扩展和维护。
  • 缺点:
    • 由于需要频繁的上下文切换和消息传递,通常 性能会稍逊于宏内核。
  • 示例:IOS 是基于微内核的系统。

宏内核

宏内核(monolithic kernel)将所有操作系统功能集成在一个大的内核程序中,运行在内核态。

  • 特点:
    • 在单一的地址空间中运行大部分系统服务,如设备驱动、文件系统、网络协议等。
    • 所有的内核服务都运行在 内核态。
  • 优点:
    • 由于所有的服务都在同一个地址空间中运行,因此服务间的通信较快。
    • 传统上,宏内核系统比微内核系统性能更高。
  • 缺点:
    • 如果内核中的一个部分失败,整个系统都可能崩溃。
    • 随着功能的增加,内核可能会变得臃肿,导致维护困难。
  • 示例:传统的 UNIX 系统、Linux 都是基于宏内核的。
宏内核 vs 微内核架构对比宏内核 (Monolithic Kernel)用户空间 (User Space)应用程序系统调用接口内核空间 (Kernel Space)进程管理内存管理文件系统设备驱动网络协议IPC其他系统服务硬件层 (Hardware)微内核 (Microkernel)用户空间 (User Space)应用程序文件系统设备驱动网络服务其他服务消息传递系统调用接口微内核 (Microkernel)进程管理内存管理IPC硬件层 (Hardware)对比项宏内核微内核示例系统性能高 (直接调用)中 (消息传递)Linux vs IOS稳定性中 (单点故障)高 (故障隔离)UNIX vs QNX

虚拟机

虚拟机(Virtual Machine, VM)是一种通过软件模拟真实计算机功能的 虚拟化技术,能够在一台物理主机上创建多个独立的虚拟计算机环境。每个虚拟机都具备与真实硬件相似的运行环境,可以运行独立的操作系统和应用程序,仿佛它们运行在专属的物理设备上。这种技术广泛应用于服务器整合、测试开发、跨平台应用等场景。

虚拟机架构图物理硬件 (Physical Hardware)CPU • 内存 (RAM) • 存储设备 • 网络设备Hypervisor (虚拟机监视器)资源管理 • 隔离 • 虚拟化虚拟机 1Linux OSApp1App2Web虚拟硬件vCPU: 2核vRAM: 4GB虚拟机 2Windows OSSQLIISOffice虚拟硬件vCPU: 4核vRAM: 8GB虚拟机 3Ubuntu OSDockerK8sDB虚拟硬件vCPU: 6核vRAM: 16GB主要功能:• 资源虚拟化与分配• 多操作系统并行运行• 虚拟机间隔离保护• 硬件资源动态调度• 快照与迁移支持技术优势:• 提高硬件利用率• 降低运维成本• 简化环境部署• 增强系统安全性• 支持灾难恢复

虚拟机监视器

Hypervisor,也称为虚拟机监视器(Virtual Machine Monitor, VMM),是 虚拟化技术 的核心组件,运行在物理主机和虚拟机之间的中间软件层。它负责协调和管理多个虚拟机,使它们能够共享底层 物理硬件资源,同时保持彼此 隔离。Hypervisor 的主要职责包括:

  1. 创建与管理虚拟机:为每个虚拟机分配独立的 虚拟化硬件资源(如 CPU、内存、存储和网络)。
  2. 硬件资源抽象:通过虚拟化技术,Hypervisor 将 物理硬件资源抽象化,使每个虚拟机认为自己独占硬件资源,从而运行独立的操作系统。
  3. 隔离与安全:确保虚拟机之间的运行互不干扰,增强系统安全性。

Hypervisor 的特权级

Hypervisor 通常运行在比操作系统更高的 特权级(如硬件虚拟化支持的 VMX root mode 或 Ring -1),以便有效管理虚拟机及其资源。相比之下,虚拟机中的操作系统通常运行在较低的特权级(如 Ring 0)。这种特权级差异确保 Hypervisor 能够完全控制硬件资源并协调多个虚拟机的运行,而不会与虚拟机中的操作系统发生冲突。

注意

特权级如何理解?

特权级是处理器提供的抽象概念,现代处理器(如 x86、ARM)通过硬件支持不同的 特权级别(Privilege Levels),以区分操作系统、应用程序和更底层的管理程序的执行权限。这些级别通常通过“环”(Rings)或类似的模式划分:

  • Ring 0:操作系统内核运行的级别,具有最高权限,可以直接访问硬件资源(如 CPU、内存)。
  • Ring 1~3:用户态应用程序运行的较低权限级别,无法直接操作硬件。
  • 虚拟化扩展:现代处理器引入了更高权限的模式(如 x86 的 VMX root mode 或 ARM 的 EL2),专为虚拟机监视器(Hypervisor/VMM)设计。

2 - 进程管理

重点内容,在选择题和大题中都会考察。主要分为进程、CPU 调度、同步和互斥以及死锁四个部分,需熟练掌握。
# 进程管理

## 进程和线程

- 进程、线程概念
- 进程、线程状态和转换
- 线程的实现
- 进程、线程的组织和控制
- 进程间通信

## CPU调度和上下文切换

- 概念和目标
- 调度实现方式
- 调度算法
- 上下文及其切换机制

## 同步与互斥

- 概念
- 软件和硬件实现方法
- 锁
- 信号量
- 条件变量
- 经典同步问题

## 死锁

- 概念
- 死锁预防
- 死锁避免
- 死锁检测和解除

2.1 - 进程和线程

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

在多道程序环境下,允许多个程序并发执行,为此操作系统引入了 进程 (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 才会返回。

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

注意

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

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

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

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

下表给出了两者的对比:

项目僵尸进程(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,线程控制块)。

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

  • 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 时,callee 的 EBP 指向的物理地址的上下存储单元分别包含 caller 的返回地址以及 caller 所在栈帧的 EBP,当我们在 callee 中执行 ret 指令时,计算机可以跳转到 caller 中 call 指令的下一条并开始执行,同时 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

混合方案中的映射关系

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

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

2.2 - 处理机调度

🔥 高优先级
选择题必考内容,重点掌握 调度指标的计算 和 各种调度算法的实现细节,调度的实现 和 进程上下文切换相比之下考察比较少。

调度指标

在操作系统里,调度(Scheduling) 是把 CPU 时间合理分配给多个进程(或线程)的核心机制。调度的好坏直接决定系统的响应速度、吞吐能力以及资源利用效率。为了衡量调度策略的优劣,我们通常用一组 调度指标 来量化:

  1. 系统层面的宏观指标——关注整个计算机系统的运行状态,如 CPU 的利用率、系统整体的吞吐量等。这类指标帮助我们了解系统在整体负载下是否“忙碌”或出现瓶颈。
  2. 进程层面的微观指标——关注单个进程在调度器眼中的表现,包括它何时进入调度队列、等待多久才被分配 CPU、实际执行多长时间以及最终何时完成等。这类指标可以帮助我们比较不同调度算法(FCFS、SJF、RR、优先级调度等)对单个作业的影响。

系统层面

系统层面主要关注 CPU 利用率和吞吐量这两个指标:

系统指标含义
CPU 利用率CPU 活跃时间与总观察时间的比率。通常表示为百分比
系统吞吐量操作系统单位时间内系统完成的工作量或进程数

进程层面

从进程视角而言,进程从被创建到执行结束会有一系列时间周期作为指标:

到达时间,AT
完成时间,CT
等待时间,WT
时间线
要求服务时间,BT
周转时间,TAT
进程调度指标英文含义
到达时间AT, Arrival Time进程在何时到达调度器,即何时被提交
等待时间WT, Waiting Time进程等待了多长时间才开始执行
要求服务时间BT, Burst Time进程从开始执行到结束需要多少时间
完成时间CT, Completion Time进程何时执行完成
周转时间TAT, Turnaround Time进程从提交到完成的时间,TAT = CT - AT = WT + BT

系统调度过程

为了有效地管理和调度进程,操作系统通常采用多级调度机制。这些调度机制分为三个层次:高级调度、中级调度和初级调度。

Pull of
job in
disk
Pull of...
Ready Queue
Ready Queue
Waiting Queue
Waiting Queue
I/O
I/O
Dispatcher
Dispatcher
CPU
CPU
END
END
Long term
Scheduler
Long term...
Short term
Scheduler
Short term...
Mid-term
Scheduler
Mid-term...
Mid-term
Scheduler
Mid-term...
Text is not SVG - cannot display
  1. 高级调度(长程调度,Long-term Scheduling):
    • 功能:高级调度 主要决定哪些进程应当被加载到内存中成为一个可运行的进程。
    • 主要目标:保持内存中适当数量的进程。不要过多也不要过少。
    • 当进程首次进入系统时,它们首先被放置在磁盘的一个区域,称为作业池。高级调度器从作业池中选择进程,根据某种策略将其加载到内存中,从而使其成为一个可运行的进程。
  2. 中级调度(中程调度,Mid-term Scheduling):
    • 功能:中级调度 涉及到进程的暂停和重启。当系统的进程数超过内存容量时,中级调度器可能会将一些进程从内存移出到磁盘上(这称为交换或页面置换),从而为新的或等待的进程腾出空间。
    • 主要目标:为高级调度和初级调度器优化内存使用。
    • 中级调度器在必要时将进程从内存交换到磁盘,并在适当的时机将其交换回内存。
  3. 初级调度(短程调度,Short-term Scheduling):
    • 功能:初级调度 决定哪个进程应当被赋予 CPU 时间片,即决定下一个运行在处理器上的进程。
    • 主要目标:确保 CPU 的高效利用。
    • 它的决策频率非常高,因为在多任务环境中,每个时间片的长度可能只有几十毫秒。因此,初级调度器必须是非常快速的。

调度的实现

调度器

调度器/调度程序(scheduler):

  • 调度器 是操作系统中负责决定下一个要执行的进程或线程的部分。
  • 基于特定的调度算法(如轮转、优先级调度、短作业优先等),它决定哪个进程或线程应当获得 CPU 时间。
  • 调度器 通常分为长程、中程和短程调度器,如前文所述。

调度时机

可能引发调度的事件大致有以下几类:

  1. 当前进程主动让出 CPU:进程正常结束、异常终止,或因等待 I/O、等待信号量等原因由运行态转为阻塞态。
  2. 有进程进入就绪队列:新进程被创建,或某个阻塞进程等待的事件已完成(如 I/O 完成)而转为就绪态。
  3. 时间片用完:时钟中断到达,当前进程用完了分配给它的时间片。

其中第 1 类事件的共同点是 当前进程已经无法继续占用 CPU,因此无论采用哪种调度方式都必然发生调度;而第 2、3 类事件的共同点是 当前进程本来还可以继续运行,是否此刻切换进程,取决于系统采用的是抢占式还是非抢占式调度。

调度方式

非抢占式调度(Non-Preemptive,又称非剥夺方式):一旦把 CPU 分配给某个进程,就让它一直运行下去,直到它 自己结束或主动转为阻塞态,才把 CPU 交给下一个进程。也就是说,只有上面第 1 类事件才会触发调度:

  • 新进程创建、更高优先级的进程变为就绪 —— 不调度,新到达的进程只能在就绪队列中排队等待。
  • 时间片到期 —— 不适用(非抢占式算法本身不使用时间片)。

抢占式调度(Preemptive,又称剥夺方式):当进程正在运行时,操作系统可以强行中断它、把它放回就绪队列,再把 CPU 分配给另一个进程。除第 1 类事件外,第 2、3 类事件同样可能触发调度:

  • 新进程创建,且其优先级(或剩余运行时间等指标)优于当前进程 —— 立即抢占,当前进程由运行态回到就绪态。
  • 阻塞进程被唤醒进入就绪队列,且优于当前进程 —— 立即抢占。
  • 时间片用完 —— 必然切换(如时间片轮转)。

因此,判断某个算法是否抢占式,一个直观的检验标准是:

一个新进程到达时,正在运行的进程会不会被打断? 会,就是抢占式;不会,就是非抢占式。

事件非抢占式抢占式
当前进程结束 / 主动阻塞调度调度
新进程到达就绪队列不调度,排队等待若优于当前进程则立即抢占
阻塞进程被唤醒进入就绪队列不调度,排队等待若优于当前进程则立即抢占
时间片用完不使用时间片强制切换,当前进程排到队尾

下图以“P1 先到达、P2 后到达且优先级更高”为例,对比两种调度方式在 P2 到达这一时刻的不同处理:

t = 3:P2 到达(优先级更高)非抢占式Non-PreemptiveP1 一直运行到结束P2不调度:新进程只能排队P2 在就绪队列等待 5抢占式PreemptiveP1P2P1(恢复执行)立即调度:P1 被剥夺 CPU,运行态 → 就绪态P1 在就绪队列等待 40123456789101112tP1:AT = 0,BT = 8,低优先级P2:AT = 3,BT = 4,高优先级就绪等待

闲逛进程

闲逛进程(Idle Process)是操作系统中的一个特殊进程,当系统没有任何其他可运行的进程时,调度器会将 CPU 的控制权交给这个进程。闲逛进程的主要目的是确保在没有任务可执行的情况下,CPU 不会空转,从而防止 CPU 进入不受控制的状态。

在 Linux 操作系统中,闲逛进程的进程 ID 通常是 0,被称为 swapper 或 idle task。它是系统启动时创建的第一个进程,始终在内核态运行,确保当没有其他可调度任务时,CPU 有事情可做。

在 Windows 系统中,闲逛进程被称为 System Idle Process,同样用于在系统空闲时占据 CPU 时间,以维持系统的运行稳定。如下图所示:

system idle process

两种线程的调度

  • 内核级线程:由操作系统内核直接支持的线程。操作系统知道这些线程的存在,并可以直接进行调度。
  • 用户级线程:完全在用户空间中实现的线程,不需要内核的介入。
  • 对于 内核级线程,操作系统可以直接调度它们,并可以利用多核或多处理器的优势。
  • 对于 用户级线程,因为内核不知道它们的存在,所以内核无法直接调度它们。线程之间的上下文切换可能比 内核级线程 更快,但在多处理器系统中,它们可能无法充分利用所有的处理器。

调度算法

根据 是否可抢占 可以对调度算法进行如下分类:

  • 非抢占型调度算法:先来先服务、最短任务优先、最高响应比优先
  • 抢占型调度算法:时间片轮转、多级反馈队列
调度算法
抢占式
非抢占式
时间片轮转
多级反馈队列
优先级调度
先来先服务
最短作业优先
最高响应比优先

先来先服务

先来先服务(First-Come, First-Served,FCFS)按照进程到达的顺序 分配依次执行。先到达的进程先执行,后续进程等待直到前一个进程执行才能进一步执行。

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
21
22
23
24
到达时间
要求服务时间
0 ms
5 ms
P2
1 ms
3 ms
P3
2 ms
7 ms
3 ms
6 ms
P1
P4
Process ID
P1
P2
P3
P4

最短作业优先

最短作业优先(Shortest Job First,SJF)算法在从就绪队列中选择进程时,会 选择运行时间最短 的进程进行执行。

在题目中进程的运行时间一般都是给定的,所以 SJF 算法比较容易实现。但是在真实的系统中进程的运行时间是不确定的,所以在使用该算法时操作系统需要对进程的运行时间进行预估。

1
1
2
1
2
3
1
2
3
4
1
2
3
4
5
Jobs = [4, 3, 7, 1, 2]
waiting time
= 0 + 1 = 1
waiting time
= 1 + 2 = 3
waiting time
= 3 + 3 = 6
waiting time
= 6 + 4 = 10
waiting time
= 10 + 7 = 17
Jobs = [4, 3, 7, 1, 2]
Jobs = [4, 3, 7, 1, 2]
Jobs = [4, 3, 7, 1, 2]
Jobs = [4, 3, 7, 1, 2]

最高响应比优先

最高响应比优先(Highest Response Ratio Next,HRRN)算法从就绪队列中选择 响应比 最高的进程进行执行。

其中 响应比(Response Ratio)的计算公式如下:

其中 (Waiting Time)为进程的等待时间, (Burst Time)为进程的要求服务时间(从执行开始到结束所需时间)。

这种计算策略可以有效地避免饥饿现象,即一个进程等待了很长时间但仍没有得到执行。一个进程的等待时间越长,其 响应比 就会更大,进而优先得到执行机会。一个进程的执行时间很长,其 响应比 就会越小,会优先调度其他进程。


0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
21
22
23
24
到达时间
要求服务时间
0 ms
5 ms
P2
1 ms
3 ms
P3
2 ms
8 ms
3 ms
6 ms
P1
P4
Process ID
P1
P2
P3
P4
  • 时刻 0:只有 P1 到达,执行 P1。
  • 时刻 5:P1 执行完成。P2 的 响应比 为 (4 + 3) / 3 ≈ 2.33,P3 的 响应比 为 (3 + 8) / 8 = 1.375,P4 的 响应比 为 (2 + 6) / 6 ≈ 1.33,此时 P2 的 响应比 最大,执行 P2。
  • 时刻 8:P2 执行完成。P3 的 响应比 为 (6 + 8) / 8 = 1.75,P4 的 响应比 为 (5 + 6) / 6 ≈ 1.83。此时 P4 的 响应比 更大,执行 P4。
  • 时刻 14:P4 执行完成,只剩下 P3 了,最后执行 P3。

所以进程的执行顺序为 P1、P2、P4、P3,每个进程的时间指标如下表所示:

进程号到达时间要求服务时间完成时间周转时间等待时间
P105550
P213874
P43614115
P328232112

优先级调度

优先级调度是一种基于进程优先级的调度算法,广泛应用于操作系统中。根据是否允许中断当前正在执行的进程,优先级调度可分为 非抢占式优先级调度 和 抢占式优先级调度 两种形式。

非抢占式优先级调度

在 非抢占式优先级调度 中,调度器总是从就绪队列(等待队列)中选择优先级最高的进程执行。一旦某个进程开始执行,它将持续运行直到完成(或主动释放 CPU,例如进入等待 I/O 状态)。在此期间,即使有更高优先级的进程到达并加入就绪队列,当前进程也不会被中断,而是继续执行直到结束。

注意

在常见的操作系统和调度算法中,优先级的值越小,优先级越高。

比如对于 Unix 系统,使用 nice 值来表示优先级:

  • nice 值越低,进程获得 CPU 时间的机会越多。
抢占式优先级调度

抢占式优先级调度 旨在确保系统中任何时刻运行的进程始终是优先级最高的。当一个更高优先级的进程到达时,调度器会立即暂停当前运行的低优先级进程(将其挂起并加入就绪队列),然后将 CPU 分配给新到达的高优先级进程。

下图给出了两种优先级调度方式的实例对比,其中绿色的进程表示执行态,黄色表示就绪态:

到达时间
要求服务时间
0 ms
4 ms
P2
1 ms
3 ms
P3
2 ms
6 ms
P1
Process ID
优先级
2
1
3
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
21
22
23
24
P1
P2
P3
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
21
22
23
24
P1
P2
P3
抢占式
Preemptive
非抢占式
Non-Preemptive

时间片轮转

RR 调度

时间片轮转(Round Robin,RR) 是一种专门用于 分时系统 的调度算法。系统为每个进程分配一个固定大小的 时间片(time quantum),就绪进程按照 FIFO 顺序组织成就绪队列,CPU 按队列顺序轮流为进程分配时间片。

调度过程可以概括为:

  1. 从 就绪队列队首 取出一个进程运行。
  2. 若进程在一个时间片内 执行完毕,则进程退出,调度器从队列中选择下一个进程。
  3. 若时间片用完而进程 尚未执行完毕,则发生一次 时间片中断,该进程重新进入 就绪队列队尾,CPU 转而执行队首的下一个进程。
  4. 如果运行过程中有新的进程到达,则新进程进入 就绪队列队尾。
  5. 重复上述过程,直到所有进程执行完毕。
时间片轮转调度:就绪队列结构与调度流程顶部展示就绪队列的 FIFO 结构,包含多个等待进程;下方展示调度流程:取出队首进程运行一个时间片,判断是否执行完毕,完毕则退出,未完毕则重新进入队尾。新进程到达就绪队列(FIFO)P1P2P3P4取出运行取出队首进程运行占用一个时间片时间片内完成?否时间片到期重新进入队尾返回队列是进程执行完毕退出系统,调度下一进程

因此,RR 的核心并不是简单地“按照进程编号轮流执行”,而是:

维护一个就绪队列,每次只允许队首进程最多运行一个时间片;未完成的进程重新排到队尾。

轮转的语义

例如,若时间片为 ,某时刻就绪队列为:

则调度过程可能为:

这里的“轮转”体现为:进程运行一个时间片后,如果没有结束,就从队首移动到队尾。

需要特别注意,RR 并不意味着每个进程最终获得完全相同的 CPU 时间。它保证的是在持续存在于就绪队列中的进程之间,CPU 使用机会大致公平;如果某个进程提前结束,或者某些进程在等待 I/O 而阻塞,就不会继续占用时间片。

举个实际例子,假设时间片大小为 3,不同进程在不同时刻加入就绪队列,那么调度过程如下图所示:

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Timeline
P1 (0, 1)
P2 (0, 2)
P3 (0, 4)
P4 (0, 6)
P5 (0, 6)
P6 (5, 8)
P7 (5, 8)
P8 (5, 4)
P9 (5, 2)
P10 (5, 1)
Process (Arrival Time, Burst Time)

调度器按照轮询的方式依次遍历进程,每个进程只有执行时间片内的时间,之后便进入等待状态:

进程 (Process)到达时间 (Arrival Time)要求服务时间 (Burst Time)完成时间 (Completion Time)周转时间 (Turnaround Time)
P10111
P20233
P3042525
P4062828
P5063131
P6584035
P7584237
P8543833
P9522318
P10512419

时间片的影响

时间片 的大小会显著影响 RR 的性能:

  • 时间片过大:RR 会逐渐接近 FCFS。进程往往一次运行很长时间,交互响应性变差。
  • 时间片过小:进程切换非常频繁,虽然响应时间较好,但会产生大量 上下文切换开销,降低 CPU 的有效利用率。
  • 因此,实际系统需要选择一个合适的时间片,使 响应速度 和 上下文切换开销 之间取得平衡。

可以将其理解为:

而 越小,进程之间的切换越频繁。

多级反馈队列

多级反馈队列(Multilevel Feedback Queue)这是一种混合算法,将进程分为 多个队列,每个队列有不同的优先级和时间片大小。

高优先级队列优先调度,时间片较短,适合交互型进程;低优先级队列时间片较长,适合计算密集型任务。新进程通常进入最高优先级队列,若在时间片内未完成,则移到下一级队列;若因 I/O 等待阻塞,完成后可能返回较高优先级队列。

CPU
CPU
CPU
Level 1 Queue
(Round Robin, Quantum = 8)
Level 2 Queue
(Round Robin, Quantum = 16)
Level n Queue
(FCFS)
Finish
Finish
Finish
Not Finish
Not Finish

多级反馈队列根据进程行为调整其优先级。例如,占用 CPU 过多的进程会被降级,而频繁等待 I/O 的进程可能被提升。这种设计兼顾了快速响应、公平性和资源利用率。

上下文切换

进程的上下文是进程执行的环境。在操作系统中,它指的是一个进程在特定时间点上的系统状态,包括多种信息,这些信息使得进程在被中断后可以再次恢复并继续执行。当操作系统从一个进程切换到另一个进程时,它会保存当前进程的上下文并恢复下一个进程的上下文。这个过程被称为 上下文切换。

等待
在 PCB0 中保存 P0 的状态
从 PCB1 中恢复 P1 的状态
在 PCB0 中保存 P1 的状态
从 PCB1 中恢复 P0 的状态
系统中断或调用
Process 0
Process 1
执行
执行
执行
等待
等待

进程上下文内容

  1. 寄存器值:这包括通用寄存器、程序计数器、栈指针、状态寄存器等。它们保存了进程的当前执行位置和状态。
  2. 程序计数器:表示进程的下一个指令的位置。
  3. 虚拟内存信息:这包括进程的页表、页目录等信息,描述了进程的地址空间布局。
  4. I/O 状态信息:包括打开的文件描述符、网络连接、I/O 指针等。
  5. CPU 调度信息:例如进程优先级、计划器状态等。
  6. 资源使用情况:这可能包括该进程所使用的各种资源的跟踪信息,如内存、文件句柄等。

上下文切换流程

  1. 保存当前进程的状态:操作系统保存当前正在运行的进程的上下文。这意味着它会将当前的寄存器值、程序计数器等保存到进程的进程控制块(PCB)中。
  2. 选择下一个要执行的进程:调度器决定下一个要运行的进程。
  3. 恢复下一个进程的状态:操作系统从新进程的 PCB 中恢复其上下文信息,包括寄存器值、程序计数器等。
  4. 开始执行新进程。

2.3 - 同步和互斥

🔥 高优先级
同步和互斥是 解答题同步问题设计 的基础,也是每年必考点,重要性这里就不多赘述了,这一节的每个知识点的细节都要深入掌握。

临界区和互斥

在并发编程中,临界区(Critical Section)是指一个程序中仅允许 一个线程 或 进程 访问的代码片段。这些代码通常会访问 共享资源,比如内存、文件、数据库等。当多个线程或进程试图同时访问这些共享资源时,可能会导致数据不一致或其他并发问题。因此,需要一种机制来确保在任何时候,保证在 一个时刻只有一个线程或进程 能够访问 临界区 的代码。

这种机制就叫做 互斥:

互斥(Mutual Exclusion)是计算机科学中一种用于防止多个进程或线程同时访问共享资源或 临界区 的机制。其主要目标是避免资源竞争和数据不一致的问题。互斥保证在任何时刻,最多只有 一个线程或进程 可以访问特定的共享资源,从而确保数据的完整性。

Non Critical Section
Entry Section
Critical Section
Exit Section
Non Critical Section
Process

临界资源

临界资源 是指 一次仅允许一个进程/线程使用的共享资源。多个进程/线程可以访问同一个临界资源,但它们对临界资源的访问必须 互斥。

例如:

  • 一个进程的 全局变量 可以被该进程内的多个 线程 访问。如果多个线程可能同时修改该变量,则对该变量的访问需要互斥,此时该变量可以作为 临界资源。
  • 多个进程共享一台 打印机,由于打印机同一时刻只能服务一个进程,因此打印机是典型的 临界资源。
  • 数据库中的数据可能被多个进程/线程并发访问,但是否属于临界资源,要看对该数据的访问是否需要 互斥。

共享资源不一定是临界资源;需要互斥访问的共享资源才是临界资源。

为何需要互斥

在 处理器调度 一节我们知道,为了避免 进程饥饿,现代操作系统常使用 时间片轮转 的方式来避免一个进程连续执行过长时间。

这意味着进程可能在执行完当前的机器指令后就被操作系统中断,由 运行态 进入 就绪态,然后等待下一次调度继续执行后续指令。
这种随时可能被中断的特性决定了若没有 临界区的互斥,多线程并发执行时 结果可能会出现不一致性。

int i = 0;
while (i < 5) {
i++;
}
.L1
mov eax, 0