MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

死锁检测技术与恢复策略

2021-09-173.7k 阅读

死锁检测技术

死锁检测的基本概念

在操作系统的并发环境中,死锁是指两个或多个进程因竞争资源而相互等待,导致这些进程都无法继续执行的一种僵持状态。死锁检测技术旨在识别系统中是否存在死锁情况,并找出涉及死锁的进程和资源。

死锁检测基于对系统资源分配图的分析。系统资源分配图(Resource Allocation Graph, RAG)是一个有向图,它由一组节点和一组边组成。节点分为两类:进程节点(用圆圈表示)和资源节点(用方框表示,方框内的小点表示该资源的多个实例)。边也分为两类:请求边(从进程节点指向资源节点,表示进程请求该资源)和分配边(从资源节点指向进程节点,表示该资源已分配给该进程)。

例如,假设有两个进程 P1 和 P2,两种资源 R1 和 R2。P1 已经分配到 R1,并且请求 R2;P2 已经分配到 R2,并且请求 R1。在资源分配图中,就会有从 P1 到 R2 的请求边,从 R2 到 P2 的分配边,从 P2 到 R1 的请求边,以及从 R1 到 P1 的分配边。这种情况下,系统可能发生死锁。

死锁检测算法

  1. 资源分配图算法(RAG 算法)
    • 算法步骤
      • 首先,寻找一个没有请求边的进程节点(即可运行进程)。如果找到了这样的进程,就移除该进程的所有分配边,将这些资源释放回系统,同时移除该进程节点及其所有相关边。
      • 重复上述步骤,尝试移除所有进程节点。如果最后所有进程节点都能被移除,说明系统没有死锁;否则,剩下的进程节点和它们之间的边就构成了死锁环,这些进程处于死锁状态。
    • 代码示例(Python 模拟)
# 假设资源和进程用数字表示
# 例如,resources = [2, 1] 表示有 2 个 R1 资源和 1 个 R2 资源
# allocation = {0: [1, 0], 1: [0, 1]} 表示进程 0 分配到 1 个 R1 资源,进程 1 分配到 1 个 R2 资源
# request = {0: [0, 1], 1: [1, 0]} 表示进程 0 请求 1 个 R2 资源,进程 1 请求 1 个 R1 资源
def is_deadlock(resources, allocation, request):
    available = resources.copy()
    work = available.copy()
    finish = [False] * len(allocation)
    while True:
        found = False
        for i in range(len(allocation)):
            if not finish[i] and all(request[i][j] <= work[j] for j in range(len(resources))):
                for j in range(len(resources)):
                    work[j] += allocation[i][j]
                finish[i] = True
                found = True
        if not found:
            break
    return not all(finish)
  1. 银行家算法(Banker's Algorithm)与死锁检测的关系 银行家算法主要用于死锁预防,它通过提前判断资源分配是否安全来避免死锁。然而,银行家算法的一些概念和数据结构也可用于死锁检测。

银行家算法维护的数据结构包括: - Available:表示系统中每种资源的可用数量。 - Max:表示每个进程对每种资源的最大需求。 - Allocation:表示每个进程当前已分配到的每种资源的数量。 - Need:表示每个进程还需要的每种资源的数量,Need[i][j] = Max[i][j] - Allocation[i][j]

在死锁检测中,可以基于这些数据结构进行类似的分析。例如,检查是否存在一组进程,它们的Need总和大于系统的Available,并且这些进程之间存在资源请求的循环依赖,以此判断是否存在死锁。

死锁检测的时机

  1. 定时检测 系统定期运行死锁检测算法,例如每隔一定时间间隔(如 10 秒)进行一次检测。这种方式的优点是简单直接,能在一定时间内发现死锁。缺点是如果时间间隔设置过长,死锁可能长时间存在,影响系统性能;如果设置过短,会增加系统开销,因为死锁检测算法本身需要消耗一定的计算资源。

  2. 资源请求时检测 每当有进程请求资源时,系统运行死锁检测算法。这种方式能及时发现死锁,因为每次资源请求都可能导致死锁的发生。但它的缺点是频繁检测会大大增加系统开销,特别是在资源请求频繁的系统中。

  3. 基于资源利用率检测 当系统资源利用率达到一定阈值时,运行死锁检测算法。例如,当 CPU 利用率连续 5 分钟超过 90%,并且内存使用率也超过 80%时,可能存在死锁导致资源无法有效释放和利用,此时进行死锁检测。这种方式结合了系统资源的实际使用情况,能在系统出现异常资源使用情况时进行检测,减少不必要的检测次数,但如何合理设置阈值是一个挑战。

死锁恢复策略

终止进程

  1. 终止单个进程
    • 选择策略:选择死锁进程集中的一个进程进行终止。通常会选择优先级较低、已经运行时间较短或者占用资源较少的进程。例如,在一个多用户系统中,后台进程的优先级可能低于前台交互进程,此时可以优先终止后台进程。
    • 实现方式:在操作系统中,可以通过向该进程发送终止信号(如在 Unix - like 系统中使用 kill 命令发送 SIGTERM 信号)。进程接收到终止信号后,会执行一些清理操作(如关闭打开的文件、释放内存等),然后退出。
    • 代码示例(Unix - like 系统 C 语言)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

int main() {
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程
        while (1) {
            printf("Child process is running...\n");
            sleep(1);
        }
    } else if (pid > 0) {
        // 父进程
        sleep(3);
        printf("Parent process is killing child process...\n");
        kill(pid, SIGTERM);
    } else {
        perror("fork");
        return 1;
    }
    return 0;
}
  1. 终止多个进程
    • 选择策略:可以选择终止死锁进程集中的多个进程,以尽快打破死锁。一种策略是根据进程的资源占用情况,按照从多到少的顺序终止进程,直到死锁解除。另一种策略是根据进程的依赖关系,终止那些处于死锁环关键位置的进程,例如那些连接多个死锁进程的进程。
    • 实现方式:与终止单个进程类似,通过向多个进程发送终止信号。可以遍历死锁进程列表,逐个发送终止信号。

