简体   繁体   English

Linux被动等待条件更新

[英]Linux passive waiting for condition update

I am attempting to create a code that simulates child care center. 我正在尝试创建一个模拟幼儿中心的代码。 In this center one adult can care for up to three children. 在这个中心,一个成年人可以照顾最多三个孩子。 This condition has to be fulfilled all the time. 必须始终满足这一条件。 Adults and children are processes generated randomly and amount of children and adults is set in program arguments. 成人和儿童是随机生成的过程,并且在计划参数中设置了儿童和成人的数量。 Child can enter only if there is enough adults inside and adult can leave only if there is enough other adults to care for the children. 只有当里面有足够的成年人时,孩子才能进入,只有当有足够的其他成年人照顾孩子时,成人才能离开。 If not, passive waiting should be implemented, until the condition allows child/adult to leave/enter. 如果不是,则应实施被动等待,直到条件允许儿童/成人离开/进入。

#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);
}

This code only simulates generating of processes. 此代码仅模拟进程的生成。 There is one shared semaphore entry that allows only one process at the time to request entering. 有一个共享信号量entry ,当时只允许一个进程请求输入。 Shared memory variables adults_inside and children_inside keep track of the inner state. 共享内存变量adults_insidechildren_inside跟踪内部状态。

My problem is basically located in handler function. 我的问题基本上位于处理函数中。 After condition disallowing child to enter is triggered, I can not figure out how to implement passive wait. 在禁止孩子进入的状态被触发后,我无法弄清楚如何实施被动等待。 I was thinking about using pause() system call and store the waiting processes in queue, but is seem quite inefficient. 我正在考虑使用pause()系统调用并将等待进程存储在队列中,但看起来效率很低。 What approach should I choose? 我应该选择什么方法?

You will need to implement this in terms of some form of IPC. 您需要根据某种形式的IPC来实现这一点。 You mentioned using Linux, but I will assume POSIX-with-unnamed-semaphores (ie not OS X) since you aren't yet using anything Linux-specific. 你提到过使用Linux,但我会假设POSIX-with-unnamed-semaphores(即OS X),因为你还没有使用任何特定于Linux的东西。 Others have mentioned this could be simpler if you used threads. 其他人提到如果使用线程,这可能会更简单。 But maybe you have some reason for using multiple processes, so I'll just assume that. 但也许你有一些使用多个进程的理由,所以我只是假设。

As specified, the code does not appear to allow adults to exit, which makes things a bit simpler. 根据规定,代码似乎不允许成年人退出,这使事情变得更简单。 You already know how many children are allowed at any point in time, as that is directly proportional to the number of adults present at any given point in time. 您已经知道在任何时间点允许的孩子数量,因为这与任何给定时间点的成年人数量成正比。

To figure out how to solve the problem, let's consider how such a thing might be handled in real life. 为了弄清楚如何解决问题,让我们考虑如何在现实生活中处理这样的事情。 We can imagine that there is some kind of "gatekeeper" at the daycare. 我们可以想象在日托中有某种“看门人”。 This "gatekeeper" is represented in the code by the sum of the state: the semaphore and the shared memory variables representing the number of adults and children present at any point in time. 这个“看门人”在代码中用状态的总和表示:信号量和共享记忆变量,表示在任何时间点出现的成人和儿童的数量。 When no children are allowed to enter, the gatekeeper blocks entry and the children must form a line. 当没有孩子被允许进入时,看门人阻止进入,孩子必须形成一条线。 I assume that the intent is that children are allowed to enter in a first-come-first-serve basis; 我认为目的是允许儿童以先到先得的方式进入; this means you'll need to have some kind of FIFO to represent the queue. 这意味着您需要使用某种FIFO来表示队列。 When a child leaves, the gatekeeper must be able to notify the first child in line that they are eligible to enter. 当孩子离开时,看门人必须能够通知第一个孩子他们有资格进入。

So this code is missing two things: 所以这段代码缺少两件事:

  1. Some kind of FIFO representing the ordering of children waiting to enter 某种FIFO表示等待进入的孩子的顺序
  2. Some kind of notification mechanism that a child can wait for a notification on, and that the gatekeeper can trigger to "wake up" the child. 某种通知机制,孩子可以等待通知,并且看门人可以触发“唤醒”孩子。

Now, the question is what data we store in this queue and how we do the notification. 现在,问题是我们在此队列中存储了哪些数据以及我们如何进行通知。 There are several options, but I'll discuss the two most obvious. 有几个选项,但我会讨论两个最明显的选项。

Making the child wait could be as simple as having the gatekeeper place the child PID at the tail of the FIFO and sending that PID SIGSTOP using kill(2) . 使子进行等待可能就像让网守将子PID放在FIFO的尾部并使用kill(2)发送PID SIGSTOP一样简单。 This may happen several times. 这可能会发生几次。 Once a child leaves, the gatekeeper dequeues from the head of the FIFO and sends the pid SIGCONT . 一旦孩子离开,看门人从FIFO的头部出队并发送pid SIGCONT

As currently architected, the "gatekeeper" in your program is more of an abstract concept. 按照目前的架构,程序中的“看门人”更像是一个抽象的概念。 A clearer implementation might implement a gatekeeper as a management process. 更清晰的实现可能会将网守实现为管理进程。

