Обнаружить, когда fifo открывается из программы

У меня есть ситуация, когда мне нужно проверить, открыла ли его другая сторона fifo, однако я не могу использовать открытие, потому что иначе программа начнет что-то делать.

Почему я должен это сделать: у меня есть программа (монитор), которая запускает серверную программу (обе созданы мной). Монитор использует этот FIFO для связи, потому что монитор можно закрыть/открыть снова, когда сервер уже запущен.

Проблема в том, что когда монитор запускает сервер: в этом случае я должен каким-то образом дождаться создания fifos, а затем открыть их. На самом деле я использую время на мониторе, который проверяет, когда создаются fifo, однако таким образом он открывает fifo прежде, чем сервер сможет это сделать (даже если инструкция после mkfifo на самом деле является открытым!!!).

Вы можете сказать, что я открываю fifo в неправильном порядке на мониторе (я открываю fifo для записи (НЕПРАВИЛЬНО) перед fifo для чтения), проблема в том, что я не могу изменить этот порядок, потому что требуется, чтобы сервер будет ждать клиентов по открытому (ТОЛЬКО RDON) fifo.

Любое предложение о том, как избежать этого состояния гонки? На самом деле я использую сон в мониторе после проверки того, созданы ли FIFO, это явно решает проблему, но я думаю, что это определенно неправильно.

Спасибо всем

Редактировать 1:

Вот как обстоят дела на данный момент

Сервер

mkfifo(fifo1)
mkfifo(fifo2)
open(fifo1 O_RDONLY)
open(fifo2 O_WRONLY)

Монитор

while (fifo1 doesn't exists && fifo2 doesn't exists);
open(fifo1 O_WRONLY)
open(fifo2 O_RDONLY)

Я думаю, что состояние гонки теперь довольно явное, важно отметить, что fifos блокируются (блокируются только RDONLY, WRONLY не будет блокироваться, даже если на другой стороне никого нет => это поведение Unix, разработано не мной).

Редактировать 2:

Состояние гонки происходит на первом открытом уровне FIFO. Я должен открыть первый файл fifo на сервере до того, как это сделает монитор.


person Francesco Belladonna    schedule 28.10.2011    source источник


Ответы (4)


Вы можете использовать именованный семафор с помощью sem_open(), который виден каждой программе на уровне файловой системы, чтобы синхронизировать две программы. По сути, ваша программа мониторинга будет ждать на заблокированном семафоре, пока сервер не увеличит его. В этот момент все файлы fifo будут инициализированы, и вы сможете двигаться вперед с помощью своего монитора с fifo в известном состоянии.

Обязательно используйте флаги O_CREAT и O_EXCL при первоначальном вызове sem_open(), чтобы создание семафора было атомарным. Например, и монитор, и серверная программа попытаются создать семафор при запуске, если он еще не существует... если он существует, вызов завершится ошибкой, что означает либо монитор, либо сервер, но не обе программы. , получил право создать семафор и инициализировать его. Затем монитор ожидает на семафоре, пока сервер инициализирует fifo... после инициализации fifo сервер освобождает семафор, и монитор может продолжить работу.

Вот процесс, как я его себе представляю... Я считаю, что он должен эффективно решить ваше состояние гонки:

В мониторе:

  1. Создайте именованный семафор и установите его в заблокированном состоянии.
  2. Запустить сервер
  3. Подождите, пока FIFO не станет видимым на уровне файловой системы.
  4. Открыть fifo1 для записи (без блокировки)
  5. Открыть fifo2 для чтения (блокируется, пока сервер не откроет fifo2 для записи)
  6. Подождите семафор (возможно, с тайм-аутом), пока сервер не разблокирует его, указывая, что теперь он успешно открыл оба FIFO.

На сервере:

  1. Создайте FIFO
  2. Open fifo1 (блокирует, пока монитор не откроет его для записи)
  3. Откройте fifo2 (неблокирующий)
  4. Разблокируйте семафор теперь, когда сервер открыл оба FIFO.

Таким образом, ваш монитор не может продолжать работу до тех пор, пока не будет «известного состояния», в котором все правильно инициализировано... сервер указывает это состояние монитору через именованный семафор.

