我正在尝试创建一个模拟托儿中心的代码。在这个中心,一名成年人最多可以照顾三个 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_inside
和 children_inside
跟踪内部状态。我的问题基本上位于处理程序函数中。在触发不允许 child 进入的条件后,我无法弄清楚如何实现被动等待。我正在考虑使用
pause()
系统调用并将等待的进程存储在队列中,但似乎效率很低。我应该选择什么方法?
最佳答案
您将需要根据某种形式的 IPC 来实现这一点。您提到使用 Linux,但我将假设 POSIX-with-unnamed-semaphores(即不是 OS X),因为您还没有使用任何特定于 Linux 的东西。其他人提到,如果您使用线程,这可能会更简单。但也许您有使用多个进程的原因,所以我只是假设。
正如指定的那样,代码似乎不允许成年人退出,这使事情变得简单一些。您已经知道在任何时间点允许有多少 child ,因为这与任何给定时间点在场的成年人数量成正比。
为了弄清楚如何解决这个问题,让我们考虑一下在现实生活中如何处理这样的事情。我们可以想象,托儿所里有某种“看门人”。这个“看门人”在代码中由状态的总和表示:信号量和共享内存变量,代表在任何时间点出现的成人和 child 的数量。当没有 child 被允许进入时,看门人会阻止进入, children 必须排成一列。我认为其目的是允许 child 以先到先得的方式进入;这意味着您需要使用某种 FIFO 来表示队列。当 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/