But since no such process exists, we need to imagine something like the child sees a "please wait" sign at the door and waits. 但由于不存在这样的过程,我们需要想象孩子在门口看到“请等待”的标志并等待。 The process representing the child can make itself wait by placing itself at the tail of the FIFO, and using the raise(3) library function, and sending itself SIGSTOP . 表示子进程的进程可以通过将自己置于FIFO的尾部,并使用raise(3)库函数并发送自身SIGSTOP来使自己等待。 Then, when any child leaves, it reads from the front of the FIFO and sends that pid SIGCONT using kill(2) . 然后,当任何一个孩子离开时,它从FIFO的前面读取并使用kill(2)发送该pid SIGCONT

This implementation is relatively straightforward and the only additional resources required are to somehow represent the queue in shared memory. 此实现相对简单,所需的唯一额外资源是以某种方式表示共享内存中的队列。

An alternative approach would be to give each child its own file descriptor(s). 另一种方法是为每个孩子提供自己的文件描述符。 This could be either a pipe(2) , or a bidirectional file descriptor like a PF_LOCAL socket(2) . 这可以是pipe(2) ,也可以是PF_LOCAL socket(2)类的双向文件描述符。 Leaving the file descriptors in blocking mode, when a child is not allowed to enter, it puts (maybe the write-side of, if a pipe) its file descriptor at the tail of the FIFO, and blocks read(2) ing one byte from the read-side (which would be the same fd if not a pipe). 将文件描述符保留在阻塞模式下,当不允许子进入时,它将文件描述符放在FIFO的尾部(可能是管道的写入端),并阻塞read(2)一个字节从读取端(如果不是管道,它将是相同的fd)。

When a child exits, it pulls the entry from the front of the FIFO and write(2) s one byte to the file descriptor there. 当一个子进入时,它从FIFO的前面拉出条目并将write(2)一个字节write(2)那里的文件描述符。 This will wake the child process that is blocked in read(2) , and it will continue on its merry way into the daycare. 这将唤醒在read(2)中被阻止的子进程,并且它将以愉快的方式继续进入日托。

As previously stated, condition variables have also been suggested. 如前所述,还提出了条件变量。 I usually avoid them; 我通常会避开它们; they are easy to misuse, and you're already serializing the entry process. 它们很容易被滥用,而且您已经在序列化了输入过程。

In both the signal and the file descriptor case, a ring buffer of integers suffices -- so that's all the state you need to store in the FIFO. 在信号和文件描述符的情况下,整数的环形缓冲区就足够了 - 这就是你需要存储在FIFO中的所有状态。

The FIFO requires some careful consideration. FIFO需要仔细考虑。 Since multiple processes will be reading and manipulating it, it must also be in shared memory. 由于多个进程将读取和操作它,因此它也必须位于共享内存中。 Whether the FIFO is implemented as a ring buffer or some other way, you'll probably want to define some limits on the length of your queue. 无论FIFO是作为环形缓冲区还是其他方式实现,您可能都希望对队列长度进行一些限制。 If there are too many children in line, maybe arriving children just "go home." 如果有太多的孩子排队,也许到家的孩子只是“回家”。 Also, you'll need to make sure you handle the case of an empty FIFO gracefully on entry/exit, and make sure that transitioning from 1 waiter to 0 works as intended. 此外,您需要确保在进入/退出时正常处理空FIFO的情况,并确保从1服务员转换为0按预期工作。 Since you're serializing entry / exit with a semaphore, this should be straightforward. 由于您使用信号量序列化进入/退出,因此这应该是直截了当的。

2 semaphores precisely model the actual problem 2个信号量精确地模拟了实际问题

While it is tempting to combine stats into synchronization, the minimum you need to synchronize for this child care is really only: 虽然很有可能将统计数据与同步结合起来,但实际上只需要为这种托儿所进行同步的最小值:

  • the number of vacancies for children are > 0 for a child to enter, otherwise they wait . 孩子进入的儿童职位空缺数量> 0 ,否则等待
  • exiting adults take their 3 vacancies atomically with respect to each other or wait . 退出的成年人相互之间原子地占据他们的3个空缺或等待 (One adult refusing to take more responsibility on the way out is not explicit in your model, but prevents an exit starvation similar to writer starvation in rwlock implementations.) (一个成年人拒绝在出路上承担更多的责任在你的模型中并不明确,但是在rwlock实现中防止类似于作家饥饿的退出饥饿。)

But they must be mapped precisely 但它们必须精确映射

When semaphores hit 0 they force a wait , so to model this with the 2 semaphores you began to setup, their usage needs to match a few more specifics: 当信号量达到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 */

Then the handler code can rely on the correct model of the semaphores to force waiting: 然后处理程序代码可以依赖正确的信号量模型来强制等待:

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!");
}

And there is always more to want 而且总有更多需要

Naturally, you may want to further extend the model by: 当然,您可能希望通过以下方式进一步扩展模型:

  1. use sem_timedwait to model parents giving up on dropping off their children. 使用sem_timedwait来模拟父母放弃他们的孩子。
  2. reintroducing stats either with additional synchronization or just log for post analysis. 通过附加同步重新引入统计数据或仅记录后期分析。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM