Главная страница Взаимодействие нетривиальных процессов Инициализация семафоров 20-24 Инициализируются четыре семафора, размещаемые в объекте разделяемой памяти. Второй аргумент seni init всегда делается ненулевым, поскольку семафоры будут использоваться совместно несколькими процессами. Ожидание сообщения, вывод его содержимого 25-36 Первая половина цикла for написана по стандартному алгоритму потребителя: ожидание изменения семафора nstored, установка блокировки для семафора mutex, обработка данных, увеличение значения семафора nempty. Обработка переполнений 37-43 При каждом проходе цикла мы проверяем наличие возникших переполнений. Сравнивается текущее значение noverf 1 ows с предыдущим. Если значение изменилось, оно выводится на экран и сохраняется. Обратите внимание, что значение считывается с заблокированным взаимным исключением noverf lowmutex, но блокировка снимается перед сравнением и выводом значения. Идея в том, что нужно всегда следовать общему правилу минимизации количества операций, выполняемых с заблокированным взаимным исключением. В листинге 13.10 приведен текст программы-клиента. Листинг 13.10. Клиент, помещающий сообщения в разделяемую память pxshtti/client2.c 1 finclude cliserv2.h 2 int 3 mainCint argc. char **argv) 5 int fd. i. nloop. nusec: 6 pid t pid: 7 char mesg[MESGSI2E]: 8 long offset: 9 struct shmstruct *ptr: 10 if (argc != 4) 11 err quit( usage: client2 <name> <floops> <fusec> ): 12 nloop = atoi(argv[2]): 13 nusec = atoi(argv[3]): 14 /* открытие и отображение объекта разделяемой памяти, созданного сервером заранее */ 15 fd = Shm open(Px ipc name(argv[l]). 0 RDWR. FILE MODE): 16 ptr = Mmap(NULL. sizeof(struct shmstruct). PROT READ PROT WRITE. 17 MAP SHARED. fd. 0): 18 Close(fd): 19 pid = getpidO: 20 for (i = 0: i < nloop: i++) { 21 SIeep us(nusec): 22 snprintf(mesg. MESGSIZE. pid %]&. message %й . (long) pid. i): 23 if (sem trywait(&ptr->nempty) == -1) { 24 if (errno EAGAIN) { прооолжение -Ar Листинг 13.10 (продолжение) 25 Setti wa i t (&pt г - >nover f 1 owrautex): 26 ptr->noverflow++: 27 Setti post(&ptr->noverflowmutex): 28 continue: 29 } else 30 err sys( setti trywait error ): 31 } 32 Setti wait(&ptr->ttiutex): 33 offset = ptr->ttisgoff[ptr->nput]: 34 if (++(ptr->nput) >= NMESG) 35 ptr->nput = 0: /* циклический буфер */ 36 Sem post(&ptr->mutex): 37 strcpy(&ptr->ttisgdata[offset]. mesg): 38 Sem post(&ptr->nstored): 39 } 40 exitCO): 41 } Аргументы командной строки 10-13 Первый аргумент командной строки задает имя объекта разделяемой памяти; второй - количество сообщений, которые должны быть отправлены серверу данным клиентом. Последний аргумент задает паузу перед отправкой очередного сообщения (в микросекундах). Мы сможем получить ситуацию переполнения, запустив одновременно несколько экземпляров клиентов и указав небольшое значение для этой паузы. Таким образом мы сможем убедиться, что сервер корректно обрабатывает ситуацию переполнения. Открытие и отображение разделяемой памяти 14-18 Мы открываем объект разделяемой памяти, предполагая, что он уже создан и проинициализирован сервером, а затем отображаем его в адресное пространство процесса. После этого дескриптор может быть закрыт. Отправка сообщений 19-31 Клиент работает по простому алгоритму программы-производителя, но вместо вызова seni wait(nempty), который приводил бы к блокированию клиента в случае отсутствия места в буфере для следующего сообщения, мы вызываем sem t rywa i t - эта функция не блокируется. Если значение семафора нулевое, возвращается ошибка EAGAIN. Мы обрабатываем эту ошибку, увеличивая значение счетчика переполнений. ПРИМЕЧАНИЕ sleep us - функция из листингов С.9 и СЮ [21]. Она приостанавливает выполнение программы на заданное количество микросекунд. Реализуется вызовом select или poll. 32-37 Пока заблокирован семафор mutex, мы можем получить значение сдвига (offset) и увеличить счетчик nput, но мы снимаем блокировку с этого семафора перед операцией копирования сообщения в разделяемую память. Когда семафор заблокирован, должны выполняться только самые необходимые операции. Сначала запустим сервер в фоновом режиме, а затем запустим один экземпляр программы-клиента, указав 50 сообщений и нулевую паузу между ними: Solaris % server2 serv2 & [2] 27223 Solaris % client2 serv2 50 0 Index = 0: pid 27224: message 0 Index =1: pid 27224: message 1 Index - 2: pid 27224: message 2 продолжает в том же духе Index =15: pid 27224: message 47 Index = 0: pid 27224; message 48 index =1; pid 27224; message 49 нет утерянных сообщений Но если мы запустим программу-клиент еще раз, то мы увидим возникновение переполнений. Solaris % client2 serv2 50 О index = 2: pid 27228: message 0 index = 3: pid 27228: message 1 пока все в порядке index = 10: pid 27228: message 8 index = 11; pid 27228; message 9 noverflow = 25 утеряно 25 сообщений index = 12: pid 27228: message 10 index = 13: pid 27228: message 11 нормально обрабатываются сообщения 12-22 index = 9; pid 27228; message 23 index = 10: pid 27228; message 24 Ha этот раз клиент успешно отправил сообщения 0-9, которые были получены и выведены сервером. Затем клиент снова получил управление и поместил сообщения 10-49, но места хватило только для первых 15, а последующие 25 (с 25 по 49) не были сохранены из-за переполнения: Очевидно, что в этом примере переполнение возникло из-за того, что мы потребовали от клиента отправлять сообщения так часто, как только возможно, не делая между ними пауз. В реальном мире такое случается редко. Целью этого примера было продемонстрировать обработку ситуаций, в которых места для очередного сообщения нет, но клиент не должен блокироваться. Такая ситуация может возникнуть, разумеется, не только при использовании разделяемой памяти, но и при использовании очередей сообщений, именованных и неименованных каналов. ПРИМЕЧАНИЕ - Переполнение приемного буфера данными встречается не только в этом примере. В разделе 8.13 [24] обсуждалась такая ситуация в связи с дейтаграммами UDP и приемным буфером сокета UDP. В разделе 18.2 [23] подробно рассказывается о том, как доменные сокеты Unix возвращают отправителю ошибку ENOBUFS при переполнении приемного буфера получателя. Это отличает доменные сокеты от протокола UDP. Программа-клиент из листинга 13.10 узнает о переполнении буфера, поэтому если этот код поместить в функцию общего назначения, которую затем будут использовать другие программы, такая функция сможет возвращать ошибку вызывающему процессу при переполнении буфера сервера. 13.7. Резюме Разделяемая память Posix реализуется с помощью функции mmap, обсуждавшейся в предыдущей главе. Сначала вызывается функция shm open с именем объекта Posix IPC в качестве одного из аргументов. Эта функция возвращает дескриптор,
|
© 2000 - 2024 ULTRASONEX-AMFODENT.RU.
Копирование материалов разрешено исключительно при условии цититирования. |