操作系统互斥全攻略:从屏蔽中断到TSL指令

屏蔽中断 (Disabling Interrupts)

  • 核心概念: 一种低级同步原语,主要用于单处理器 (Uniprocessor / Single-CPU) 系统。通过在执行临界区代码前暂时禁止 CPU 响应外部硬件中断,保证一小段代码(通常是操作关键内核数据结构)的原子性执行
  • 工作原理:
    1. 进入临界区前: 执行特殊 CPU 指令(如 CLI - Clear Interrupt Flag on x86)关闭中断响应
    2. 执行临界区代码: CPU 此时不会响应任何外部硬件中断(如定时器中断、磁盘 I/O 完成中断、键盘中断等)。当前执行流(内核线程或进程)独占 CPU,不会被中断处理程序抢占
    3. 退出临界区后: 执行另一条指令(如 STI - Set Interrupt Flag on x86)重新开启中断响应。CPU 会处理在屏蔽期间发生但被延迟的中断。
  • 核心目的与作用:
    • 保证短操作的原子性: 防止当前执行的关键代码被中断处理程序打断(中断处理程序可能访问相同的内核数据结构,导致数据不一致)。
    • 防止内核抢占: 在非抢占式内核中,是实现互斥的一种简单方式(阻止可能导致内核线程切换的时钟中断)。
    • 实现其他同步原语的基础: 更高级别的同步机制(如自旋锁)在单处理器上的实现,其底层通常依赖于屏蔽中断(或结合屏蔽中断)来保证锁操作本身的原子性。
  • 关键特性和注意事项:
    1. 仅适用于单处理器系统: 在多处理器 (SMP) 系统上,屏蔽中断只能阻止当前 CPU 响应中断。其他 CPU 上的线程仍然可以并发访问共享数据。因此,在 SMP 系统中,屏蔽中断不能单独提供互斥,必须与其他机制(如自旋锁)结合使用。
    2. 临界区必须非常短: 屏蔽中断会延迟所有中断响应,包括重要的系统中断(如时钟中断、磁盘中断)。如果屏蔽时间过长:
      • 会导致丢失中断(硬件事件未被处理)。
      • 严重影响系统的实时性和**响应性如鼠标键盘无响应)。
      • 可能导致硬件故障(某些设备需要及时响应中断)。
      • 因此,只应用于保护执行时间极短的代码段(通常是几条指令)。
    3. 不能用于用户态程序: 屏蔽中断是特权指令,只能在 CPU 的内核模式 (Ring 0) 下执行。
  • 典型应用场景 (在现代内核中):
    1. 保护每个 CPU 的私有数据: 在 SMP 系统中,修改每个 CPU 独有的数据(如当前运行的任务指针)时,只需要防止当前 CPU 被中断打断即可。
    2. 实现自旋锁: 在单处理器内核中,自旋锁的实现通常是在获取锁时屏蔽中断(防止中断处理程序获取同一个锁导致死锁),释放锁时再开启中断。
    3. 非常短的内核操作: 修改几个全局标志位、操作几个全局指针等,且操作必须原子完成,时间极短。
    4. 中断处理程序内部: 中断处理程序本身默认屏蔽(或部分屏蔽)其他中断。有时需要在处理程序内部进一步精细控制中断屏蔽状态。

锁变量 (Lock Variable - Naive Attempt)

  • 核心思想: 使用一个共享的整型变量(例如 lock)作为标志位。lock = 0 表示临界区空闲(解锁),lock = 1 表示临界区已被占用(加锁)。

  • 设想的工作流程:
    1. 进程进入临界区前检查 lock
    2. 如果 lock == 0(空闲),进程将 lock 设置为 1(加锁),然后进入临界区。
    3. 如果 lock == 1(已锁),进程等待(忙等或阻塞),直到 lock 变为 0
    4. 进程退出临界区时,将 lock 设置为 0(解锁)。
  • 致命缺陷: 检查和设置 lock 的操作不是原子的。考虑以下场景:
    1. 进程 A 检查 lock,发现它是 0(空闲)。
    2. 在进程 A 将 lock 设置为 1 之前,发生时钟中断或进程切换。
    3. 进程 B 被调度运行,它也检查 lock,同样发现它是 0(因为 A 还没来得及设置)。
    4. 进程 B 将 lock 设置为 1 并进入临界区。
    5. 进程 A 恢复运行,它仍然认为 lock0(基于之前检查的结果),于是也将 lock 设置为 1 并进入临界区。
  • 结果: 两个进程(A 和 B)同时进入了临界区,违反了互斥原则。这就是一个典型的竞态条件 (Race Condition)
  • 结论: 简单的锁变量方案无法实现互斥,因为检查和设置锁状态的操作本身需要是原子的,而软件层面的普通读写操作无法保证这一点。

