【问题标题】:Why is a monitor implemented in terms of semaphores this way?为什么以这种方式根据信号量实现监视器?
【发布时间】:2018-04-05 19:09:33
【问题描述】:

我无法根据操作系统概念中的信号量来理解监视器的实现

5.8.3 使用信号量实现监视器

我们现在考虑监控机制的可能实现 使用信号量。

为每个监视器提供一个信号量互斥锁(初始化为 1)。一种 进程必须在进入监视器之前执行 wait(mutex) 并且必须 离开监视器后执行信号(互斥)。

由于信令进程必须等待恢复的进程离开或等待,因此引入了一个额外的信号量 next, 初始化为0。信令进程可以使用next挂起 他们自己。还提供了一个整数变量next_count 来计数 next 上挂起的进程数。因此,每个外部 函数F 被替换为

wait(mutex);
...
body of F
...
if (next count > 0)
    signal(next);
else
    signal(mutex);

确保监视器内的互斥。

我们现在也可以描述条件变量是如何实现的了。 对于每个条件x,我们引入一个信号量x_sem 和一个 整数变量x_count,都初始化为0。操作x.wait()现在可以实现为

x_count++;
if (next_count > 0)
    signal(next);
else
    signal(mutex);
wait(x sem);
x_count--;

操作x.signal()可以实现为

if (x_count > 0) {
    next_count++;
    signal(x_sem);
    wait(next);
    next_count--;
}

引入信号量next 和在next 上挂起的进程计数next_count 是什么意思?

为什么x.wait()x.signal() 是这样实现的?

谢谢。

【问题讨论】:

    标签: operating-system synchronization computer-science semaphore monitor


    【解决方案1】:

    -------注意-------

    WAIT()SIGNAL() 表示对监视器方法的调用
    wait()signal() 表示对信号量方法的调用,在下面的解释中。

    -------笔记结束-------

    我认为如果你从一个具体的例子来思考会更容易。但在此之前,让我们先尝试了解什么是显示器。正如书中所解释的,monitor 是一种抽象数据类型,这意味着它不是可用于实例化变量的真实类型。相反,它就像一个包含一些规则和指南的规范,基于这些规则和指南,不同的语言可以为进程同步提供支持。

    信号量是作为一种基于软件的解决方案引入的,用于通过基于硬件的方法(如 TestAndSet() 或 Swap())实现同步。即使使用信号量,程序员也必须确保他们以正确的顺序和正确地调用 wait() 和 signal() 方法。因此,引入了一个名为 monitors 的抽象规范,将所有这些与同步相关的东西封装为一个原语,因此在监视器内执行的任何进程都将确保这些方法(信号量等待和信号) 调用。

    使用监视器,所有共享变量和函数(使用共享变量)都被放入监视器结构中,当调用这些函数中的任何一个时,监视器实现会确保共享资源受到互斥和任何问题的保护同步

    现在有了监视器,与信号量或其他同步技术不同,我们不仅仅处理关键部分的一部分,而是根据不同的功能处理其中的许多部分。此外,我们还拥有可在这些函数中访问的共享变量。对于监视器中的每个不同函数,为了确保只执行其中一个函数并且没有其他进程在任何函数上执行,我们可以使用名为 mutex 的全局信号量。

    考虑使用下面的监视器解决哲学家就餐问题的示例。

    monitor dining_philopher
    {
         enum {THINKING, HUNGRY, EATING} state[5];
         condition self[5];
    
         void pickup(int i) {
             state[i] = HUNGRY;
             test(i);
    
             if (state[i] != EATING)
                 self[i].WAIT();
         }
    
         void putdown(int i) {
             state[i] = THINKING;
             test((i + 4) % 5);
             test((i + 1) % 5);
         }
    
         void test(int i) {
             if (
                  (state[(i + 4) % 5] != EATING) &&
                  (state[i] == HUNGRY) &&
                  (state[(i + 1) % 5] != EATING)) 
             {
                      state[i] = EATING;
                      self[i].SIGNAL();
             }
         }
    
         initialization code() {
              for (int i = 0; i < 5; i++)
                  state[i] = THINKING;
              }
         }
    }
    

    理想情况下,进程如何调用这些函数的顺序如下:

    DiningPhilosophers.pickup(i);
    ...
      // do somework
    ...
    DiningPhilosophers.putdown(i);
    

    现在,当一个进程在 pickup() 方法中执行时,另一个进程可能会尝试调用 putdown() (甚至是pickup) 方法。为了确保互斥,我们必须确保在任何给定时间只有一个进程在监视器内运行。因此,为了处理这些情况,我们有一个全局信号量 mutex,它封装了所有可调用的 (pickup & putdown) 方法。所以这两个方法会实现如下:

         void pickup(int i) {
             // wait(mutex);
    
             state[i] = HUNGRY;
             test(i);
    
             if (state[i] != EATING)
                 self[i].WAIT();
    
             // signal(mutex);
         }
    
         void putdown(int i) {
             // wait(mutex);
    
             state[i] = THINKING;
             test((i + 4) % 5);
             test((i + 1) % 5);
    
             // signal(mutex);
         }
    

    现在只有一个进程能够以任何方法在监视器内执行。现在,通过此设置,如果进程 P1 已执行 pickup() (但尚未 放下 筷子) 然后处理 P2 (比如相邻的用餐者) 尝试 pickup():因为他/她的筷子 (共享资源) 正在使用中,它必须 wait() 才能使用。让我们看看监视器的条件变量的 WAITSIGNAL 实现:

    WAIT(){
        x_count++;
    
        if (next_count > 0)
            signal(next);
        else
            signal(mutex);
    
        wait(x_sem);
        x_count--;
    }
    
    SIGNAL() {
        if (x_count > 0) {
            next_count++;
            signal(x_sem);
            wait(next);
            next_count--;
        }
    }
    

    条件变量的 WAIT 实现不同于信号量的实现,因为它必须提供更多功能,例如 通过释放 mutex 允许其他进程调用监视器的功能(在等待的同时) 全局信号量。因此,当 P2pickup() 方法调用 WAIT 时,它将调用 signal(mutex) 允许其他进程调用监视器方法并在特定于条件的信号量上调用 wait(x_sem)。现在,P2 在这里被屏蔽了。此外,变量 x_count 跟踪条件变量 (self) 上等待的进程数。

    所以当 P1 调用 putdown() 时,这将通过 test() 方法调用 SIGNAL。在 SIGNAL 内部,当 P1 在它持有的筷子上调用 signal(x_sem) 时,它必须做一件事。它必须确保只有一个进程在监视器内运行。如果它只调用 signal(x_sem) 那么从那时起 P1P2 都将开始在监视器内做事。为了防止这个P1,它在松开筷子后会阻塞自己,直到P2结束。为了阻止自己,它使用信号量next。为了通知 P2 或其他进程有人被阻止,它使用计数器 next_count

    所以,现在 P2 会拿到筷子,在它退出 pickup() 方法之前它必须释放等待 P1P1 strong>P2 完成。所以现在,我们必须将pickup()方法(以及监视器的所有功能)改成如下:

         void pickup(int i) {
             // wait(mutex);
    
             state[i] = HUNGRY;
             test(i);
    
             if (state[i] != EATING)
                 self[i].WAIT();
    
             /**************
             if (next_count > 0)
                 signal(next);
             else
                 signal(mutex);
             **************/
         }
    
         void putdown(int i) {
             // wait(mutex);
    
             state[i] = THINKING;
             test((i + 4) % 5);
             test((i + 1) % 5);
    
             /**************
             if (next_count > 0)
                 signal(next);
             else
                 signal(mutex);
             **************/
         }
    

    所以现在,在任何进程退出监视器的功能之前,它会检查是否有任何等待进程,如果有则释放它们而不是 mutex 全局信号量。最后一个这样的等待进程将释放 mutex 信号量,允许新进程进入监控功能。

    我知道它很长,但我花了一些时间才理解并想把它写下来。我会尽快将其发布在博客上。

    如果有任何错误,请告诉我。

    最好的,
    沙比尔

    【讨论】:

      【解决方案2】:

      我同意它令人困惑。

      让我们先了解第一段代码:

      // if you are the only process on the queue just take the monitor and invoke the function F.
      wait(mutex);
      ...
      body of F
      ...
      if (next_count > 0)
          // if some process already waiting to take the monitor you signal the "next" semaphore and let it take the monitor.
          signal(next);
      else
          // otherwise you signal the "mutex" semaphore so if some process requested the monitor later.
          signal(mutex);
      

      回到你的问题:

      接下来引入信号量的原因和计数是什么 next_count 的进程在下一个平均值时暂停?

      假设您有一个正在执行 I/O 的进程,它需要被阻塞直到它完成。所以你让等待在就绪队列中的其他进程获取监视器并调用函数 F。

      next_count 仅用于跟踪队列中等待的进程。

      next 信号量上挂起的进程是在条件变量上发出等待的进程,因此它将被挂起,直到其他一些 进程(下一个进程)将其唤醒并恢复工作。

      为什么 x.wait() 和 x.signal() 是这样实现的?

      让我们使用 x.wait()

      semaphore x_sem; // (initially = 0)
      int x_count = 0; // number of process waiting on condition (x)
      
      
      /*
       * This is used to indicate that some process is issuing a wait on the 
       * condition x, so in case some process has sent a signal x.signal()
       * without no process is waiting on condition x the signal will be lost signal (has no effect).
      */
      x_count++;
      
      /*
       *  if there is some process waiting on the ready queue,
       *  signal(next) will increase the semaphore internal counter so other processes can take the monitor.
       */
      if (next_count > 0)
          signal(next);
      /*
       *  Otherwise, no process is waiting.
       *  signal(mutex) will release the mutex.
       */
      else
          signal(mutex);
      /*
       * now the process that called x.wait() will be blocked until other process will release (signal) the
       * x_sem semaphore: signal(x_sem)
       */
      wait(x_sem);
      // process is back from blocking.
      // we are done, decrease x_count.
      x_count--;
      

      现在让我们使用 x.signal()

      // if there are processes waiting on condition x.
      if (x_count > 0) {
          // increase the next count as new blocked process has entered the queue (the one who called x.wait()). remember (wait(x_sem))
          next_count++;
          // release x_sem so the process waiting on x condition resume.
          signal(x_sem);
          // wait until next process is done.
          wait(next);
          // we are done.
          next_count--;
      }
      

      如果您有任何问题,请发表评论。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-04-01
        • 1970-01-01
        • 2018-11-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-11-12
        相关资源
        最近更新 更多