0%

操作系统之死锁(二):资源

1. 资源概述

  • 大部分的死锁都和资源有关,在进程对设备、文件具有独占性(排他性)时会产生死锁。我们把这类需要排他性使用的对象称为资源(resource)。资源主要分为可抢占资源和不可抢占资源

2. 可抢占资源和不可抢占资源

  • 资源主要有可抢占资源和不可抢占资源。可抢占资源(preemptable resource) 可以从拥有它的进程中抢占而不会造成其他影响,内存就是一种可抢占性资源,任何进程都能够抢先获得内存的使用权

  • 不可抢占资源(nonpreemtable resource) 指的是除非引起错误或者异常,否则进程无法抢占指定资源,这种不可抢占的资源比如有光盘,在进程执行调度的过程中,其他进程是不能得到该资源的

  • 死锁与不可抢占资源有关,虽然抢占式资源也会造成死锁,不过这种情况的解决办法通常是在进程之间重新分配资源来化解。所以,我们的重点自然就会放在了不可抢占资源上

  • 下面给出了使用资源所需事件的抽象顺序

    • 如果在请求时资源不存在,请求进程就会强制等待。在某些操作系统中,当请求资源失败时进程会自动阻塞,当自资源可以获取时进程会自动唤醒。在另外一些操作系统中,请求资源失败并显示错误代码,然后等待进程等待一会儿再继续重试
    • 请求资源失败的进程会陷入一种请求资源、休眠、再请求资源的循环中。此类进程虽然没有阻塞,但是处于从目的和结果考虑,这类进程和阻塞差不多,因为这类进程并没有做任何有用的工作
    • 请求资源的这个过程是很依赖操作系统的。在一些系统中,一个 request 系统调用用来允许进程访问资源。在一些系统中,操作系统对资源的认知是它是一种特殊文件,在任何同一时刻只能被一个进程打开和占用。资源通过 open 命令进行打开。如果文件已经正在使用,那么这个调用者会阻塞直到当前的占用文件的进程关闭文件为止

3. 资源获取

  • 对于一些数据库系统中的记录这类资源来说,应该由用户进程来对其进行管理。有一种管理方式是使用信号量(semaphore) 。这些信号量会初始化为 1 。互斥锁也能够起到相同的作用

    • 这里说一下什么是互斥锁(Mutexes):在计算机程序中,互斥对象(mutex) 是一个程序对象,它允许多个程序共享同一资源,例如文件访问权限,但并不是同时访问
    • 需要锁定资源的线程都必须在使用资源时将互斥锁与其他线程绑定(进行加锁)。当不再需要数据或线程结束时,互斥锁设置为解锁
  • 下面是一个伪代码,这部分代码说明了信号量的资源获取、资源释放等操作,如下所示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    typedef int semaphore;
    semaphore aResource;

    void processA(void){

    down(&aResource);
    useResource();
    up(&aResource);

    }
  • 上面显示了一个进程资源获取和释放的过程,但是一般情况下会存在多个资源同时获取锁的情景,这样该如何处理?如下所示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    typedef int semaphore;
    semaphore aResource;
    semaphore bResource;

    void processA(void){

    down(&aResource);
    down(&bResource);
    useAResource();
    useBResource();
    up(&aResource);
    up(&bResource);

    }
  • 对于单个进程来说,并不需要加锁,因为不存在和这个进程的竞争条件。所以单进条件下程序能够完好运行。现在让我们考虑两个进程的情况,A 和 B ,还存在两个资源。如下所示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    typedef int semaphore;
    semaphore aResource;
    semaphore bResource;

    void processA(void){

    down(&aResource);
    down(&bResource);
    useBothResource();
    up(&bResource);
    up(&aResource);

    }

    void processB(void){

    down(&aResource);
    down(&bResource);
    useBothResource();
    up(&bResource);
    up(&aResource);

    }
  • 在上述代码中,两个进程以相同的顺序访问资源。在这段代码中,一个进程在另一个进程之前获取资源,如果另外一个进程想在第一个进程释放之前获取资源,那么它会由于资源的加锁而阻塞,直到该资源可用为止。在下面这段代码中,有一些变化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    typedef int semaphore;
    semaphore aResource;
    semaphore bResource;

    void processA(void){

    down(&aResource);
    down(&bResource);
    useBothResource();
    up(&bResource);
    up(&aResource);

    }

    void processB(void){

    down(&bResource); // 变化的代码
    down(&aResource); // 变化的代码
    useBothResource();
    up(&aResource); // 变化的代码
    up(&bResource); // 变化的代码

    }
  • 这种情况就不同了,可能会发生同时获取两个资源并有效地阻塞另一个过程,直到完成为止。也就是说,可能会发生进程 A 获取资源 A 的同时进程 B 获取资源 B 的情况。然后每个进程在尝试获取另一个资源时被阻塞

  • 在这里我们会发现一个简单的获取资源顺序的问题就会造成死锁,所以死锁是很容易发生的,所以下面我们就对死锁做一个详细的认识和介绍

-------------------- 本文结束感谢您的阅读 --------------------