首页 > 编程知识 正文

信号量的实现和应用,什么是信号量机制

时间:2023-05-04 21:59:44 阅读:113419 作者:636

互斥锁,我们刚才讨论过,通常应该是最简单的同步工具。 本节介绍具有互斥锁之类功能的更好的工具,但提供了更高级的方法来帮助流程同步活动。

一个信号s是整数变量,除初始化外,只能通过wait (和signal ) )两个标准原子操作访问。

操作wait ()最初被称为p )荷兰语的proberen,测试)。

操作signal ()最初被称为v ) (荷兰语的verhogen,增加);

wait () :可以定义如下

等待(s ) {

wile(s=0) )。

; //busy wait

s----;

}

signal (可定义为:

signal(s ) {

s;

}

wait ) )和signal ) )的操作中,请不要更改信号量整数值。 也就是说,没有其他过程可以在一个进程改变信号的大小时同时改变同一信号的值。 另外,对于wait(s ),也不能中断s整数值的测试(S 0)和修正(S-- )。

首先,让我们来看看信号量的使用方法。

使用信号量

操作系统通常区分计数信号量和二进制信号量。 计数信号量的值不受限制,但二进制信号量的值只有0或1。 因此,二进制信号量类似于互斥锁。 实际上,在没有提供排他锁定的系统中,可以使用二进制信号量来提供排他锁定。

计数信号量可用于控制对具有多个实例的资源的访问。 信号量的初始值是可用资源数。 如果进程需要使用资源,则对该信号执行等待()操作(减少信号计数)。 当进程释放资源时,必须对该信号执行signal ()操作。 增加信号的计数。 信号量计数为0表示所有资源都在使用中。 然后,需要使用资源的进程将被阻止,直到计数大于0。

也可以使用信号量解决各种同步问题。 例如,假设有两个并发进程: P1有语句S1,P2有语句S2。 假设仅在S1运行后才要求运行S2。 这很容易通过P1和P2共享相同的信号量synch并将其初始化为0来实现。

在进程P1中,插入语句。

S1;

信号(同步);

在进程P2中,插入语句。

等待(同步;

S2;

由于synch初始化为0,因此P2仅在P1调用signal(synch )后(即S1语句执行后)执行S2。

信号量的实现

请回想一下。 互斥锁的实现有很忙的等待时间。 刚才提到的信号操作wait ()和signal () )也存在同样的问题。

为了克服繁忙等待的需要,可以按这种方式改变信号操作wait ()和signal ) )的定义。 也就是说,进程操作wait ) ),如果信号大小不正确,必须等待。 但是这个过程并不忙,而是屏蔽自己。 阻止操作将进程置于与信号相关联的等待队列中,并将进程状态切换为等待状态。 然后,控制进入CPU调度程序并选择运行其他进程。

等待信号s并阻止的进程应该在其他进程执行操作signal ()之后重新运行。 通过操作wakeup ()重新运行进程,将进程从等待状态更改为就绪状态。 但是,该进程将添加到就绪队列中。 (根据CPU调度算法的不同,CPU可能无法从正在运行的进程切换到新的就绪进程。 )

为了实现这样定义的信号量,如下定义信号量。

类型定义结构{

int value;

结构流程*列表;

} semaphore;

每个信号量都有整数value和进程链接列表list。 如果进程需要等待信号量,它将被添加到进程链表中。 使用signal (,等待,从进程链接列表中取出进程并唤醒。

信号操作wait ) )可以定义如下:

等待(semaphore * s ) {

s-value----;

if(s-value0) {

添加到列表流程到列表;

块();

}

}

信号操作signal ) )可以定义如下:

signal(semaphore*s ) {

S-value;

if (s -值=0) {

移除a process p from s-list;

weup(p;

}

}

操作block ) )挂起调用它的进程。 操作唤醒(p )以重新开始执行阻止进程p。 这两个操作都由操作系统作为基本的系统调用提供。

另外,这样实现的信号量的值可以是负值,在具有忙碌待机的信号量中通常确定

义下,信号量的值不能为负。如果信号量的值为负,那么它的绝对值就是等待它的进程数。出现这种情况源于,在实现操作 wait() 时互换了递减和测试的顺序。

