Linux被动等待条件更新

Joh*_*ith 7 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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

dho*_*dho 3

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

正如所指定的,该代码似乎不允许成人退出,这使得事情变得更简单一些。您已经知道在任何时间点允许有多少儿童,因为这与任何给定时间点在场的成人人数成正比。

为了弄清楚如何解决这个问题,让我们考虑一下在现实生活中如何处理这样的事情。我们可以想象,托儿所里有某种“看门人”。这个“看门人”在代码中由状态的总和表示:信号量和共享内存变量,表示任何时间点存在的成人和儿童的数量。当不允许儿童进入时,看门人会阻止进入,儿童必须排队。我认为其目的是允许儿童按照先到先得的原则进入;这意味着您需要某种 FIFO 来表示队列。当孩子离开时,看门人必须能够通知排队的第一个孩子他们有资格进入。

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

  1. 某种 FIFO 代表等待进入的孩子的顺序
  2. 某种通知机制,孩子可以等待通知,并且看门人可以触发“唤醒”孩子。

现在的问题是我们在这个队列中存储什么数据以及我们如何进行通知。有多种选择,但我将讨论最明显的两个。

让子进程等待可以很简单,只需让网守将子进程 PID 放在 FIFO 的尾部并使用 发送该 PIDSIGSTOP即可kill(2)。这种情况可能会发生几次。一旦孩子离开,网守就会从 FIFO 的头部出列并发送 pid SIGCONT

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

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

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

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

当子进程退出时,它会从 FIFO 前面拉出条目,并将write(2)一个字节发送到那里的文件描述符。这将唤醒被阻塞的子进程read(2),并且它将继续愉快地进入 daycare。

如前所述,还建议了条件变量。我通常会避开他们;它们很容易被误用,而且您已经将输入过程序列化了。

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

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