Пассивное ожидание обновления условий Linux
Я пытаюсь создать код, который имитирует центр по уходу за ребенком. В этом центре один взрослый может ухаживать за тремя детьми. Это условие должно выполняться постоянно. Взрослые и дети-это процессы, генерируемые случайным образом, а количество детей и взрослых задается в аргументах программы. Ребенок может войти, только если внутри достаточно взрослых, а взрослый может выйти, только если достаточно других взрослых, чтобы заботиться о детях. Если нет, пассивное ожидание должно быть реализовано, пока условие позволяет ребенку / взрослому выйти / войти.
#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%sn",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%sn",type,pid," entered!");
usleep(child_leave_t*1000);
printf("%s %i%sn",type,pid," left!");
(*children_inside)--;
}
else
{
printf("%s %i%sn",type,pid," can not enter. Waiting...");
}
}
else
{
printf("%s %i%sn",type,pid," can not enter. Waiting...");
}
}
else if(type == "adult")
{
(*adults_inside)++;
printf("%s %i%sn",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()
системный вызов и хранение процессов ожидания в очереди, но кажется довольно неэффективным. Какой подход выбрать?
2 ответов
вам нужно будет реализовать это с точки зрения какой-либо формы IPC. Вы упомянули использование Linux, но я предположу, что POSIX-with-unnamed-semaphores (т. е. не OS X), так как вы еще не используете ничего специфичного для Linux. Это может быть проще, если вы использовали нити. Но, возможно, у вас есть причина для использования нескольких процессов, поэтому я просто предположу это.
как указано, код не позволяет взрослым выходить, что делает вещи немного проще. Вы уже знать, сколько детей разрешено в любой момент времени, так как это прямо пропорционально количеству взрослых, присутствующих в любой данный момент времени.
чтобы выяснить, как решить проблему, давайте рассмотрим, как такая вещь может быть обработана в реальной жизни. Можно представить, что в детском саду есть какой-то "привратник". Этот "привратник" представлен в коде суммой состояния: семафор и переменные общей памяти, представляющие количество взрослых и дети в любой момент времени. Когда дети не могут войти, привратник блокирует вход, и дети должны сформировать линию. Я предполагаю, что намерение состоит в том, что детям разрешено входить в первую очередь; это означает, что вам нужно будет иметь какой-то FIFO для представления очереди. Когда ребенок уходит, привратник должен иметь возможность уведомить первого ребенка в очереди, что они имеют право войти.
таким образом, этот код отсутствует два вещи:
- какой-то FIFO, представляющий порядок детей, ожидающих входа
- какой-то механизм уведомления, что ребенок может ждать уведомления, и что привратник может вызвать, чтобы "разбудить" ребенка.
теперь вопрос в том, какие данные мы храним в этой очереди и как мы делаем уведомление. Есть несколько вариантов, но я рассмотрю два наиболее очевидным.
Заставляя ребенка ждать, может быть так же просто, как если бы привратник поместил ребенка PID в хвост FIFO и отправил этот PID SIGSTOP
используя kill(2)
. Это может произойти несколько раз. Как только ребенок уходит, привратник выносит из головы ФИФО и посылает пид SIGCONT
.
как в настоящее время architected, "привратник" в вашей программе больше абстрактной концепции. Более четкая реализация может реализовать gatekeeper как процесс управления.
но поскольку такого процесса не существует, нам нужно представить себе что-то вроде того, как ребенок видит знак "пожалуйста, подождите" на двери и ждет. Процесс, представляющий ребенка, может заставить себя ждать, поместив себя в хвост ФИФО и используя raise(3)
функция библиотеки и отправка себя SIGSTOP
. Затем, когда любой ребенок уходит, он читает с передней части FIFO и отправляет этот pid SIGCONT
используя kill(2)
.
эта реализация относительно проста, и единственные дополнительные ресурсы, необходимые, чтобы как-то представлять очередь в общей памяти.
альтернативным подходом было бы дать каждому ребенку свой собственный файловый дескриптор(ы). Это может быть либо pipe(2)
, или двунаправленный файловый дескриптор как PF_LOCAL
socket(2)
. Оставляя файловые дескрипторы в режиме блокировки, когда ребенку не разрешено входить, он помещает (возможно, сторону записи, если канал) свой файловый дескриптор в хвост FIFO и блокирует read(2)
ing один байт со стороны чтения (который был бы тем же fd, если бы не труба.)
когда ребенок выходит, он тянет вход с передней части FIFO и write(2)
s один байт в файловый дескриптор. Это разбудит дочерний процесс, который заблокирован в read(2)
, и он будет продолжать свой веселый путь в детский сад.
как уже говорилось ранее, переменные состояния также были предложены. Я обычно избегаю их; ими легко злоупотреблять, и вы уже сериализуете процесс входа.
в сигнал и случай файлового дескриптора, кольцевого буфера целых чисел достаточно - так что это все состояние, которое вам нужно сохранить в FIFO.
FIFO требует некоторого тщательного рассмотрения. Поскольку несколько процессов будут читать и манипулировать им, он также должен быть в общей памяти. Независимо от того, реализован ли FIFO как кольцевой буфер или каким-либо другим способом, вы, вероятно, захотите определить некоторые ограничения на длину вашей очереди. Если в очереди слишком много детей, возможно, прибывающие дети просто "идут домой"." Кроме того, вам нужно будет убедиться, что вы изящно обрабатываете случай пустого FIFO при входе/выходе и убедитесь, что переход от 1 официанта к 0 работает по назначению. Поскольку вы сериализуете вход / выход с помощью семафора, это должно быть просто.
2 семафора точно моделируют актуальную проблему
хотя соблазнительно объединить статистику в синхронизацию, минимум, который вам нужно синхронизировать для этого ухода за ребенком, действительно только:
- количество вакансий для детей
> 0
для ребенка, чтобы войти, иначе они ждать. - выходя взрослые принимают их 3 вакансии атомарно по отношению друг к другу или ждать. (Один взрослый отказывается принимать больше ответственность на выходе не является явной в вашей модели, но предотвращает голод выхода, подобный голоду писателя в реализациях rwlock.)
но они должны быть нанесены на карту точно
когда семафоры нажмите 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!");
}
и всегда хочу!--6-->
естественно, вы можете расширить модель:
- использовать
sem_timedwait
чтобы моделировать родителей, отказывающихся от высадки своих детей. - повторное введение статистики либо с дополнительной синхронизацией, либо просто войдите в систему для анализа сообщений.