严格轮询法 (Strict Alternation / Spinlock with Busy Waiting)

  • 核心思想: 两个进程(例如进程 0 和进程 1)通过一个共享变量 turn 严格交替进入临界区。
  • 代码示例 (伪代码):
    // Process 0
    while (TRUE) {
        while (turn != 0) {} // 忙等,直到轮到进程 0
        critical_region();   // 进入临界区
        turn = 1;            // 将轮次交给进程 1
        noncritical_region(); // 非临界区
    }
    
    // Process 1
    while (TRUE) {
        while (turn != 1) {} // 忙等,直到轮到进程 1
        critical_region();   // 进入临界区
        turn = 0;            // 将轮次交给进程 0
        noncritical_region(); // 非临界区
    }
    
  • 优点: 简单,保证了互斥(因为 turn 的值强制了顺序)。
  • 致命缺点:
    • 忙等待 (Busy Waiting): 进程在等待 turn 改变时持续占用 CPU 循环检查,浪费 CPU 资源。
    • 违反“前进条件”: 进程的执行速度被强制绑定。如果进程 0 在非临界区执行时间很长,即使进程 1 早已准备好且临界区空闲,进程 1 也必须等待进程 0 执行完非临界区并显式地将 turn 设置为 1 后才能进入。这违反了“位于临界区外的进程不应被其他非临界区的进程阻塞”的原则。