通过每个进程控制块 PCB 的一个链接字段,等待进程的链表可以轻松实现。每个信号量包括一个整数和一个 PCB 链表指针。向链表中增加和删除进程以便确保有限等待的一种方法采用 FIFO 队列,这里的信号量包括队列的首指针和尾指针。然而,一般来说,链表可以使用任何排队策略。信号量的正确使用不依赖于信号量链表的特定排队策略。

关键的是,信号量操作应原子执行。我们应保证:对同一信号量,没有两个进程可以同时执行操作 wait() 和 signal()。这是一个临界区问题。

对于单处理器环境,在执行操作 wait() 和 signal() 时,可以简单禁止中断。这种方案在单处理器环境下能工作,这是因为一旦中断被禁用,不同进程指令不会交织在一起。只有当前运行进程一直执行,直到中断 被重新启用并且调度程序重新获得控制。

对于多处理器环境,每个处理器的中断都应被禁止;否则,在不同处理器上不同的运行进程可能会以任意不同方式一起交织执行。每个处理器中断的禁止会很困难,也会严重影响性能。因此,SMP 系统应提供其他加锁技术,如 compare_and__swap() 或自旋锁,以确保 wait() 与 signal() 原子执行。

重要的是,对于这里定义的操作 wait() 和 signal(),我们并没有完全取消忙等待。我们只是将忙等待从进入区移到临界区。此外,我们将忙等待限制在操作 wait() 和 signal() 的临界区内,这些区比较短(如经合理编码,它们不会超过 10 条指令)。因此,临界区几乎不被占用,忙等待很少发生,而且所需时间很短。对于应用程序,存在一种完全不同的情况,即临界区可能很长(数分钟或数小时)或几乎总是被占用。在这种情况下,忙等待极为低效。

死锁与饥饿

具有等待队列的信号量实现可能导致这样的情况:两个或多个进程无限等待一个事件,而该事件只能由这些等待进程之一来产生。当出现这样的状态时,这些进程就为死锁。

为了说明起见,假设有一个系统,它有两个进程 P0 和 P1,每个访问共享信号量 S 和 Q,这两个信号量的初值均为 1:

P0

P1

wait(S);

wait(Q);

wait(Q);

wait(S);

signal(S);

signal(Q);

signal(Q);

signal(S);

假设 P0 执行 wait(S),接着 P1 执行 wait(Q)。当 P0 执行 wait(Q) 时,它必须等待直到 P1 执行 signal(Q)。类似地,当 P1 执行 wait(S) 时,它必须等待直到 P0 执行 signal(S)。由于这两个操作 signal() 都不能执行,这样 P0 和 P1 就死锁了。

我们说一组进程处于死锁状态:组内的每个进程都等待一个事件,而该事件只可能由组内的另一个进程产生。这里主要关心的事件是资源的获取和释放。然而,其他类型的事件也能导致死锁。

与死锁相关的另一个问题是无限阻塞或饥饿,即进程无限等待信号量。如果对与信号量有关的链表按 LIFO 顺序来增加和删除进程,那么可能发生无限阻塞。

优先级的反转

如果一个较高优先级的进程需要读取或修改内核数据,而且这个内核数据当前正被较低优先级的进程访问(这种串联方式可涉及更多进程),那么就会出现一个调度挑战。由于内核数据通常是用锁保护的,较高优先级的进程将不得不等待较低优先级的进程用完资源。如果较低优先级的进程被较高优先级的进程抢占,那么情况变得更加复杂。

比如,假设有三个进程 L、M 和 H,它们的优先级顺序为 L

这个问题称为优先级反转。它只出现在具有两个以上优先级的系统中,因此一个解决方案是只有两个优先级。然而,这对于大多数通用操作系统是不够的。通常,这些系统在解决问题时采用优先级继承协议。

根据这个协议,所有正在访问资源的进程获得需要访问它的更高优先级进程的优先级,直到它们用完了有关资源为止。当它们用完时,它们的优先级恢复到原始值。在上面的示例中,优先级继承协议将允许进程 L 临时继承进程 H 的优先级,从而防止进程 M 抢占执行。当进程 L 用完资源 R 时,它将放弃继承的进程 H 的优先级,以采用原来的优先级。因为资源 R 现在可用,进程 H(而不是进程 M)会接下来运行。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。