操作系统进程同步与互斥详解:软件实现、硬件实现与信号量机制

📅 2026/7/2 9:49:18
操作系统进程同步与互斥详解:软件实现、硬件实现与信号量机制
在多道程序设计环境中系统内同时存在多个并发执行的进程它们共享系统中的硬件、软件资源也会相互协作完成复杂任务。如果不对进程的资源访问和执行顺序加以约束就会出现数据不一致、资源竞争冲突、进程饥饿甚至死锁等问题最终导致系统运行异常。本文要解决的核心问题是如何通过合理的机制保证多进程并发场景下临界资源的安全共享以及协作进程的执行顺序正确。全文按照: 概念定义→设计准则→软件实现→硬件实现→信号量机制→经典问题实战→方案对比一、进程同步与互斥的核心概念1.1 互斥互斥的核心特征是排他性访问无固定执行顺序。当多个进程竞争同一个临界资源时同一时刻只能有一个进程占用该资源进程之间没有严格的先后执行要求谁先申请到资源谁先使用。常见的互斥场景包括公共卫生间坑位、办公室共享打印机、多进程共享的全局余额变量balance等。从本质上看互斥是进程间的间接制约关系源于对临界资源的竞争属于竞争关系。1.2 同步同步的核心特征是顺序性执行有严格先后依赖。多个进程为了完成协作任务存在明确的执行先后顺序后续操作必须等待前置操作完成后才能执行执行顺序不能颠倒或提前。常见的同步场景包括施工队必须先打地基再盖楼、生产者生产产品后消费者才能消费、道口栏杆放行后车辆才能通行等。从本质上看同步是进程间的直接制约关系源于进程之间的任务协作属于合作关系。为了直观区分二者我们通过下图进行对比1.3 临界资源与临界区1临界资源临界资源指一个时间段内只允许一个进程使用的资源既可以是打印机、IO 设备等硬件资源也可以是全局变量、共享缓冲区、文件数据等软件资源。 临界资源的核心特点可以总结为多进程共享 单进程独占所有进程必须以互斥的方式共享这类资源。2临界区访问的四个阶段一个进程访问临界资源的完整代码逻辑可以划分为 4 个标准部分void process() { while (TRUE) { // 1. 进入区检查是否可以进入临界区若可以则设置访问标志 entry_section(); // 2. 临界区真正访问临界资源的代码段是互斥保护的核心 critical_section(); // 3. 退出区清除访问标志释放临界资源 exit_section(); // 4. 剩余区进程中与临界资源无关的其他业务代码 remainder_section(); } }二、进程同步机制的设计准则所有实现进程互斥与同步的机制都需要遵循统一的设计准则以此保证机制的正确性、公平性与高效性。2.1 四大基本准则空闲让进当临界资源处于空闲状态时应当立即允许申请资源的进程进入临界区最大化提升资源利用率。忙则等待当临界资源正在被访问时其他申请该资源的进程必须进入等待状态严格保证临界资源的互斥访问。有限等待进程必须在有限的时间内进入临界区不能无限期等待下去避免出现进程 饥饿 现象。让权等待如果进程当前无法进入临界区应当立即释放 CPU 资源进入阻塞状态避免占用 CPU 空转提升 CPU 整体利用率。2.2 死等与忙等的核心区别初学者很容易混淆 死等 与 忙等 两种不良等待状态二者在 CPU 占用、等待性质上有本质区别具体对比如下表格等待类型别名核心表现核心特点死等阻塞等待 / Dead Waiting进程无法获得资源被无限期阻塞没有机制保证其能在有限时间内获取资源最终陷入永久等待不占用 CPU被动等待等待无上限忙等自旋等待 / Busy Waiting进程不释放 CPU持续循环检查条件是否满足直到条件成立才继续执行不会因条件不满足进入阻塞态占用 CPU主动轮询可能无限等待简单来说死等是 睡过去等不知道什么时候能醒忙等是 站在原地反复看一直占用资源。三、临界区互斥的软件实现方案早期操作系统通过纯软件算法在用户态实现临界区的互斥访问。这类方案主要针对双进程场景通过共享标志位协调进程执行顺序是理解同步互斥逻辑的基础。3.1 单标志法核心思想设置一个共享全局变量turn作为标志位用来表示当前允许哪个进程进入临界区。只有当turn等于自身进程编号时进程才能进入临界区退出临界区后将turn修改为对方进程编号。存在问题严重违背 空闲让进 准则。即使临界资源完全空闲只要turn不属于当前进程该进程也无法进入资源利用率极低。3.2 双标志先检查法核心思想设置布尔型数组flag[]flag[i]表示进程 i 想要进入临界区的意愿。每个进程进入临界区前先检查是否有其他进程想进入如果没有就将自身的flag设为true然后访问临界区。伪代码实现// 进程i的执行逻辑 while (flag[j] TRUE); // 第一步检查对方是否想进入临界区 flag[i] TRUE; // 第二步标记自己想要进入 critical_section(); // 访问临界资源 flag[i] FALSE; // 退出临界区取消标记存在问题违背 忙则等待 准则两个进程可能同时通过检查步骤同时进入临界区破坏互斥性同时存在忙等问题违背让权等待。3.3 双标志后检查法核心思想针对先检查法的并发漏洞改为 先上锁后检查 的逻辑 —— 进程先将自身的flag设为true表示想要进入再检查对方进程的标志如果对方也想进入就循环等待。伪代码实现// 进程i的执行逻辑 flag[i] TRUE; // 第一步先标记自己想进入临界区 while (flag[j] TRUE); // 第二步再检查对方是否想进入 critical_section(); // 访问临界资源 flag[i] FALSE; // 退出临界区取消标记存在问题违背 空闲让进 与 有限等待 准则。两个进程可能同时上锁互相等待谁都无法进入临界区造成资源空闲但无进程访问的情况同时可能导致进程饥饿。3.4 Peterson 算法核心思想综合双标志先检查和后检查的优点在双标志的基础上增加一个turn变量表示 谦让权。进程进入前先标记自身意愿再主动谦让对方最后检查对方是否想进入且当前轮到对方如果是则等待否则进入临界区。伪代码实现// 进程i的执行逻辑 flag[i] TRUE; // 标记自己想要进入临界区 turn j; // 主动谦让让对方进程优先进入 while (flag[j] TRUE turn j); // 对方想进且轮到对方则循环等待 critical_section(); // 访问临界资源 flag[i] FALSE; // 退出临界区取消标记算法评价完美遵循 空闲让进 忙则等待 有限等待 三大准则解决了互斥破坏和进程饥饿问题但依然违背 让权等待 准则存在忙等问题。四种软件方案的演进逻辑与缺陷可以通过下图直观梳理3.5 软件实现的整体局限性始终无法解决 忙等 问题CPU 利用率低执行效率差依赖对进程执行顺序的假设鲁棒性差复杂并发场景下容易出现逻辑漏洞绝大多数方案针对双进程设计扩展性差难以直接应用于多进程场景依赖底层运行环境缺乏通用性不同硬件架构下执行表现不一致。四、临界区互斥的硬件实现方案针对软件方案的固有缺陷硬件层面提供了更简单、高效的互斥实现核心思路是保证检查和修改操作的原子性—— 操作要么全部执行要么全部不执行中间不会被进程调度打断。4.1 关中断核心思想进程进入临界区前先关闭 CPU 的外部中断保证临界区代码执行过程中不会被调度中断退出临界区后再开启中断。实现逻辑disable_interrupts(); // 关中断禁止进程调度 critical_section(); // 访问临界区全程不会被打断 enable_interrupts(); // 开中断恢复进程调度能力优缺点优点实现逻辑最简单能彻底保证临界区代码的原子性缺点操作权限高仅内核态可使用用户态无法调用关中断时间过长会影响系统中断响应不适用于多处理机系统。4.2 TestAndSetTS指令TestAndSet 是一条硬件原子指令能一次性完成 读取标志→判断→修改标志 的完整操作执行过程不可被打断。指令底层逻辑伪代码bool TestAndSet(bool *lock) { bool old_value *lock; *lock TRUE; return old_value; }互斥实现逻辑while (TestAndSet(lock)); // 循环申请锁直到锁处于空闲状态 critical_section(); // 访问临界资源 lock FALSE; // 访问完成释放锁优缺点优点实现简单适用于多处理机环境无需复杂的软件逻辑协调缺点不满足 让权等待 准则暂时无法进入临界区的进程会循环执行 TS 指令陷入忙等。4.3 Swap 指令Swap 指令也是一条硬件原子指令作用是原子性地交换两个变量的值核心逻辑与 TS 指令一致只是实现形式不同。指令底层逻辑伪代码void Swap(bool *a, bool *b) { bool temp *a; *a *b; *b temp; }互斥实现逻辑bool key TRUE; do { Swap(lock, key); // 原子交换锁变量和key变量 } while (key TRUE); // 若key为TRUE说明锁被占用继续循环等待 critical_section(); // 访问临界资源 lock FALSE; // 访问完成释放锁本质说明Swap 是标准机器指令本身具备原子性CPU 会在执行完这条指令后再检查是否需要切换进程因此整个交换操作不会被打断。五、信号量机制核心原理信号量机制由荷兰科学家 Dijkstra 提出是操作系统中最经典、应用最广泛的同步互斥工具。它完美解决了 忙等 问题同时能灵活支持各种复杂的同步互斥场景是现代操作系统并发控制的核心基础。5.1 信号量与 PV 操作定义信号量本质上是一个表示系统中空闲临界资源数量的变量其初始值≥0。信号量一旦完成初始化就只能通过两个标准原语操作修改其值原语的执行过程不可被中断。两个标准原语分别是 P 操作wait 操作和 V 操作signal 操作P 操作申请资源执行一次 P 操作信号量值减 1。若减 1 后信号量≥0说明申请成功进程继续执行若减 1 后信号量 0说明没有空闲资源进程自我阻塞进入该信号量的等待队列。V 操作释放资源执行一次 V 操作信号量值加 1。若加 1 后信号量≤0说明等待队列中还有阻塞的进程就唤醒队列中的一个进程让它从阻塞态转为就绪态。5.2 整型信号量整型信号量是最基础的实现形式用一个整数表示资源数量P 操作通过循环等待实现// P操作申请资源 void wait(int S) { while (S 0); // 资源不足循环等待 S S - 1; // 申请一个资源 } // V操作释放资源 void signal(int S) { S S 1; // 释放一个资源 }核心缺点进程等待时持续循环判断条件存在忙等问题不满足让权等待准则。5.3 记录型信号量记录型信号量在整型信号量的基础上增加了阻塞等待队列彻底解决了忙等问题是实际操作系统中使用的标准版本。它包含两个核心成员value资源数量计数器list阻塞进程的等待队列链表。// 记录型信号量结构体定义 typedef struct { int value; // 资源数量计数器 struct process *list; // 阻塞进程等待队列 } semaphore; // P操作申请资源 void wait(semaphore *S) { S-value--; if (S-value 0) { block(S-list); // 资源不足进程自我阻塞主动释放CPU } } // V操作释放资源 void signal(semaphore *S) { S-value; if (S-value 0) { wakeup(S-list); // 仍有进程在等待唤醒队列中的一个阻塞进程 } }value 值的物理意义若value 0表示当前系统中空闲资源的数量若value 0表示资源刚好被全部用完且没有进程在等待若value 0其绝对值表示当前阻塞等待队列中的进程总数量。记录型信号量的 P、V 操作完整执行流程可以通过下图直观理解5.4 用信号量实现进程互斥实现互斥逻辑非常简单设置一个互斥信号量mutex初始值为 1代表临界资源初始可用。在临界区前执行 P 操作临界区后执行 V 操作即可。semaphore mutex 1; // 互斥信号量初值为1表示资源初始可用 // 进程A void process_A() { wait(mutex); // 申请互斥访问权限 critical_section(); // 临界区代码 signal(mutex); // 释放互斥访问权限 } // 进程B void process_B() { wait(mutex); critical_section(); signal(mutex); }5.5 用信号量实现进程同步进程同步的核心口诀是前操作后 V后操作前 P。设置一个同步信号量初始值为 0在先执行的操作之后执行 V 操作在后执行的操作之前执行 P 操作。例如场景要求进程 A 的代码段 S1 执行完之后进程 B 的代码段 S2 才能执行。semaphore sync 0; // 同步信号量初值为0 // 进程A先执行的进程 void process_A() { S1; // 前置操作代码 signal(sync); // 通知后置进程前置操作已完成 } // 进程B后执行的进程 void process_B() { wait(sync); // 等待前置操作完成 S2; // 后置操作代码 }六、经典问题实战生产者 - 消费者模型生产者 - 消费者问题是操作系统同步互斥模块最经典的实战模型也是期末考试、考研、面试的高频考点。我们通过标准化解题步骤完成该问题的实现。6.1 通用解题四步法解决所有进程同步互斥问题都可以遵循以下固定步骤思路清晰不易出错判断问题类型区分是纯互斥、纯同步还是同步 互斥混合。如果是混合类型优先分析同步关系再处理互斥关系。定义信号量与初值根据同步点和互斥资源确定需要的信号量并设置正确的初始值。梳理运行主体明确场景中有几类进程站在每个进程的角度分析它需要申请什么资源、执行什么操作、释放什么资源。排布 PV 操作按照 同步 P 在前、互斥 P 在后 的原则在对应位置插入 P、V 操作检查逻辑是否通顺。6.2 问题描述与分析问题描述系统中有一组生产者进程和一组消费者进程它们共享一个大小为 n、初始为空的缓冲区。生产者每次生产一个产品放入缓冲区消费者每次从缓冲区取出一个产品并消费所有进程不能同时访问缓冲区。问题分析类型判断同步 互斥混合问题同步关系缓冲区为空时消费者不能取产品必须等生产者放入缓冲区满时生产者不能放产品必须等消费者取出。互斥关系缓冲区是临界资源所有进程不能同时访问缓冲区。信号量定义empty空缓冲区数量初值为 n代表生产者还能放入多少个产品full满缓冲区数量初值为 0代表缓冲区中已有多少个产品mutex缓冲区互斥信号量初值为 1保证同一时间只有一个进程访问缓冲区。生产者 - 消费者模型的整体运行逻辑如下图所示6.3 完整 PV 操作实现// 信号量初始化 semaphore empty n; // 空缓冲区数量初值为n semaphore full 0; // 满缓冲区数量初值为0 semaphore mutex 1; // 缓冲区互斥信号量初值为1 // 生产者进程 void producer() { while (TRUE) { produce_item(); // 生产一个产品 wait(empty); // 申请空缓冲区同步P操作 wait(mutex); // 申请缓冲区访问权限互斥P操作 put_item_into_buffer(); // 将产品放入缓冲区 signal(mutex); // 释放缓冲区访问权限互斥V操作 signal(full); // 增加满缓冲区数量同步V操作 } } // 消费者进程 void consumer() { while (TRUE) { wait(full); // 申请满缓冲区同步P操作 wait(mutex); // 申请缓冲区访问权限互斥P操作 get_item_from_buffer(); // 从缓冲区取出产品 signal(mutex); // 释放缓冲区访问权限互斥V操作 signal(empty); // 增加空缓冲区数量同步V操作 consume_item(); // 消费产品 } }6.4 易错点避坑指南P 操作顺序不能颠倒必须先执行同步 P 操作再执行互斥 P 操作。如果先执行互斥 P 再执行同步 P可能会出现死锁生产者占有缓冲区却等待空缓冲区消费者占有缓冲区却等待满缓冲区。信号量初值不能写错互斥信号量初值固定为 1同步信号量初值根据初始资源数量设置。V 操作顺序无影响同步 V 和互斥 V 的顺序可以互换不会影响逻辑正确性。P 和 V 必须成对出现有一个 P 操作就必须对应一个 V 操作避免资源泄漏或死锁。七、各类实现方案的对比与适用边界我们将软件实现、硬件实现、信号量机制三类方案从多个维度进行对比明确各自的适用场景与限制条件方案类型代表实现互斥性有限等待让权等待适用场景核心限制软件实现Peterson 算法满足满足不满足双进程简单场景、教学演示存在忙等扩展性差仅适用于双进程硬件实现TS 指令、Swap 指令满足不满足不满足多处理机系统、内核态简单互斥存在忙等仅能实现互斥无法支持复杂同步信号量机制记录型信号量满足满足满足所有多进程同步互斥场景、复杂协作模型原语需硬件 / 内核支持使用不当易引发死锁边界说明软件互斥方案仅适用于理论学习与双进程简单场景实际工业系统中不会单独使用纯软件方案硬件原子指令是现代锁机制的底层基础用户态开发通常不会直接调用而是封装在编程语言的锁机制中信号量机制是通用解决方案但需要开发者正确设置信号量初值与 PV 操作位置使用不当会导致死锁、饥饿等问题。从技术演进的角度看现代并发编程中的互斥锁、条件变量、分布式锁等机制底层逻辑都源于本章的同步互斥理论。掌握这些基础原理有助于理解高级并发工具的底层实现。八、本章总结与思考本文围绕 多进程并发下的资源安全共享与执行顺序控制 这一核心问题从概念定义、设计准则、软件实现、硬件实现、信号量机制、经典问题六个层面进行了完整的分析与推导形成了完整的知识闭环。核心结论总结如下互斥与同步是进程并发控制的两大核心互斥解决资源竞争的排他性问题同步解决进程协作的顺序性问题同步机制的四大准则是衡量方案优劣的核心标准其中 让权等待 是提升 CPU 利用率的关键纯软件方案无法彻底解决忙等问题硬件方案通过原子操作保证互斥性信号量机制则同时满足所有设计准则是最通用的解决方案解决经典同步问题的核心是先分析同步关系、再处理互斥关系严格遵循 同步 P 在前互斥 P 在后 的原则。互动思考你在学习进程同步与互斥的过程中遇到过哪些容易混淆的概念或者易错的 PV 操作场景欢迎在评论区留言讨论我们一起交流避坑经验。写作说明本文内容源自笔者近期系统学习操作系统进程管理时的个人笔记。为了提升阅读体验笔者借助AI工具对原始笔记进行了逻辑梳理、表格优化和排版润色但所有核心知识点均经过笔者逐一核对与校正。如有疏漏欢迎指正交流。