资源剥夺

  1. 从死锁进程剥夺资源
    • 选择策略:选择死锁进程中占用资源相对不关键的进程,剥夺其部分或全部资源。例如,在一个数据库系统中,如果一个事务(可看作一个进程)持有锁资源导致死锁,并且该事务处理的是一些非关键数据(如日志记录等),可以剥夺其锁资源。
    • 实现方式:操作系统需要具备资源回收和重新分配的机制。对于一些资源(如内存),可以通过操作系统的内存管理模块,强制回收进程占用的内存页。对于设备资源,操作系统可以暂停进程对设备的使用,将设备重新分配给其他进程。
    • 代码示例(模拟内存资源剥夺,Python)
# 假设进程用字典表示,如 process = {'name': 'P1','memory': 100} 表示进程 P1 占用 100 字节内存
def deprive_memory(processes, target_process):
    for process in processes:
        if process['name'] == target_process['name']:
            released_memory = process['memory']
            process['memory'] = 0
            return released_memory
    return 0
  1. 向其他进程重新分配剥夺的资源
    • 分配策略:优先将剥夺的资源分配给那些等待这些资源且能够继续执行的进程。可以根据进程的优先级、等待时间等因素进行排序,选择最适合的进程进行资源分配。例如,一个等待资源时间最长的进程可能具有较高的分配优先级。
    • 实现方式:操作系统维护一个等待资源的进程队列,当有资源可用时,按照分配策略从队列中选择进程进行资源分配。例如,在 Linux 内核中,对于 CPU 资源的分配,会根据进程的优先级和等待时间等因素进行调度。

回滚进程

  1. 进程回滚的概念 进程回滚是指将进程恢复到之前的某个状态,使得进程可以重新执行,避免死锁。这就好比是给进程“时光倒流”,让它回到还没有陷入死锁的状态。例如,在数据库事务中,如果一个事务因为锁竞争导致死锁,可以将该事务回滚到开始状态,然后重新执行。
  2. 回滚点的设置
    • 设置策略:回滚点可以在进程执行的关键位置设置,例如在每次资源分配操作之前。这样,当死锁发生时,可以回滚到最近的资源分配操作之前,重新尝试分配资源。另外,也可以根据时间间隔设置回滚点,例如每隔一定时间(如 1 分钟)设置一个回滚点。
    • 实现方式:可以通过记录进程的执行状态和资源分配情况来实现回滚点。对于内存中的数据,可以使用日志记录修改操作,当需要回滚时,根据日志撤销这些修改。对于文件操作,可以使用文件系统的快照功能,回滚到之前的文件状态。
  3. 回滚与重新执行
    • 回滚操作:当死锁检测到某个进程需要回滚时,根据设置的回滚点信息,将进程的状态、资源分配等恢复到回滚点时的状态。例如,恢复进程的内存数据、关闭未完成的文件操作等。
    • 重新执行:进程回滚后,从回滚点处重新开始执行。操作系统需要重新调度该进程,使其能够获取所需资源并继续执行。在重新执行过程中,由于死锁已经被检测到,系统可以采取一些措施(如改变资源分配顺序)来避免再次发生死锁。

结合多种恢复策略

  1. 策略结合的优势 单一的死锁恢复策略可能存在局限性。例如,终止进程可能会导致正在进行的工作丢失,资源剥夺可能会影响进程的正常执行,回滚进程可能需要额外的系统开销来记录和恢复状态。结合多种恢复策略可以取长补短。例如,先尝试资源剥夺,如果无法解除死锁,再考虑终止部分进程,这样可以在尽量减少工作丢失的情况下解决死锁问题。
  2. 结合策略的实施
    • 决策逻辑:根据死锁的具体情况和系统的运行状态来决定使用哪种策略组合。例如,如果死锁涉及的进程较少且资源占用相对集中,可以先尝试资源剥夺;如果死锁导致系统资源严重耗尽,可能需要直接终止部分进程。
    • 实现方式:可以编写一个决策模块,该模块根据死锁检测的结果(如死锁进程列表、资源分配情况等)和系统当前的状态信息(如 CPU 使用率、内存使用率等),选择合适的恢复策略组合并执行。例如,在一个大型分布式系统中,通过中央控制节点收集各个节点的死锁信息和系统状态,然后根据预先设定的决策逻辑,向各个节点发送恢复策略执行指令。

在实际的操作系统设计中,死锁检测技术与恢复策略是保障系统稳定运行的重要手段。不同的系统需要根据自身的特点和应用场景,选择合适的死锁检测时机和恢复策略,以在最小化系统开销的同时,有效地处理死锁问题,确保系统的并发性能和可靠性。