person Jason    schedule 28.10.2011
comment
Ну, я думаю, есть проблема: если я разблокирую семафор на сервере непосредственно перед открытием fifo, монитор все равно выиграет гонку, чтобы открыть fifo первым. И я не могу разблокировать семафор после открытия, потому что это блокирующее открытие :\ - person Francesco Belladonna; 28.10.2011
comment
В fifo, когда он открывается, поскольку RDONLY является блокирующим вызовом, вы даже можете увидеть это на экране процесса, у них есть WCHAN = pipe_wait - person Francesco Belladonna; 28.10.2011
comment
Именованный семафор можно использовать после вызовов open(), чтобы сообщить вам, что сервер готов выполнить read() в открытом FIFO. Другими словами, ваш монитор не может выполнять запись в FIFO до тех пор, пока семафор не будет освобожден ... в основном читатели разблокируют семафор, позволяя писателям записывать в FIFO. - person Jason; 28.10.2011
comment
Я думаю, вы неправильно поняли проблему, я должен открыть (на сервере) первый fifo, прежде чем это сделает монитор. Нет никаких проблем после того, как оба открыли fifos. - person Francesco Belladonna; 28.10.2011
comment
Это невозможно... Вызов только для чтения для открытия FIFO будет блокироваться до тех пор, пока не появится хотя бы один писатель, который открыл FIFO. - person Jason; 28.10.2011
comment
Быстрый вопрос ... зачем нужен этот конкретный порядок? ... происходит ли что-то еще (т. е. что-то еще нужно сначала записать в FIFO), так что монитор не может быть первой программой, открывающей FIFO только для записи? - person Jason; 28.10.2011
comment
Да, монитор не единственный клиент. После того, как монитор запустил сервер, его можно закрыть и некоторые клиенты могут работать на сервере. Поэтому важно, чтобы сервер оставался заблокированным при первом открытии. - person Francesco Belladonna; 29.10.2011
comment
@Fire-Dragon-DoL: эта причина на самом деле не объясняет, почему вам нужно оставаться заблокированным при первом открытии ... если в FIFO нет записывающих устройств, вы просто прочитаете 0-байты и / или вы можно использовать poll или select, чтобы проверить, доступны ли данные для чтения из FIFO. - person Jason; 29.10.2011
comment
Требование (для домашнего задания) состоит в том, чтобы сервер оставался заблокированным при первом открытии, поэтому это должно быть сделано таким образом. Также опрос не является хорошей идеей, потому что будет тратить циклы процессора. - person Francesco Belladonna; 29.10.2011
comment
@Fire-Dragon-DoL: ручное ожидание в режиме ожидания тратит впустую циклы ЦП ... poll и select переводят процесс в спящий режим и пробуждают его, когда есть что-то готовое для чтения. Во-вторых, вы упомянули, что сервер должен оставаться заблокированным, но он разблокируется, когда кто-либо еще откроет FIFO для записи, включая клиента ... так когда же серверу разрешено разблокироваться из open()? - person Jason; 29.10.2011
comment
Сервер остается заблокированным при первом открытии, когда кто-то открывает первый fifo, сервер разблокируется, делает что-то для этого клиента, а затем снова блокируется (это запрашивает учитель). Я думаю, что не понял, что вы спросили, надеюсь, это ответ - person Francesco Belladonna; 30.10.2011
comment
Спасибо за эту информацию ... мой следующий вопрос: что должна делать программа мониторинга, пока сервер заблокирован в FIFO в ожидании клиентов? - person Jason; 30.10.2011
comment
Что ж, программа мониторинга может отправлять некоторые команды (например, клиент), которые отличаются от клиентов. Например, клиент может отправлять/получать какие-то сообщения (это программа, имитирующая почтовый ящик). Монитор может добавить нового пользователя (или удалить его) или изменить размер почтового ящика. Предполагается, что монитор ожидает ввода данных пользователем (команд) для отправки на сервер. Монитор использует тот же FIFO, что и клиент. Только 1 клиент (монитор может считаться клиентом) в каждый момент времени может получить доступ к fifos при выполнении запроса. - person Francesco Belladonna; 30.10.2011
comment
Запись в FIFO является атомарной... Нет никакого вреда в том, что несколько писателей открыты в одном и том же FIFO. - person Jason; 30.10.2011
comment
Не совсем верно, записи в fifos являются атомарными, пока количество записанных байтов не будет меньше или равно PIPE_BUF. Кстати, я решил проблему, позволив серверу и клиентской программе изменить порядок, в котором они открывают fifos только при запуске (и только один раз). Другого способа не нашел. (Кстати, я не думаю, что это что-то чистое) - person Francesco Belladonna; 30.10.2011
comment
Да, я знал, что размер PIPE_BUF является условием для атомарной записи в FIFO, но я не думал, что вы пытаетесь записать более 512 байт на сообщение, поэтому я не чувствовал необходимости упоминать об этом. .. Кстати, я немного озадачен вашим решением, просто потому, что сервер не будет оставаться заблокированным на open(), как вы просили - монитор откроет fifo1 для записи после открытия fifo2 для чтения, разблокируя сервер. Вам все еще нужно добавить семафор или некоторый объект синхронизации IPC, чтобы заблокировать монитор от открытия fifo1, пока сервер не скажет, что его можно открыть. - person Jason; 30.10.2011

