跳转到内容

操作系统设计/进程/信号量

来自维基教科书,开放书籍,开放世界

信号量

[编辑 | 编辑源代码]

信号量,在最基本的形式中,是一个受保护的整数变量,它可以在多处理环境中促进和限制对共享资源的访问。两种最常见的信号量类型是计数信号量二进制信号量。计数信号量表示多个资源,而二进制信号量,顾名思义,表示两种可能的状态(通常为 0 或 1;锁定或解锁)。信号量是由已故的艾兹格·迪克斯特拉发明的。

信号量可以被视为对有限数量资源的一种表示,例如餐馆的座位容量。如果一家餐馆有 50 个座位,而现在没有人在那里,则信号量将被初始化为 50。随着每个人到达餐馆,他们导致座位容量减少,因此信号量也会相应减少。当达到最大容量时,信号量将为零,并且其他人将无法进入餐馆。相反,希望的餐厅顾客必须等到有人用完资源,或者在这个比喻中,吃完饭。当一位顾客离开时,信号量会增加,资源再次可用。

信号量只能使用以下操作访问:wait()signal()wait() 在一个进程想要访问资源时调用。这相当于到达的顾客试图获得一张空桌子。如果有一张空桌子,或者信号量大于零,那么他就可以使用该资源并坐在桌边。如果没有空桌子,并且信号量为零,则该进程必须等待直到它可用。signal() 在进程使用完资源时调用,或者顾客吃完饭时调用。以下是此计数信号量(其中值可以大于 1)的实现。

wait(Semaphore s){
    while (s==0);    /* wait until s>0 */
    s=s-1;
}

signal(Semaphore s){
    s=s+1;
}

Init(Semaphore s , Int v){
    s=v;
}

从历史上看,wait() 被称为 P(代表荷兰语“Proberen”,意思是尝试),signal() 被称为 V(代表荷兰语“Verhogen”,意思是增加)。标准 Java 库改用“acquire”代表 P,“release” 代表 V

当 P 或 V 正在执行时,其他进程不能访问信号量。这是通过原子硬件和代码实现的。原子操作是不可分割的,也就是说,它可以被认为是一个整体执行的。

如果只有一种资源,则使用二进制信号量,它只能具有 0 或 1 的值。它们通常用作互斥锁。以下是使用二进制信号量实现互斥的示例。

do
{
    wait(s);
    // critical section
    signal(s);
    // remainder section
} while(1);

在此实现中,想要进入其临界区的进程必须获取二进制信号量,然后在发出完成信号之前,它将获得互斥访问权限。

例如,我们有信号量 s,以及两个想要同时进入其临界区的进程 P1 和 P2。P1 首先调用 wait(s)。s 的值减为 0,P1 进入其临界区。当 P1 处于其临界区时,P2 调用 wait(s),但由于 s 的值为零,它必须等到 P1 完成其临界区并执行 signal(s)。当 P1 调用 signal 时,s 的值增加到 1,然后 P2 可以继续在其临界区执行(再次减少信号量)。互斥访问权限是通过确保一次只能有一个进程处于其临界区来实现的。

如上面的示例所示,等待信号量的进程必须不断检查信号量是否不为零。在真正的多编程系统中(其中多个进程通常共享单个 CPU),这种持续循环显然是一个问题。这称为忙等待,它浪费 CPU 周期。当信号量执行此操作时,它被称为自旋锁

为了避免忙等待,信号量可以使用一个与之关联的进程队列,这些进程正在等待信号量,允许信号量阻塞进程,然后在信号量增加时唤醒它。操作系统可以提供block() 系统调用,它会挂起调用它的进程,以及wakeup(P <Process>) 系统调用,它会恢复被阻塞进程 P 的执行。如果一个进程对一个值为零的信号量调用wait(),则该进程将被添加到信号量的队列中,然后被阻塞。该进程的状态将切换到等待状态,并将控制权转移到 CPU 调度程序,CPU 调度程序将选择另一个进程来执行。当另一个进程通过调用signal() 来增加信号量时,并且队列中有任务,则会从队列中取出一个任务并恢复它。

在一个稍微修改的实现中,信号量的值可以小于零。当一个进程执行wait() 时,信号量计数会自动递减。负值的幅度将决定有多少个进程在等待信号量。

wait(Semaphore s){
    s=s-1;
    if (s<0) {
        // add process to queue
        block();
    }
}

signal(Semaphore s){
    s=s+1;
    if (s<=0) {
        // remove process p from queue
        wakeup(p);
    }
}

Init(Semaphore s , Int v){
    s=v;
}
华夏公益教科书