我正在尝试创建一个模拟幼儿中心的代码。在这个中心,一个成年人可以照顾最多三个孩子。必须始终满足这一条件。成人和儿童是随机生成的过程,并且在计划参数中设置了儿童和成人的数量。只有当里面有足够的成年人时,孩子才能进入,只有当有足够的其他成年人照顾孩子时,成人才能离开。如果没有,则应实施被动等待,直到条件允许儿童/成人离开/进入。
#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_inside
和children_inside
跟踪内部状态。
我的问题基本上位于处理函数中。在禁止孩子进入的状态被触发后,我无法弄清楚如何实施被动等待。我正在考虑使用pause()
系统调用并将等待进程存储在队列中,但看起来效率很低。我应该选择什么方法?
答案 0 :(得分:3)
您需要根据某种形式的IPC实现此目的。你提到过使用Linux,但我会假设POSIX-with-unnamed-semaphores(即OS X),因为你还没有使用任何特定于Linux的东西。其他人提到如果使用线程,这可能会更简单。但也许你有一些使用多个进程的理由,所以我只想假设。
根据规定,代码似乎不允许成年人退出,这使事情变得更简单。您已经知道在任何时间点允许的孩子数量,因为这与任何特定时间点的成年人数量成正比。
为了弄清楚如何解决这个问题,让我们考虑如何在现实生活中处理这样的事情。我们可以想象在日托中有某种“看门人”。这个“看门人”在代码中用状态的总和表示:信号量和共享记忆变量,表示在任何时间点出现的成人和儿童的数量。当没有孩子被允许进入时,看门人阻止进入,孩子必须形成一条线。我认为目的是允许儿童以先到先得的方式进入;这意味着您需要使用某种FIFO来表示队列。当孩子离开时,看门人必须能够通知第一个孩子他们有资格进入。
所以这段代码缺少两件事:
现在,问题是我们在此队列中存储了哪些数据以及我们如何进行通知。有几个选项,但我会讨论两个最明显的选择。
让孩子等待可能就像让网守将子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)
虽然很有可能将统计数据合并到同步中,但实际上只需要为这种托儿所进行同步的最低要求:
> 0
,以便儿童进入,否则他们等待。当信号量点击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!");
}
当然,您可能希望通过以下方式进一步扩展模型:
sem_timedwait
来模仿放弃子女的父母。