Linux被动等待条件更新

时间:2017-04-18 17:42:57

标签: c linux synchronization

我正在尝试创建一个模拟幼儿中心的代码。在这个中心,一个成年人可以照顾最多三个孩子。必须始终满足这一条件。成人和儿童是随机生成的过程,并且在计划参数中设置了儿童和成人的数量。只有当里面有足够的成年人时,孩子才能进入,只有当有足够的其他成年人照顾孩子时,成人才能离开。如果没有,则应实施被动等待,直到条件允许儿童/成人离开/进入。

#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <string.h>
#include <semaphore.h>
#include <sys/mman.h>
#include <sys/ipc.h>
#include <sys/shm.h>

 void load_init_values();
 void handler(int, int, char*);

 pid_t adults, children;
 int adult_max_t, child_max_t, adc, chc, amt, cmt, shm_a_id, shm_c_id;
 int *adults_inside, *children_inside;
 sem_t *adults_sem, *children_sem, *entry;


int main(int argc, char *argv[])
{

 srand(time(NULL));
 setbuf(stdout,NULL);

 adc=atoi(argv[1]);
 chc=atoi(argv[2]);
 adult_max_t=atoi(argv[3]);
 child_max_t=atoi(argv[4]);
 amt=atoi(argv[5]);
 cmt=atoi(argv[6]);

 int pid=0;

 load_init_values();
 adults = fork();

 if (adults == 0)
 {

   for(int i=0; i<=adc-1; i++)
   {
      int adult_t = (random() % (adult_max_t + 1));
      usleep(adult_t*1000);

       adults = fork();

       // Adult process is created here
       if(adults == 0)
       {
        handler(getpid(), amt, "adult");
       }
       else
       {
       }
   }
 }

 else
 {
   children = fork();

   if (children == 0)
   {

     for(int i=0; i<=chc-1; i++)
     {
       int child_t = (random() % (child_max_t + 1));
       usleep(child_t*1000);

       children = fork();

       // Child process is created here
       if(children == 0)
       {
        handler(getpid(), cmt, "child");
        break;
       }
       else
       {
       }
     }
   }

   else
   {
   }
 }
 return 0;
}

void handler(int pid,int maxtime, char* type)
{
 sem_wait(entry);

  printf("%s %i%s\n",type,pid," attempting to enter...");

  if(type == "child")
  {
    int child_leave_t = (random() % (maxtime + 1));

    if((*adults_inside) != 0)
    {

     if(((*children_inside)+1)/(*adults_inside) <= 3)
     {
       (*children_inside)++;
       printf("%s %i%s\n",type,pid," entered!");

       usleep(child_leave_t*1000);

       printf("%s %i%s\n",type,pid," left!");
       (*children_inside)--;
     }

     else
     {
        printf("%s %i%s\n",type,pid," can not enter. Waiting...");
     }

    }
    else
    {
        printf("%s %i%s\n",type,pid," can not enter. Waiting...");
    }
  }

  else if(type == "adult")
  {

    (*adults_inside)++;
    printf("%s %i%s\n",type,pid," entered.");

  }

 sem_post(entry);
}

void load_init_values()
{
 adults_sem = mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, 0, 0);
 children_sem = mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, 0, 0);
 entry = mmap(NULL, sizeof(sem_t), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, 0, 0);
 shm_a_id = shmget(IPC_PRIVATE, sizeof(int), IPC_CREAT | IPC_EXCL | 0666);
 shm_c_id = shmget(IPC_PRIVATE, sizeof(int), IPC_CREAT | IPC_EXCL | 0666);
 adults_inside = (int *) shmat(shm_a_id, NULL, 0);
 children_inside = (int *) shmat(shm_c_id, NULL, 0);
 sem_init(adults_sem,1,1);
 sem_init(children_sem,1,1);
 sem_init(entry,1,1);
}

此代码仅模拟流程的生成。有一个共享信号量entry,当时只允许一个进程请求进入。共享内存变量adults_insidechildren_inside跟踪内部状态。

我的问题基本上位于处理函数中。在禁止孩子进入的状态被触发后,我无法弄清楚如何实施被动等待。我正在考虑使用pause()系统调用并将等待进程存储在队列中,但看起来效率很低。我应该选择什么方法?

2 个答案:

答案 0 :(得分:3)

您需要根据某种形式的IPC实现此目的。你提到过使用Linux,但我会假设POSIX-with-unnamed-semaphores(即OS X),因为你还没有使用任何特定于Linux的东西。其他人提到如果使用线程,这可能会更简单。但也许你有一些使用多个进程的理由,所以我只想假设。

根据规定,代码似乎不允许成年人退出,这使事情变得更简单。您已经知道在任何时间点允许的孩子数量,因为这与任何特定时间点的成年人数量成正比。

为了弄清楚如何解决这个问题,让我们考虑如何在现实生活中处理这样的事情。我们可以想象在日托中有某种“看门人”。这个“看门人”在代码中用状态的总和表示:信号量和共享记忆变量,表示在任何时间点出现的成人和儿童的数量。当没有孩子被允许进入时,看门人阻止进入,孩子必须形成一条线。我认为目的是允许儿童以先到先得的方式进入;这意味着您需要使用某种FIFO来表示队列。当孩子离开时,看门人必须能够通知第一个孩子他们有资格进入。

所以这段代码缺少两件事:

  1. 某种表示等待进入的儿童的排序的FIFO
  2. 某种通知机制,孩子可以等待通知,并且看门人可以触发“唤醒”孩子。
  3. 现在,问题是我们在此队列中存储了哪些数据以及我们如何进行通知。有几个选项,但我会讨论两个最明显的选择。

    让孩子等待可能就像让网守将子PID放在FIFO的尾部并使用SIGSTOP发送PID kill(2)一样简单。这可能会发生几次。一旦孩子离开,看门人从FIFO的头部出队并发送pid SIGCONT

    按照目前的架构,程序中的“看门人”更像是一个抽象的概念。更清晰的实现可能会将网守实现为管理进程。

    但由于不存在这样的过程,我们需要想象孩子在门口看到“请等待”的标志并等待。表示子进程的进程可以通过将自己置于FIFO的尾部,并使用raise(3)库函数并发送自身SIGSTOP来使自己等待。然后,当任何一个孩子离开时,它从FIFO的前面读取并使用SIGCONT发送该pid kill(2)

    此实现相对简单,所需的唯一额外资源是以某种方式表示共享内存中的队列。

    另一种方法是为每个孩子提供自己的文件描述符。这可以是pipe(2),也可以是PF_LOCAL socket(2)之类的双向文件描述符。将文件描述符保留为阻塞模式,当不允许子进入时,它将文件描述符放在FIFO的尾部(可能是管道的写入端),并阻塞read(2)一个读取端的字节(如果不是管道,则与fd相同)。

    当一个子进入时,它将从FIFO前面的条目和write(2)一个字节拉到那里的文件描述符。这将唤醒在read(2)中被阻止的子进程,它将继续以愉快的方式进入日托。

    如前所述,还提出了条件变量。我通常会避开它们;它们很容易被滥用,而且你已经在序列化了这个过程。

    在信号和文件描述符的情况下,整数的环形缓冲区就足够了 - 这就是你需要存储在FIFO中的所有状态。

    FIFO需要仔细考虑。由于多个进程将读取和操作它,因此它也必须位于共享内存中。无论FIFO是作为环形缓冲区还是其他方式实现,您可能都希望对队列长度进行一些限制。如果有太多的孩子排队,也许到家的孩子只是“回家”。此外,您需要确保在进入/退出时正常处理空FIFO的情况,并确保从1服务员转换为0按预期工作。由于您使用信号量序列化进入/退出,这应该是直截了当的。

答案 1 :(得分:3)

2个信号量精确地模拟了实际问题

虽然很有可能将统计数据合并到同步中,但实际上只需要为这种托儿所进行同步的最低要求:

  • 儿童的空缺数量为> 0,以便儿童进入,否则他们等待
  • 离开成年人相对于彼此原子地采取他们的3个空缺或等待。 (一个成年人拒绝在出路上承担更多的责任在你的模型中并不明确,但是在rwlock实现中防止了类似于作家饥饿的退出饥饿。)

但必须精确映射

当信号量点击0时,它们强制执行等待,因此要使用您开始设置的2个信号量对其进行建模,其使用情况需要与更多细节相匹配:

 sem_init(adults_exiting_sem,1,1); /* Allow 1 adult to be decrementing */
 sem_init(children_spots_sem,1,0); /* Allow no child without an adult */

然后处理程序代码可以依赖正确的信号量模型来强制等待:

void handler(int pid,int maxtime, char* type)
{
  int leave_t = (random() % (maxtime + 1));

  if(type == "child")
  {

    printf("%s %i%s\n",type,pid," attempting to enter...");
    sem_wait(children_spots_sem);

    printf("%s %i%s\n",type,pid," entered!");
    sleep(leave_t);

    sem_post(children_spots_sem);
  }
  else if(type == "adult")
  {
    /* probably an inline function */
    sem_post(children_spots_sem);
    sem_post(children_spots_sem);
    sem_post(children_spots_sem);

    printf("%s %i%s\n",type,pid," entered.");
    sleep(leave_t);
    printf("%s %i%s\n",type,pid," attempting to leave...");

    /* adult exit funnel */
    sem_wait(adults_exiting_sem);

    /* probably an inline function */
    sem_wait(children_spots_sem);
    sem_wait(children_spots_sem);
    sem_wait(children_spots_sem);

    sem_post(adults_exiting_sem);

  }
    printf("%s %i%s\n",type,pid," left!");
}

总有更多需要

当然,您可能希望通过以下方式进一步扩展模型:

  1. 使用sem_timedwait来模仿放弃子女的父母。
  2. 通过其他同步重新引入统计信息,或者只记录后期分析。