Если вы выполняете open() в правильном порядке, состояния гонки не возникает. (единственной возможной гонкой будет третий процесс, мешающий тому же fifos) Из прекрасного руководства:

«Однако он должен быть открыт с обоих концов одновременно, прежде чем вы сможете выполнять с ним какие-либо операции ввода или вывода. Открытие FIFO для чтения обычно блокируется, пока какой-либо другой процесс не откроет тот же FIFO для записи, и наоборот».

Это означает, что порядок

{ процесс 1: открыть (fifo1, RO); процесс2: открыть (fifo1, WO); }

...

{ процесс 1: открыть (fifo2, WO); процесс2: открыть (fifo2, RO); }

всегда будет успешным (при отсутствии голодания процесса) Порядок операций в каждом fifo не имеет значения; для fifo1 первым может идти либо процесс 1, либо процесс 2 (и он будет заблокирован до тех пор, пока другая сторона не добьется успеха).

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

#define FIFO1 "fifo1"
#define FIFO2 "fifo2"

void do_master(void);
void do_slave(void);
void randsleep(unsigned max);

/************************************/
void do_master(void)
{
int in,out, rc;
char buff[20];

randsleep(5);
mkfifo(FIFO1, 0644);
randsleep(7);
mkfifo(FIFO2, 0644);

out = open(FIFO2, O_WRONLY);
if (out == -1) {
    fprintf(stderr, "[Master]: failed opening output\n" );
    return;
    }
fprintf(stderr, "[Master]: opened output\n" );
in = open(FIFO1, O_RDONLY);
if (in == -1)  {
    fprintf(stderr, "[Master]: failed opening input\n" );
    close(out);
    return;
    }
fprintf(stderr, "[Master]: opened input\n" );

rc = write( out, "M2S\n\0" , 5);
fprintf(stderr, "[Master]: wrote %d\n", rc );

rc = read( in, buff , sizeof buff);
fprintf(stderr, "[Master]: read %d: %s\n", rc, buff );
unlink(FIFO1);
unlink(FIFO2);
}
/***********************************/
void do_slave(void)
{
int in,out, rc;
unsigned iter=0;
char buff[20];

loop1:
in = open(FIFO2, O_RDONLY);
if (in == -1) {
    fprintf(stderr, "[Slave%u]: failed opening input\n", ++iter );
    randsleep(2);
    goto loop1;
    }
fprintf(stderr, "[Slave]: opened input\n" );

loop2:
out = open(FIFO1, O_WRONLY);
if (out == -1) {
    fprintf(stderr, "[Slave%u]: failed opening output\n", ++iter );
    randsleep(3);
    goto loop2;
    }
fprintf(stderr, "[Slave]: opened output\n" );

rc = write( out, "S2M\n\0" , 5);
fprintf(stderr, "[Slave]: wrote %d\n", rc );

rc = read( in, buff , sizeof buff);
fprintf(stderr, "[Slave]: read %d:%s\n", rc, buff );
}
/*************************************/
void randsleep(unsigned max)
{
unsigned val;
val = rand();
val %= max;
sleep(val);
return;
}
/*************************************/
int main(void)
{
int rc;

switch (rc=fork()) {
    case -1: exit(1); break;
    case 0: do_slave(); break;
    default: do_master(); break;
    }
exit (0);
}
person wildplasser    schedule 31.10.2011
comment
Я вижу, но я не понимаю, почему у меня возникла проблема с состоянием гонки (у меня была проблема, что fifo был рано открыт в режиме только для записи, прежде чем он был открыт как только для чтения) - person Francesco Belladonna; 31.10.2011
comment
Ну, мы тоже не знаем, пока вы не покажете нам реальный код. Открытие на любом конце трубы должно блокироваться до тех пор, пока другой конец не будет открыт. Добавление проверок, включающих /proc/, readdir(), stat() и т. д., только открывает окно для других гонок. Open() является атомарным; два open() с каждой стороны являются примитивом синхронизации. (или один из них блокируется навечно) - person wildplasser; 31.10.2011
comment
Я не могу показать код, это большой проект (ну там не менее 20 файлов), иначе я бы это сделал. - person Francesco Belladonna; 31.10.2011
comment
Ну, вы могли бы сократить его до минимального примера, демонстрирующего вашу проблему. Поскольку промежуточного кода нет (см. мой ответ), это должно быть довольно тривиально. - person wildplasser; 31.10.2011

