c - Linux 被动等待条件更新

标签 c linux synchronization

我正在尝试创建一个模拟托儿中心的代码。在这个中心,一名成年人最多可以照顾三个 child 。这个条件必须一直满足。成人和 child 是随机生成的过程, child 和成人的数量在程序参数中设置。 child 只有在里面有足够的成年人才能进入,成年人只有在有足够的其他成年人照顾 child 的情况下才能离开。如果不是,则应实现被动等待,直到条件允许 child /成人离开/进入。

#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跟踪内部状态。

我的问题基本上位于处理程序函数中。在触发不允许 child 进入的条件后,我无法弄清楚如何实现被动等待。我正在考虑使用 pause()系统调用并将等待的进程存储在队列中,但似乎效率很低。我应该选择什么方法?

最佳答案

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

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

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

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

  • 某种FIFO代表等待进入的 child 的顺序
  • 某种通知机制, child 可以等待通知,并且网守可以触发“唤醒” child 。

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

    让子进程等待可能就像让网守将子进程 PID 放在 FIFO 的尾部并发送该 PID SIGSTOP 一样简单。使用 kill(2) .这可能会发生多次。一旦 child 离开,网守从 FIFO 的头部出列并发送 pid SIGCONT .

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

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

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

    另一种方法是给每个 child 自己的文件描述符。这可能是 pipe(2) ,或双向文件描述符,如 PF_LOCAL socket(2) .让文件描述符处于阻塞模式,当一个子进程不被允许进入时,它把它的文件描述符放在 FIFO 的尾部(可能是写端,如果是管道),并阻塞 read(2)从读取端读取一个字节(如果不是管道,它将是相同的 fd)。

    当 child 退出时,它会从 FIFO 和 write(2) 的前面拉入条目。 s 一个字节到那里的文件描述符。这将唤醒在 read(2) 中阻塞的子进程,它将继续愉快地进入托儿所。

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

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

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

    关于c - Linux 被动等待条件更新,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43478894/

    相关文章:

    c++ - 是否可以将 C++ 命名空间添加到 C 库中的所有符号?

    c - 带有可变长度数组的奇怪输出?

    java - 同步客户端-服务器游戏状态

    javascript - 多设备间同步JS时间

    java - 如何从首选项同步 getter 和 setter?

    c - 如何使用 WinSock2 和 C 获取已连接客户端的 IP 地址?

    c - 通过唯一 ID 查找 glade gtk3 小部件

    c - 基本 shell 程序的管道问题

    php - UTF-8贯穿始终

    linux - 将标准错误发送到标准输出不起作用