Главная страница  Взаимодействие нетривиальных процессов 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 [ 114 ] 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186

Инициализация семафоров

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 в качестве одного из аргументов. Эта функция возвращает дескриптор,



1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 [ 114 ] 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186

© 2000 - 2024 ULTRASONEX-AMFODENT.RU.
Копирование материалов разрешено исключительно при условии цититирования.