Peterson 解法 (Peterson's Solution)

  • 核心思想: 一种软件解决方案,结合了锁变量警告变量的概念,用于解决两个进程的互斥问题,无需硬件支持,也避免了严格轮询的缺点。它使用两个共享变量:
    • interested[2]: 布尔数组,interested[i] = TRUE 表示进程 i 想进入临界区。
    • turn: 指示哪个进程有优先权进入临界区(如果两者都想进入)。
    • 由于这是硬件层面上的操作所以一下代码是汇编,理解思路即可
  • 代码示例 (伪代码):
    #define FALSE 0
    #define TRUE 1
    #define N 2 // 进程数量
    
    int turn;                  // 谁的轮次
    int interested[N];         // 初始化为 0 (FALSE)
    
    void enter_region(int process) { // process 是 0 或 1
        int other = 1 - process;     // 另一个进程的编号
        interested[process] = TRUE; // 表明本进程想进入
        turn = process;             // 设置轮次标志(表示本进程愿意让出优先权)
        while (turn == process && interested[other] == TRUE) {
            // 空循环(忙等),直到条件不满足
        }
    }
    
    void leave_region(int process) {
        interested[process] = FALSE; // 表明本进程离开临界区
    }
    
  • 工作原理 (enter_region):
    1. 进程 process 设置 interested[process] = TRUE,表明它想进入临界区。
    2. 进程 process 设置 turn = process。这相当于礼貌地说:“现在轮到我了,但如果另一个进程也想进,它可以先走”。
    3. 进程 process 进入忙等循环:while (turn == process && interested[other] == TRUE)
      • 这个条件的意思是:“如果现在轮到我(turn == process),但同时另一个进程也表示它想进入临界区(interested[other] == TRUE),那么我就等待”。
      • 只要另一个进程在临界区(interested[other] == TRUE)并且 turn 没变,或者另一个进程也想进且 turn 是对方(turn != process),本进程就会等待。
    4. 当条件不满足时(要么 turn 不是自己了,要么另一个进程不想进了),进程退出循环,进入临界区。
  • 工作原理 (leave_region):
    1. 进程 process 设置 interested[process] = FALSE,表明它已离开临界区。
    2. 调用TSL时,发起该指令的CPU会锁定内存总线,这意味着其他 CPU在这段时间内将无法访问内存总线
  • 关键点:
    • 通过 interested 数组和 turn 变量的组合,解决了锁变量方案的原子性问题。
    • 避免了严格轮询法的强制交替,允许一个进程连续进入临界区(如果另一个进程不想进)。
    • 仍然存在忙等待问题。
    • 仅适用于两个进程
    • 在现代 CPU(多核、乱序执行、缓存)上可能由于内存可见性问题失效,需要内存屏障(Memory Barriers)保证正确性。但在理论教学和单处理器顺序一致性模型下是有效的。
  • 总结: Peterson 解法是一个经典的、纯软件的、解决两个进程互斥问题的算法。它证明了互斥可以在没有硬件原子指令的情况下实现,但存在忙等待和扩展性(仅限两进程)问题。

TSL 指令 (Test and Set Lock Instruction)

  • 核心概念: 一种由硬件直接提供原子操作 (Atomic Operation) 指令,用于解决临界区问题。它是现代同步原语(如自旋锁)的基础。
  • 指令功能 (TSL RX, LOCK):
    • 原子地 (Atomically) 执行以下两个操作:
      1. 将内存位置 LOCK当前值读入寄存器 RX
      2. 将内存位置 LOCK值设置为一个非零值(通常是 1)。
    • 整个操作在硬件层面保证是不可分割的(在执行期间不会被中断,其他 CPU 也无法访问 LOCK)。
  • 硬件支持: CPU 在执行 TSL 指令时,会锁定内存总线 (Lock Memory Bus),确保在此期间其他 CPU 无法访问该内存地址(或任何内存地址),从而保证原子性。
  • 实现互斥锁 (自旋锁):
    ; 假设 LOCK 是全局锁变量,初始化为 0 (解锁)
    enter_region:
        TSL REGISTER, LOCK   ; 原子操作:复制LOCK到REGISTER,设置LOCK=1
        CMP REGISTER, #0     ; 检查REGISTER(即LOCK的旧值)是否为0?
        JNE enter_region     ; 如果不是0(锁已被设置),循环等待(忙等)
        RET                  ; 如果是0(成功获取锁),返回进入临界区
    
    leave_region:
        MOVE LOCK, #0        ; 释放锁:将LOCK设置为0
        RET                  ; 返回
    
  • 工作原理 (enter_region):
    1. 执行 TSL REGISTER, LOCK
      • 原子地读取 LOCK 的旧值到 REGISTER
      • 原子地将 LOCK 设置为 1(加锁状态)。
    2. 检查 REGISTER(即 LOCK旧值):
      • 如果 REGISTER == 0:表示调用 TSL 之前锁是空闲的(解锁状态)。进程成功获取锁,退出循环,进入临界区。
      • 如果 REGISTER != 0(通常是 1):表示调用 TSL 之前锁已被占用(加锁状态)。进程进入忙等循环,不断重试 TSL 指令。
  • 工作原理 (leave_region):
    1. LOCK 设置为 0(解锁状态)。
  • 优点:
    • 简单高效: 实现简单(几条指令),在锁持有时间短且竞争不激烈时效率高。
    • 适用于多处理器 (SMP): 硬件总线锁定机制保证了在 SMP 系统上的正确性。
    • 避免上下文切换: 忙等避免了进程/线程切换的开销(在等待时间短时有利)。
  • 缺点:
    • 忙等待 (Busy Waiting): 在锁被长时间持有时,等待的进程会持续占用 CPU 循环执行 TSL 和检查,浪费 CPU 资源(尤其在单处理器系统上更严重,持有锁的进程可能因等待 CPU 而无法释放锁)。
    • 优先级反转问题: 如果忙等线程优先级高,而持有锁的线程优先级低,高优先级线程会忙等,阻止低优先级线程运行释放锁,导致死锁(需要优先级继承或天花板协议解决)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值