Рассмотрите возможность использования сокетов домена Unix типа SOCK_STREAM. Сервер bind изменит свой сокет на имя в файловой системе. Каждый клиент получает собственное двунаправленное соединение с сервером.

person jilles    schedule 28.10.2011

По крайней мере, то, что я нашел до сих пор, заставляет меня понять, что нет способа определить, открыт ли файл fifo, за исключением того, что вы тоже открываете его.

Редактировать 1:

Как сказал Джейсон, есть два способа (однако оба не разрешены в моей домашней работе):

1) *Вы можете выполнить поиск через /proc/PID/fd (замените PID числовым идентификатором процесса), чтобы увидеть, какие клиентские процессы уже открыли ваш FIFO* 2) Другой вариант вызвать fuser на вашем FIFO

Однако первый требует чего-то, чего не хотят преподаватели: наблюдения внутри proc/PID/fd. Второй, который я слышал, требует привилегий root, и это снова то, что я не могу сделать. Надеюсь, это поможет кому-то еще в будущем.

person Francesco Belladonna    schedule 30.10.2011
comment
Технически, если вы хотите знать, был ли FIFO открыт другим процессом, и вы не хотите пытаться открыть сам файл, чтобы избежать блокировки, вы можете выполнить поиск через /proc/*PID*/fd (замените < i>PID с числовым идентификатором процесса), чтобы увидеть, какие клиентские процессы уже открыли ваш FIFO... значения внутри /proc/*PID*/fd являются символическими ссылками на файлы, открытые этим процессом. Ваше время поиска может быть сокращено еще больше, если вы знаете, каковы значения PID клиента, в противном случае вы можете просто выполнить поиск методом грубой силы по всем запущенным процессам. - person Jason; 30.10.2011
comment
Другой вариант - вызвать fuser в вашем FIFO... вы можете использовать popen(), чтобы открыть канал дочернему процессу, который выполнит эту команду, а затем вернет результаты. - person Jason; 30.10.2011
comment
М-м, это интересно, и, возможно, это правильные ответы. Однако учитель прямо сказал не использовать /proc/PID/fd (ну вы не могли этого знать, но вопрос не касается домашнего задания). Что касается фьюзера, я слышал, что он требует привилегий root (поэтому я не могу его использовать). Это правда? - person Francesco Belladonna; 31.10.2011
comment
Нет никаких проблем с использованием самого fuser как не-root ... основные проблемы с использованием fuser без root-привилегий заключаются в том, что процессы, не принадлежащие текущему пользователю, могут не отображаться в ответе от fuser. - person Jason; 31.10.2011
comment
Ммм, это может быть проблемой, потому что я не знаю, кто запускает сервер - person Francesco Belladonna; 31.10.2011
comment
Вы должны запустить сервер с тех пор, как ваш монитор запустил сервер ... поэтому процессы сервера и монитора - это те, которые вы запустили, и вы владеете ими. - person Jason; 31.10.2011
comment
Да, но по какой-то причине они могли запустить его как root и после перезапустить монитор как обычный пользователь (сервер не обязательно запускается, если монитор закрывается). - person Francesco Belladonna; 31.10.2011