Средства межпроцессной связи СОДЕРЖАНИЕ 1. Введение 2. Очереди сообщений 2.1. Использование очередей сообщений 2.2. Создание очередей сообщений 2.2.1. Использование msgget 2.2.2. Программа-пример 2.3. Управление очередями сообщений 2.3.1. Использование msgctl 2.3.2. Программа-пример 2.4. Операции над очередями сообщений 2.4.1. Использование операций 2.4.1.1. Посылка сообщений 2.4.1.2. Прием сообщений 2.4.2. Программа-пример 3. Семафоры 3.1. Использование семафоров 3.2. Создание множеств семафоров 3.3. Управление семафорами 3.3.1. Использование semctl 3.3.2. Программа-пример 3.4. Операции над множествами семафоров 3.4.1. Использование semop 3.4.2. Программа-пример 4. Разделяемые сегменты памяти 4.1. Использование разделяемых сегментов памяти 4.2. Создание разделяемых сегментов памяти 4.3. Управление разделяемыми сегментами памяти 4.3.1. Использование shmctl 4.3.2. Программа-пример 4.4. Операции над разделяемыми сегментами памяти 4.4.1. Использование операций 4.4.1.1. Присоединение сегментов 4.4.1.2. Отсоединение сегментов 4.4.2. Программа-пример 1. ВВЕДЕНИЕ ОС UNIX поддерживает три типа средств межпроцессной связи: Очереди сообщений. Семафоры. Разделяемые сегменты памяти. В данной главе описываются системные вызовы, предназначенные для работы с каждым из них. В главу включено несколько примеров программ, демонстрирующих использование этих системных вызовов. При написании программ главное внимание уделялось их ясности, а не эффективности. Средства межпроцессной связи ОС UNIX имеют много общих черт, подробно описанных на примере очередей сообщений. В разделах, посвященных семафорам и разделяемым сегментам памяти, основное внимание сосредоточено на специфике именно этих механизмов. 2. ОЧЕРЕДИ СООБЩЕНИЙ Очереди сообщений как средство межпроцессной связи дают возмож- ность процессам взаимодействовать, обмениваясь данными. Данные передаются между процессами дискретными порциями, называемыми сообщениями. Процессы, использующие этот тип межпроцессной свя- зи, могут выполнять две операции: Послать сообщение. Принять сообщение. Процесс, прежде чем послать или принять какое-либо сообщение, должен попросить систему породить необходимые для обработки данных операций программные механизмы. Процесс делает это при помощи системного вызова msgget(2). Обратившись к нему, процесс становится владельцем/создателем некоторого средства обмена со- общениями; кроме того, процесс специфицирует первоначальные права на выполнение операций для всех процессов, включая себя. Впоследствии владелец/создатель может уступить право собствен- ности или изменить права на операции при помощи системного вы- зова msgctl(2), однако на протяжении всего времени существова- ния средства обмена сообщениями создатель остается создателем. Другие процессы, обладающие соответствующими правами, для вы- полнения различных управляющих действий также могут использо- вать системный вызов msgctl(2). Процессы, имеющие права на операции и пытающиеся послать или принять сообщение, могут приостанавливаться, если выполнение операции не было успешным. В частности это означает, что про- цесс, пытающийся послать сообщение, может ожидать, пока про- цесс-получатель не будет готов; наоборот, получатель может ждать отправителя. Если указано, что процесс в таких ситуациях должен приостанавливаться, говорят о выполнении над сообщением "операции с блокировкой". Если приостанавливать процесс нельзя, говорят, что над сообщением выполняется "операция без блокиров- ки". Процесс, выполняющий операцию с блокировкой, может быть приос- тановлен до тех пор, пока не будет удовлетворено одно из усло- вий: Операция завершилась успешно. Процесс получил сигнал. Очередь сообщений ликвидирована. Системные вызовы позволяют процессам пользоваться этими возмож- ностями обмена сообщениями. Вызывающий процесс передает систем- ному вызову аргументы, а системный вызов выполняет (успешно или нет) свою функцию. Если системный вызов завершается успешно, он выполняет то, что от него требуется, и возвращает некоторую со- держательную информацию. В противном случае процессу возвраща- ется значение -1, известное как признак ошибки, а внешней пере- менной errno присваивается код ошибки. 2.1. Использование очередей сообщений Перед тем, как посылать или принимать сообщения, должны быть созданы очередь сообщений с уникальным идентификатором и ассо- циированная с ней структура данных. Порожденный уникальный идентификатор называется идентификатором очереди сообщений (msqid); он используется для обращений к очереди сообщений и ассоциированной структуре данных. Говоря об очереди сообщений следует иметь в виду, что реально в ней хранятся не сами сообщения, а их описатели, имеющие следую- щую структуру: struct msg { struct msg *msg_next; /* Указатель на следующее сообщение */ long msg_type; /* Тип сообщения */ short msg_ts; /* Размер текста сообщения */ short msg_spot; /* Адрес текста сообщения */ }; Приведенное определение находится во включаемом файле . С каждым уникальным идентификатором очереди сообщений ассоции- рована одна структура данных, которая содержит следующую инфор- мацию: struct msqid_ds { struct ipc_perm msg_perm; /* Структура прав на выполнение операций */ struct msg *msg_first; /* Указатель на первое сообщение в очереди */ struct msg *msg_last; /* Указатель на последнее сообщение в очереди */ ushort msg_cbytes; /* Текущее число байт в очереди */ ushort msg_qnum; /* Число сообщений в очереди */ ushort msg_qbytes; /* Макс. допустимое число байт в очереди */ ushort msg_lspid; /* Ид-р последнего отправителя */ ushort msg_lrpid; /* Ид-р последнего получателя */ time_t msg_stime; /* Время последнего отправления */ time_t msg_rtime; /* Время последнего получения */ time_t msg_ctime; /* Время последнего изменения */ }; Это определение также находится во включаемом файле . Поле msg_perm данной структуры использует в качестве шаблона структуру ipc_perm, которая задает права на операции с сообщениями и определяется так: struct ipc_perm { ushort uid; /* Идентификатор пользователя */ ushort gid; /* Идентификатор группы */ ushort cuid; /* Идентификатор создателя очереди */ ushort cgid; /* Ид-р группы создателя очереди */ ushort mode; /* Права на чтение/запись */ ushort seq; /* Последовательность номеров используемых слотов */ key_t key; /* Ключ */ }; Последнее определение находится во включаемом файле , общем для всех средств межпроцессной связи. Если в аргументе msgflg системного вызова msgget(2) установлен только флаг IPC_CREAT, выполняется одно из двух действий: Порождается новый идентификатор msqid и создаются ассо- циированные с ним очередь сообщений и структура данных. Возвращается существующий идентификатор msqid, с кото- рым уже ассоциированы очередь сообщений и структура данных. Действие определяется по значению аргумента key. Если еще не существует идентификатора msqid со значением ключа key, выпол- няется первое действие, то есть для данного ключа выделяется новый уникальный идентификатор и создаются ассоциированные с ним очередь сообщений и структура данных (при условии, что не будет превышен соответствующий системный лимит). Кроме того, можно специфицировать ключ key со значением IPC_P- RIVATE (0). Если указан такой "личный" ключ, для него обяза- тельно выделяется новый уникальный идентификатор и создаются ассоциированные с ним очередь сообщений и структура данных (при условии, что это не приведет к превышению системного лимита). При выполнении утилиты ipcs поле KEY для подобного идентифика- тора msqid из соображений секретности содержит нули. Если идентификатор msqid со специфицированным значением ключа key уже существует, выполняется второе действие, то есть возв- ращается ассоциированный идентификатор. Если Вы желаете тракто- вать возвращение существующего идентификатора как ошибку, в пе- редаваемом системному вызову аргументе msgflg нужно установить флаг IPC_EXCL. Более детально использование данного системного вызова обсужда- ется в пункте Использование msgget. При выполнении первого действия процесс, вызвавший msgget(2), становится владельцем/создателем очереди сообщений; соответст- венно этому инициализируется ассоциированная структура данных. Напомним, что владелец очереди может быть изменен, однако про- цесс-создатель всегда остается создателем; см. пункт Управление очередями сообщений. При создании очереди сообщений определяют- ся также начальные права на выполнение операций над ней. После того, как созданы очередь сообщений с уникальным иденти- фикатором и ассоциированная с ней структура данных, можно ис- пользовать системные вызовы семейства msgop(2) (операции над очередями сообщений) и msgctl(2) (управление очередями сообще- ний). Операции, как упоминалось выше, заключаются в посылке и приеме сообщений. Для каждой из этих операций предусмотрен системный вызов, msgsnd() и msgrcv() соответственно. Более детально дан- ные системные вызовы описаны в пункте Операции над очередями сообщений. Для управления очередями сообщений используется системный вызов msgctl(2). Он позволяет выполнять следующие управляющие дейст- вия: Опросить содержимое структуры данных, ассоциированной с идентификатором очереди сообщений msqid. Изменить права на выполнение операций над очередью со- общений. Изменить максимально допустимое число байт (msg_qbytes) в очереди сообщений, определяемой идентификатором msqid. Удалить из системы идентификатор msqid, ликвидировать очередь сообщений и ассоциированную с ней структуру данных. Более детально системный вызов msgctl(2) описан в пункте Управ- ление очередями сообщений. 2.2. Создание очередей сообщений В данном пункте детально описывается системный вызов msgget(2) и приводится программа-пример, иллюстрирующая его использова- ние. 2.2.1. Использование msgget В статье msgget(2) Справочника программиста синтаксис данного системного вызова описан так: #include #include #include int msgget (key, msgflg) key_t key; int msgflg; Тип key_t описан во включаемом файле при помощи typedef как целый тип. Целочисленное значение, возвращаемое в случае успешного завер- шения системного вызова, есть идентификатор очереди сообщений (msqid). В случае неудачи результат равен -1. Новый идентификатор msqid, очередь сообщений и ассоциированная с ней структура данных выделяются в каждом из двух случаев: Значение ключа key равно IPC_PRIVATE. Ключ key еще не имеет ассоциированного с ним идентифи- катора очереди сообщений и выражение (msgflg & IPC_CREAT) истинно. Целое число, передаваемое в качестве аргумента msgflg, удобно рассматривать как восьмеричное. Оно задает Права на выполнение операций. Флаги. Права на выполнение операций есть права на чтение из очереди и запись в нее (то есть на прием/посылку сообщений) для владель- ца, членов группы и прочих пользователей. В следующей таблице сведены возможные элементарные права и соответствующие им вось- меричные значения: ~---------------------- --------------------- │ Права на операции │Восьмеричное значение│ ---------------------- --------------------- │ Чтение для владельца │ 0400 │ │ Запись для владельца │ 0200 │ │ Чтение для группы │ 0040 │ │ Запись для группы │ 0020 │ │ Чтение для остальных │ 0004 │ │ Запись для остальных │ 0002 │ ---------------------- --------------------- В каждом конкретном случае нужная комбинация прав задается как результат побитного ИЛИ значений, соответствующих элементарным правам. Так, правам на чтение/запись для владельца и на чтение для членов группы и прочих пользователей соответствует восьме- ричное число 0644. Отметим полную аналогию с правами доступа к файлам. Флаги определены во включаемом файле . В следующей таблице сведены мнемонические имена флагов и соответствующие им восьмеричные значения: ~----------- --------------------- │ Флаг │Восьмеричное значение│ ----------- --------------------- │ IPC_CREAT │ 0001000 │ │ IPC_EXCL │ 0002000 │ ----------- --------------------- Значение аргумента msgflg в целом является, следовательно, ре- зультатом побитного ИЛИ (операция | в языке C) прав на выполне- ние операций и флагов, например: msqid = msgget (key, (IPC_CREAT | 0644)); msqid = msgget (key, (IPC_CREAT | IPC_EXCL | 0400)); Как уже указывалось, системный вызов вида msqid = msgget (IPC_PRIVATE, msgflg); приведет к попытке выделения нового идентификатора очереди со- общений и ассоциированной информации независимо от значения ар- гумента msgflg. Попытка может быть неудачной только из-за пре- вышения системного лимита на общее число очередей сообщений, задаваемого настраиваемым параметром MSGMNI [см. master(4)]. При использовании флага IPC_EXCL в сочетании с IPC_CREAT сис- темный вызов msgget(2) завершается неудачей в том и только в том случае, когда с указанным ключом key уже ассоциирован иден- тификатор. Флаг IPC_EXCL необходим, чтобы предотвратить ситуа- цию, когда процесс полагает, что получил новый (уникальный) идентификатор очереди сообщений, в то время как это не так. Иными словами, когда используются и IPC_CREAT и IPC_EXCL, при успешном завершении системного вызова обязательно возвращается новый идентификатор msqid. В статье msgget(2) Справочника программиста описывается началь- ное значение ассоциированной структуры данных, формируемое при успешном завершении системного вызова. В статье содержится пе- речень условий, приводящих к ошибкам, и соответствующих им мне- монических имен для значений переменной errno. 2.2.2. Программа-пример В данном пункте приводится программа-пример, управляемая пос- редством меню. Она позволяет поупражняться со всевозможными комбинациями в использовании системного вызова msgget(2), прос- ледить, как передаются аргументы и получаются результаты. Имена переменных выбраны максимально близкими к именам, исполь- зуемым в спецификации синтаксиса системного вызова, что облег- чает чтение программы. Выполнение программы начинается с приглашения ввести шестнадца- теричный ключ key, восьмеричный код прав на операции и, нако- нец, выбираемую при помощи меню комбинацию флагов (строки 14- 30). В меню предлагаются все возможные комбинации, даже бесс- мысленные, что позволяет при желании проследить за реакцией на ошибку. Затем выбранные флаги комбинируются с правами на операции, пос- ле чего выполняется системный вызов, результат которого помеща- ется в переменную msqid (строка 49). Контроль успешного завер- шения системного вызова производится в строке 50. Если значение msqid равно -1, выдается сообщение об ошибке и выводится значе- ние внешней переменной errno (строки 52, 53). Если ошибки не произошло, выводится значение полученного идентификатора очере- ди сообщений (строка 57). Далее приводится текст программы-примера. Здесь и далее номера строк служат только для ссылок и не являются частью программы. 1 /* Программа иллюстрирует 2 возможности системного вызова msgget() 3 (получение идентификатора очереди сообщений) */ 4 #include 5 #include 6 #include 7 #include 8 #include 9 main () 10 { 11 key_t key; /* Тип описан как целое */ 12 int opperm, flags; /* Права на операции и флаги */ 13 int msgflg, msqid; 14 /* Ввести требуемый ключ */ 15 printf ("\nВведите шестнадцатеричный ключ: "); 16 scanf ("%x", &key); 17 /* Ввести права на операции */ 18 printf ("\nВведите права на операции "); 19 printf ("в восьмеричной записи: "); 20 scanf ("%o", &opperm); 21 /* Установить требуемые флаги */ 22 printf ("\nВведите код, соответствущий "); 23 printf ("нужной комбинации флагов:\n"); 24 printf (" Нет флагов = 0\n"); 25 printf (" IPC_CREAT = 1\n"); 26 printf (" IPC_EXCL = 2\n"); 27 printf (" IPC_CREAT и IPC_EXCL = 3\n"); 28 printf (" Выбор = "); 29 /* Получить флаги, которые нужно установить */ 30 scanf ("%d", &flags); 31 /* Проверить значения */ 32 printf ("\nключ = 0x%x, права = 0%o, флаги = %d\n", 33 key, opperm, flags); 34 /* Объединить флаги с правами на операции */ 35 switch (flags) { 36 case 0: /* Флаги не устанавливать */ 37 msgflg = (opperm | 0); 38 break; 39 case 1: /* Установить флаг IPC_CREAT */ 40 msgflg = (opperm | IPC_CREAT); 41 break; 42 case 2: /* Установить флаг IPC_EXCL */ 43 msgflg = (opperm | IPC_EXCL); 44 break; 45 case 3: /* Установить оба флага */ 46 msgflg = (opperm | IPC_CREAT | IPC_EXCL); 47 } 48 /* Выполнить системный вызов msgget */ 49 msqid = msgget (key, msgflg); 50 if (msqid == -1) { 51 /* Сообщить о неудачном завершении */ 52 printf ("\nmsgget завершился неудачей!\n" 53 printf ("Код ошибки = %d\n", errno); 54 } 55 else 56 /* При успешном завершении сообщить msqid */ 57 printf ("\nИдентификатор msqid = %d\n", msqid); 58 exit (0); 59 } 2.3. Управление очередями сообщений В данном пункте детально описывается использование системного вызова msgctl(2) и приводится программа-пример, позволяющая по- упражняться со всеми его возможностями. 2.3.1. Использование msgctl В статье msgctl(2) Справочника программиста синтаксис данного системного вызова описан так: #include #include #include int msgctl (msqid, cmd, buf) int msqid, cmd; struct msqid_ds *buf; При успешном завершении результат равен нулю; в случае неудачи возвращается -1. В качестве аргумента msqid должен выступать идентификатор оче- реди сообщений, предварительно полученный при помощи системного вызова msgget(2). Управляющее действие определяется значением аргумента cmd. До- пустимых значений три: IPC_STAT Поместить информацию о состоянии очереди, содержа- щуюся в структуре данных, ассоциированной с иден- тификатором msqid, в пользовательскую структуру, на которую указывает аргумент buf. IPC_SET В структуре данных, ассоциированной с идентифика- тором msqid, переустановить значения действующих идентификаторов пользователя и группы, прав на операции, максимально допустимого числа байт в очереди. IPC_RMID Удалить из системы идентификатор msqid, ликвидиро- вать очередь сообщений и ассоциированную с ней структуру данных. Чтобы выполнить управляющее действие IPC_SET или IPC_RMID, про- цесс должен иметь действующий идентификатор пользователя, рав- ный либо идентификаторам создателя или владельца очереди, либо идентификатору суперпользователя. Чтобы выполнить действие IPC_STAT, требуется право на чтение. 2.3.2. Программа-пример Назначение переменных, описанных в программе: command Код управляющего действия. choice Используется для выбора поля ассоциированной структуры данных, которое нужно изменить. msqid_ds Структура для хранения информации об очереди. Программа ведет себя следующим образом. Прежде всего предлага- ется ввести допустимый идентификатор очереди сообщений, который заносится в переменную msqid (строки 15, 16). Это значение тре- буется в каждом системном вызове msgctl. Затем нужно ввести код выбранного управляющего действия (строки 17-22); он заносится в переменную command. Если выбрано действие IPC_STAT (код 1), системный вызов выпол- няется (строка 31) и распечатывается информация о состоянии очереди (строки 32-39); в программе распечатываются только те поля структуры, которые могут быть переустановлены. Отметим, что если системный вызов завершается неудачей, распечатывается информация о состоянии очереди на момент последнего успешного выполнения системного вызова. Кроме того, выводится сообщение об ошибке и распечатывается значение переменной errno (строки 90, 91). Если системный вызов завершается успешно, выводится сообщение, уведомляющее об этом, и значение использованного идентификатора очереди сообщений (строки 95, 96). Если выбрано действие IPC_SET (код 2), программа прежде всего получает информацию о текущем состоянии очереди сообщений с за- данным идентификатором (строка 45). Это необходимо, поскольку пример обеспечивает изменение только одного поля за один раз, в то время как системный вызов изменяет всю структуру целиком. Кроме того, если в одно из полей структуры, находящейся в об- ласти памяти пользователя, будет занесено некорректное значе- ние, это может вызвать неудачи в выполнении управляющих дейст- вий, повторяющиеся до тех пор, пока значение поля не будет исп- равлено. Затем программа предлагает ввести код, соответствующий полю структуры, которое должно быть изменено (строки 46-53). Этот код заносится в переменную choice. Далее, в зависимости от указанного поля, программа предлагает ввести то или иное новое значение (строки 54-79). Значение заносится в соответствующее поле структуры данных, расположенной в области памяти пользова- теля, и выполняется системный вызов (строка 81). Если выбрано действие IPC_RMID (код 3), выполняется системный вызов (строка 86), удаляющий из системы идентификатор msqid, очередь сообщений и ассоциированную с ней структуру данных. От- метим, что для выполнения этого управляющего действия аргумент buf не требуется, поэтому его значение может быть заменено ну- лем (NULL). 1 /* Программа иллюстрирует 2 возможности системного вызова msgctl() 3 (управление очередями сообщений) */ 4 #include 5 #include 6 #include 7 #include 8 main () 9 { 10 extern int errno; 11 int msqid, command, choice, rtrn; 12 struct msqid_ds msqid_ds, *buf; 13 buf = &msqid_ds; 14 /* Ввести идентификатор и действие */ 15 printf ("Введите идентификатор msqid: "); 16 scanf ("%d", &msqid); 17 printf ("Введите номер требуемого действия:\n"); 18 printf (" IPC_STAT = 1\n"); 19 printf (" IPC_SET = 2\n"); 20 printf (" IPC_RMID = 3\n"); 21 printf (" Выбор = "); 22 scanf ("%d", &command); 23 /* Проверить значения */ 24 printf ("идентификатор = %d, действие = %d\n", 25 msqid, command); 26 switch (command) { 27 case 1: /* Скопировать информацию 28 о состоянии очереди сообщений 29 в пользовательскую структуру 30 и вывести ее */ 31 rtrn = msgctl (msqid, IPC_STAT, buf); 32 printf ("\n Идентификатор пользователя = %d\n", 33 buf->msg_perm.uid); 34 printf ("\n Идентификатор группы = %d\n", 35 buf->msg_perm.gid); 36 printf ("\n Права на операции = 0%o\n", 37 buf->msg_perm.mode); 38 printf ("\n Размер очереди в байтах = %d\n", 39 buf->msg_qbytes); 40 break; 41 case 2: /* Выбрать и изменить поле (поля) 42 ассоциированной структуры данных */ 43 /* Сначала получить исходное значение 44 структуры данных */ 45 rtrn = msgctl (msqid, IPC_STAT, buf); 46 printf ("\nВведите номер поля, "); 47 printf ("которое нужно изменить:\n"); 48 printf (" msg_perm.uid = 1\n"); 49 printf (" msg_perm.gid = 2\n"); 50 printf (" msg_perm.mode = 3\n"); 51 printf (" msg_qbytes = 4\n"); 52 printf (" Выбор = "); 53 scanf ("%d", &choice); 54 switch (choice) { 55 case 1: 56 printf ("\nВведите ид-р пользователя: "); 57 scanf ("%d", &buf->msg_perm.uid); 58 printf ("\nИд-р пользователя = %d\n", 59 buf->msg_perm.uid); 60 break; 61 case 2: 62 printf ("\nВведите ид-р группы: "); 63 scanf ("%d", &buf->msg_perm.gid); 64 printf ("\nИд-р группы = %d\n", 65 buf->msg_perm.uid); 66 break; 67 case 3: 68 printf ("\nВведите восьмеричный код прав: "); 69 scanf ("%o", &buf->msg_perm.mode); 70 printf ("\nПрава на операции = 0%o\n", 71 buf->msg_perm.mode); 72 break; 73 case 4: 74 printf ("\nВведите размер очереди = "); 75 scanf ("%d", &buf->msg_qbytes); 76 printf ("\nЧисло байт в очереди = %d\n", 77 buf->msg_qbytes); 78 break; 79 } 80 /* Внести изменения */ 81 rtrn = msgctl (msqid, IPC_SET, buf); 82 break; 83 case 3: /* Удалить идентификатор и 84 ассоциированные с ним очередь 85 сообщений и структуру данных */ 86 rtrn = msgctl (msqid, IPC_RMID, NULL); 87 } 88 if (rtrn == -1) { 89 /* Сообщить о неудачном завершении */ 90 printf ("\nmsgctl завершился неудачей!\n"); 91 printf ("\nКод ошибки = %d\n", errno); 92 } 93 else { 94 /* При успешном завершении сообщить msqid */ 95 printf ("\nmsgctl завершился успешно,\n"); 96 printf ("идентификатор = %d\n", msqid); 97 } 98 exit (0); 99 } 2.4. Операции над очередями сообщений В данном пункте детально описывается использование системных вызовов msgsnd() и msgrcv() и приводится программа-пример, поз- воляющая поупражняться со всеми их возможностями. 2.4.1. Использование операций В статье msgop(2) Справочника программиста синтаксис упомянутых системных вызовов описан так: #include #include #include int msgsnd (msqid, msgp, msgsz, msgflg) int msqid; struct msgbuf *msgp; int msgsz, msgflg; int msgrcv (msqid, msgp, msgsz, msgtyp, msgflg) int msqid; struct msgbuf *msgp; long msgtyp; int msgsz, msgflg; 2.4.1.1. Посылка сообщений При успешном завершении системного вызова msgsnd() результат равен нулю; в случае неудачи возвращается -1. В качестве аргумента msqid должен выступать идентификатор оче- реди сообщений, предварительно полученный при помощи системного вызова msgget(2). Аргумент msgp является указателем на структуру в области памяти пользователя, содержащую тип посылаемого сообщения и его текст. Аргумент msgsz специфицирует длину массива символов в структуре данных, указываемой аргументом msgp, то есть длину сообщения. Максимально допустимый размер данного массива определяется сис- темным параметром MSGMAX. Отметим, что значение поля msg_qbytes ассоциированной структуры данных может быть уменьшено с предполагаемой по умолчанию вели- чины MSGMNB при помощи управляющего действия IPC_SET системного вызова msgctl(2), однако впоследствии увеличить его может толь- ко суперпользователь. Аргумент msgflg позволяет специфицировать выполнение над сооб- щением "операции с блокировкой"; для этого флаг IPC_NOWAIT дол- жен быть сброшен (msgflg & IPC_NOWAIT = 0). Блокировка имеет место, если либо текущее число байт в очереди уже равно макси- мально допустимому значению для указанной очереди (то есть зна- чению поля msg_qbytes или MSGMNB), либо общее число сообщений во всех очередях равно максимально допустимому системой (сис- темный параметр MSGTQL). Если в такой ситуации флаг IPC_NOWAIT установлен, системный вызов msgsnd() завершается неудачей и возвращает -1. 2.4.1.2. Прием сообщений При успешном завершении системного вызова msgrcv() результат равен числу принятых байт; в случае неудачи возвращается -1. В качестве аргумента msqid должен выступать идентификатор оче- реди сообщений, предварительно полученный при помощи системного вызова msgget(2). Аргумент msgp является указателем на структуру в области памяти пользователя, содержащую тип принимаемого сообщения и его текст. Аргумент msgsz специфицирует длину принимаемого сообщения. Мож- но указать, что в случае, если значение данного аргумента мень- ше, чем длина сообщения в массиве, должна возникать ошибка (см. описание аргумента msgflg). Аргумент msgtyp используется для выбора из очереди первого со- общения определенного типа. Если значение аргумента равно нулю, запрашивается первое сообщение в очереди, если больше нуля - первое сообщение типа msgtyp, а если меньше нуля - первое сооб- щение наименьшего из типов, которые не превосходят абсолютной величины аргумента msgtyp. Аргумент msgflg позволяет специфицировать выполнение над сооб- щением "операции с блокировкой"; для этого должен быть сброшен флаг IPC_NOWAIT (msgflg & IPC_NOWAIT = 0). Блокировка имеет место, если в очереди сообщений нет сообщения с запрашиваемым типом (msgtyp). Если флаг IPC_NOWAIT установлен и в очереди нет сообщения требуемого типа, системный вызов немедленно заверша- ется неудачей. Аргумент msgflg может также специфицировать, что системный вызов должен заканчиваться неудачей, если размер со- общения в очереди больше значения msgsz; для этого в данном ар- гументе должен быть сброшен флаг MSG_NOERROR (msgflg & MSG_NOERROR = 0). Если флаг MSG_NOERROR установлен, сообщение обрезается до длины, указанной аргументом msgsz. 2.4.2. Программа-пример Назначение переменных, описанных в программе: sndbuf Используется в качестве буфера, содержащего посы- лаемое сообщение (строка 12); шаблон при описании этой переменной - структура данных msgbufl (строки 9-12). Структура msgbufl является почти точной ко- пией структуры msgbuf, описание которой находится во включаемом файле . Единственное раз- личие состоит в том, что длина символьного массива в msgbufl равна максимально допустимому размеру сообщения для данной конфигурации (MSGMAX), в то время как в msgbuf она устанавливается равной еди- нице, чтобы отвечать требованиям компилятора. По этой причине нельзя использовать в пользователь- ской программе описание msgbuf непосредственно, то есть пользователь должен сам определять поля дан- ной структуры. rcvbuf Используется в качестве буфера, содержащего прини- маемое сообщение (строка 12); шаблон при описании этой переменной - структура данных msgbufl (строки 9-12). i Используется как счетчик символов при вводе с кла- виатуры и занесении в массив, а также отслеживает длину сообщения при выполнении системного вызова msgsnd(); кроме того, используется как счетчик при выводе принятого сообщения после выполнения сис- темного вызова msgrcv(). c Содержит символ, возвращаемый функцией getchar() (строка 45). flag При выполнении системного вызова msgsnd() содержит значение, определяющее, нужен ли флаг IPC_NOWAIT (строка 55). flags При выполнении системного вызова msgrcv() содержит значение, определяющее комбинацию флагов IPC_NOWA- IT и MSG_NOERROR (строка 103). choice Содержит признак, определяющий выбранную операцию - посылка или прием сообщения (строка 27). Отметим, что в программе структура данных msqid_ds снабжается указателем на нее (строка 19), указатель соответствующим обра- зом инициализируется (строка 20); это позволяет следить за по- лями ассоциированной структуры данных, которые могут измениться в результате операций над сообщениями. При помощи системного вызова msgctl() (действие IPC_STAT) программа получает значения полей ассоциированной структуры данных и выводит их (строки 74-82 и 145-152). Прежде всего программа запрашивает, какую операцию нужно выпол- нить - послать или принять сообщение. Должно быть введено чис- ло, соответствующее требуемой операции; это число заносится в переменную choice (строки 21-27). Если выбрана операция посылки сообщения, указатель msgp инициа- лизируется адресом структуры данных sndbuf (строка 30). После этого запрашивается идентификатор очереди сообщений, в которую должно быть послано сообщение; идентификатор заносится в пере- менную msqid (строки 31-34). Затем должен быть введен тип сооб- щения; он заносится в поле mtype структуры данных, указываемой значением msgp (строка 34). После этого программа приглашает ввести с клавиатуры текст по- сылаемого сообщения и выполняет цикл, в котором символы читают- ся и заносятся в массив mtext структуры данных (строки 43-46). Ввод продолжается до тех пор, пока не будет обнаружен признак конца файла; для функции getchar() таким признаком является символ CTRL+D, непосредственно следующий за символом возврата каретки. После того как признак конца обнаружен, определяется размер сообщения (строки 47, 48) - он на единицу больше значе- ния счетчика i, поскольку элементы массива, в который заносится сообщение, нумеруются с нуля. Следует помнить, что сообщение будет содержать заключительные символы и, следовательно, будет казаться, что сообщение на три символа короче, чем указывает аргумент msgsz. Чтобы обеспечить пользователю обратную связь, текст сообщения, содержащийся в массиве mtext структуры sndbuf, немедленно выво- дится (строки 49-51). Следующее, и последнее, действие заключается в определении, должен ли быть установлен флаг IPC_NOWAIT. Чтобы выяснить это, программа предлагает ввести 1, если флаг установить нужно, или любое другое число, если не нужно (строки 52-59). Введенное значение заносится в переменную flag. Если введена единица, ар- гумент msgflg полагается равным IPC_NOWAIT, в противном случае msgflg устанавливается равным нулю. После этого выполняется системный вызов msgsnd() (строка 63). Если вызов завершается неудачей, выводится сообщение об ошибке, а также ее код (строки 64-66). Если вызов завершается успешно, печатается возвращенное им значение, которое должно быть равно нулю (строки 69-71). При каждой успешной посылке сообщения обновляются три поля ас- социированной структуры данных. Изменения можно описать следую- щим образом: msg_qnum Определяет общее число сообщений в очереди; в ре- зультате выполнения операции увеличивается на еди- ницу. msg_lspid Содержит идентификатор процесса, который последним послал сообщение; полю присваивается соответствую- щий идентификатор. msg_stime Содержит время последней посылки сообщения, время измеряется в секундах, начиная с 00:00:00 1 января 1970 года (по Гринвичу). После каждой успешной операции посылки сообщения значения этих полей выводятся (строки 76-82) Если указано, что требуется принять сообщение, начальное значе- ние указателя msgp устанавливается равным адресу структуры дан- ных rcvbuf (строка 87). Запрашивается код требуемой комбинации флагов, который заносит- ся в переменную flags (строки 94-103). Переменная msgflg уста- навливается в сответствии с выбранной комбинацией (строки 104- 119). В заключение запрашивается, сколько байт нужно принять; указан- ное значение заносится в переменную msgsz (строки 120-123). После этого выполняется системный вызов msgrcv() (строка 130) Если вызов завершается неудачей, выводится сообщение об ошибке, а также ее код (строки 131-133). Если вызов завершается успеш- но, программа сообщает об этом, а также выводит размер и текст сообщения (строки 135-144). При каждом успешном приеме сообщения обновляются три поля ассо- циированной структуры данных. Изменения можно описать следующим образом: msg_qnum Определяет общее число сообщений в очереди; в ре- зультате выполнения операции уменьшается на едини- цу. msg_lrpid Содержит идентификатор процесса, который последним получил сообщение; полю присваивается соответст- вующий идентификатор. msg_rtime Содержит время последнего получения сообщения, время измеряется в секундах, начиная с 00:00:00 1 января 1970 года (по Гринвичу). Далее следует текст программы. 1 /* Программа иллюстрирует 2 возможности системных вызовов msgsnd() и msgrcv() 3 (операции над очередями сообщений) */ 4 #include 5 #include 6 #include 7 #include 8 #define MAXTEXTSIZE 8192 9 struct msgbufl { 10 long mtype; 11 char mtext [MAXTEXTSIZE]; 12 } sndbuf, rcvbuf, *msgp; 13 main () 14 { 15 extern int errno; 16 int flag, flags, choice, rtrn, i, c; 17 int rtrn, msqid, msgsz, msgflg; 18 long msgtyp; 19 struct msqid_ds msqid_ds, *buf; 20 buf = &msqid_ds; 21 /* Выбрать требуемую операцию */ 22 printf ("\nВведите код, соответствующий "); 23 printf ("посылке или приему сообщения:\n"); 24 printf (" Послать = 1\n"); 25 printf (" Принять = 2\n"); 26 printf (" Выбор = "); 27 scanf ("%d", &choice); 28 if (choice == 1) { 29 /* Послать сообщение */ 30 msgp = &sndbuf; /* Указатель на структуру */ 31 printf ("\nВведите идентификатор "); 32 printf ("очереди сообщений,\n"); 33 printf ("в которую посылается сообщение: "); 34 scanf ("%d", &msqid); 35 /* Установить тип сообщения */ 36 printf ("\nВведите положительное число - "); 37 printf ("тип сообщения: "); 38 scanf ("%d", &msgp->mtype); 39 /* Ввести посылаемое сообщение */ 40 printf ("\nВведите сообщение: \n"); 41 /* Управляющая последовательность CTRL+D 42 завершает ввод сообщения */ 43 /* Прочитать символы сообщения 44 и поместить их в массив mtext */ 45 for (i = 0; ((c = getchar ()) != EOF); i++) 46 sndbuf.mtext [i] = c; 47 /* Определить размер сообщения */ 48 msgsz = i + 1; 49 /* Выдать текст посылаемого сообщения */ 50 for (i = 0; i < msgsz; i++) 51 putchar (sndbuf.mtext [i]); 52 /* Установить флаг IPC_NOWAIT, если это нужно */ 53 printf ("\nВведите 1, если хотите установить "); 54 printf ("флаг IPC_NOWAIT: "); 55 scanf ("%d", &flag); 56 if (flag == 1) 57 msgflg = IPC_NOWAIT; 58 else 59 msgflg = 0; 60 /* Проверить флаг */ 61 printf ("\nФлаг = 0%o\n", msgflg); 62 /* Послать сообщение */ 63 rtrn = msgsnd (msqid, msgp, msgsz, msgflg); 64 if (rtrn == -1) { 65 printf ("\nmsgsnd завершился неудачей!\n"); 66 printf ("Код ошибки = %d\n", errno); 67 } 68 else { 69 /* Вывести результат; при успешном 70 завершении он должен равняться нулю */ 71 printf ("\nРезультат = %d\n", rtrn); 72 /* Вывести размер сообщения */ 73 printf ("\nРазмер сообщения = %d\n", msgsz); 74 /* Опрос измененной структуры данных */ 75 msgctl (msqid, IPC_STAT, buf); 76 /* Вывести изменившиеся поля */ 77 printf ("Число сообщений в очереди = %d\n", 78 buf->msg_qnum); 79 printf ("Ид-р последнего отправителя = %d\n", 80 buf->msg_lspid); 81 printf ("Время последнего отправления = %d\n", 82 buf->msg_stime); 83 } 84 } 85 if (choice == 2) { 86 /* Принять сообщение */ 87 msgp = &rcvbuf; 88 /* Определить нужную очередь сообщений */ 89 printf ("\nВведите ид-р очереди сообщений: "); 90 scanf ("%d", &msqid); 91 /* Определить тип сообщения */ 92 printf ("\nВведите тип сообщения: "); 93 scanf ("%d", &msgtyp); 94 /* Сформировать управляющие флаги 95 для требуемых действий */ 96 printf ("\nВведите код, соответствущий "); 97 printf ("нужной комбинации флагов:\n"); 98 printf (" Нет флагов = 0\n"); 99 printf (" MSG_NOERROR = 1\n"); 100 printf (" IPC_NOWAIT = 2\n"); 101 printf (" MSG_NOERROR и IPC_NOWAIT = 3\n"); 102 printf (" Выбор = "); 103 scanf ("%d", &flags); 104 switch (flags) { 105 /* Установить msgflg как побитное ИЛИ 106 соответствующих констант */ 107 case 0: 108 msgflg = 0; 109 break; 110 case 1: 111 msgflg = MSG_NOERROR; 112 break; 113 case 2: 114 msgflg = IPC_NOWAIT; 115 break; 116 case 3: 117 msgflg = MSG_NOERROR | IPC_NOWAIT; 118 break; 119 } 120 /* Определить, какое число байт принять */ 121 printf ("\nВведите число байт, которое "); 122 printf ("нужно принять (msgsz): "); 123 scanf ("%d", &msgsz); 124 /* Проверить значение аргументов */ 125 printf ("\nИдентификатор msqid = %d\n", msqid); 126 printf ("Тип сообщения = %d\n", msgtyp); 127 printf ("Число байт = %d\n", msgsz); 128 printf ("Флаги = %o\n", msgflg); 129 /* Вызвать msgrcv для приема сообщения */ 130 rtrn = msgrcv (msqid, msgp, msgsz, msgtyp, msgflg); 131 if (rtrn == -1) { 132 printf ("\nmsgrcv завершился неудачей!\n"); 133 printf ("Код oшибки = %d\n", errno); 134 } 135 else { 136 printf ("\nmsgrcv завершился успешно,\n"); 137 printf ("идентификатор очереди = %d\n", msqid); 138 /* Напечатать число принятых байт, 139 оно равно возвращаемому значению */ 140 printf ("Принято байт: %d\n", rtrn); 141 /* Распечатать принятое сообщение */ 142 for (i = 0; i < rtrn; i++) 143 putchar (rcvbuf.mtext [i]); 144 } 145 /* Опрос ассоциированной структуры данных */ 146 msgctl (msqid, IPC_STAT, buf); 147 printf ("\nЧисло сообщений в очереди = %d\n", 148 buf->msg_qnum); 149 printf ("Ид-р последнего получателя = %d\n", 150 buf->msg_lrpid); 151 printf ("Время последнего получения = %d\n", 152 buf->msg_rtime); 153 } 154 exit (0); 155 } 3. СЕМАФОРЫ Рассматриваемые в данном разделе средства позволяют процессам взаимодействовать, изменяя значения объектов, называемых сема- форами. Значение семафора - это целое число в диапазоне от 0 до 32767. Поскольку во многих приложениях требуется более одного семафора, ОС UNIX предоставляет возможность создавать множества семафоров. Их максимальный размер ограничен системным парамет- ром SEMMSL. Множества семафоров создаются при помощи системного вызова semget(2). Процесс, выполнивший системный вызов semget(2), становится вла- дельцем/создателем множества семафоров. Он определяет, сколько будет семафоров в множестве; кроме того, он специфицирует пер- воначальные права на выполнение операций над множеством для всех процессов, включая себя. Впоследствии данный процесс может уступить право собственности или изменить права на операции при помощи системного вызова semctl(2), предназначенного для управ- ления семафорами, однако на протяжении всего времени существо- вания множества семафоров создатель остается создателем. Другие процессы, обладающие соответствующими правами, для выполнения прочих управляющих действий также могут использовать системный вызов semctl(2). Над каждым семафором, принадлежащим множеству, при помощи сис- темного вызова semop(2) можно выполнить любую из трех операций: Увеличить значение. Уменьшить значение. Дождаться обнуления. Для выполнения первых двух операций у процесса должно быть пра- во на изменение, для выполнения третьей достаточно права на чтение. Чтобы увеличить значение семафора, системному вызову semop(2) следует передать требуемое число. Чтобы уменьшить зна- чение семафора, нужно передать требуемое число, взятое с обрат- ным знаком; если результат получается отрицательным, операция не может быть успешно выполнена. Для третьей операции нужно пе- редать 0; если текущее значение семафора отлично от нуля, опе- рация не может быть успешно выполнена. Операции могут снабжаться флагами. Флаг SEM_UNDO означает, что операция выполняется в проверочном режиме, то есть требуется только узнать, можно ли успешно выполнить данную операцию. При отсутствии флага IPC_NOWAIT системный вызов semop(2) может быть приостановлен до тех пор, пока значение семафора, благода- ря действиям другого процесса, не позволит успешно завершить операцию (ликвидация множества семафоров также приведет к за- вершению системного вызова). Подобные операции называются "опе- рациями с блокировкой". С другой стороны, если обработка завер- шается неудачей и не указано, что выполнение процесса должно быть приостановлено, операция над семафором называется "опера- цией без блокировки". Системный вызов semop(2) оперирует не с отдельным семафором, а с множеством семафоров, применяя к нему "массив операций". Мас- сив содержит информацию о том, с какими семафорами нужно опери- ровать и каким образом. Выполнение массива операций с точки зрения пользовательского процесса является неделимым действием. Это значит, во-первых, что если операции выполняются, то только все вместе и, во-вторых, что другой процесс не может получить доступ к промежуточному состоянию множества семафоров, когда часть операций из массива уже выполнилась, а другая часть еще не успела. Операционная система, разумеется, выполняет операции из массива по очереди, причем порядок не оговаривается. Если очередная операция не может быть выполнена, то эффект предыдущих операций аннулируется. Если таковой оказалась операция с блокировкой, выполнение системного вызова приостанавливается. Если неудачу потерпела операция без блокировки, системный вызов немедленно завершается, возвращая значение -1 как признак ошибки, а внеш- ней переменной errno присваивается код ошибки. 3.1. Использование семафоров Перед тем как использовать семафоры (выполнять операции или уп- равляющие действия), нужно создать множество семафоров с уни- кальным идентификатором и ассоциированной структурой данных. Уникальный идентификатор называется идентификатором множества семафоров (semid); он используется для обращений к множеству и структуре данных. С точки зрения реализации множество семафоров представляет со- бой массив структур. Каждая структура соответствует семафору и определяется следующим образом: struct sem { ushort semval; /* Значение семафора */ short sempid; /* Идентификатор процесса, выпол- нявшего последнюю операцию */ ushort semncnt; /* Число процессов, ожидающих увеличения значения семафора */ ushort semzcnt; /* Число процессов, ожидающих обнуления значения семафора */ }; Определение находится во включаемом файле . С каждым идентификатором множества семафоров ассоциирована структура данных, содержащая следующую информацию: struct semid_ds { struct ipc_perm sem_perm; /* Структура прав на выполнение операций */ struct sem *sem_base; /* Указатель на первый семафор в множестве */ ushort sem_nsems; /* Количество семафоров в множестве */ time_t sem_otime; /* Время последней операции */ time_t sem_ctime; /* Время последнего изменения */ }; Это определение также находится во включаемом файле . Отметим, что поле sem_perm данной структуры использует в качестве шаблона структуру ipc_perm, общую для всех средств межпроцессной связи (см. выше раздел ОЧЕРЕДИ СООБЩЕНИЙ). Системный вызов semget(2) аналогичен вызову msgget(2) (разуме- ется, с заменой слов "очередь сообщений" на "множество семафо- ров"). Он также предназначен для получения нового или опроса существующего идентификатора, а нужное действие определяется значением аргумента key. В аналогичных ситуациях semget(2) тер- пит неудачу. Единственное отличие состоит в том, что при созда- нии требуется посредством аргумента nsems указывать число сема- форов в множестве. После того как созданы множество семафоров с уникальным иденти- фикатором и ассоциированная с ним структура данных, можно ис- пользовать системные вызовы semop(2) для операций над семафора- ми и semctl(2) для выполнения управляющих действий. 3.2. Создание множеств семафоров Для создания множества семафоров служит системный вызов sem- get(2). В статье semget(2) Справочника программиста синтаксис данного системного вызова описан так: #include #include #include int semget (key, nsems, semflg) key_t key; int nsems, semflg; Целочисленное значение, возвращаемое в случае успешного завер- шения, есть идентификатор множества семафоров (semid). В случае неудачи результат равен -1. Смысл аргументов key и semflg тот же, что и у соответствующих аргументов системного вызова msgget(2). Аргумент nsems задает число семафоров в множестве. Если запрашивается идентификатор существующего множества, значение nsems не должно превосходить числа семафоров в множестве. Превышение системных параметров SEMMNI, SEMMNS и SEMMSL при по- пытке создать новое множество всегда ведет к неудачному завер- шению. Системный параметр SEMMNI определяет максимально допус- тимое число уникальных идентификаторов множеств семафоров в системе. Системный параметр SEMMNS определяет максимальное об- щее число семафоров в системе. Системный параметр SEMMSL опре- деляет максимально допустимое число семафоров в одном множест- ве. В статье semget(2) Справочника программиста описывается началь- ное значение ассоциированной структуры данных, формируемое в случае успешного завершения системного вызова. 3.3. Управление семафорами В данном пункте детально описывается использование системного вызова semctl(2) и приводится программа-пример, позволяющая по- упражняться со всеми его возможностями. 3.3.1. Использование semctl В статье semctl(2) Справочника программиста синтаксис данного системного вызова описан так: #include #include #include int semctl (semid, semnum, cmd, arg) int semid, cmd; int semnum; union semun { int val; struct semid_ds *buf; ushort *array; } arg; Результат системного вызова semctl(2) в случае успешного завер- шения зависит от выполняемого управляющего действия. Как прави- ло он равен 0, но четыре действия (GETVAL, GETPID, GETNCNT и GETZCNT) являются исключениями. При возникновении ошибки всегда возвращается -1. Аргументы semid и semnum определяют множество или отдельный се- мафор, над которым выполняется управляющее действие. В качестве аргумента semid должен выступать идентификатор множества сема- форов, предварительно полученный при помощи системного вызова semget(2). Аргумент semnum задает номер семафора в множестве. Семафоры нумеруются с нуля. Назначение аргумента arg зависит от управляющего действия, ко- торое определяется значением аргумента cmd. Допустимы следующие действия: GETVAL Получить значение семафора и выдать его в качестве результата. SETVAL Установить значение семафора равным arg.val. GETPID Получить идентификатор процесса, последним выпол- нявшего операцию над семафором, и выдать его в ка- честве результата. GETNCNT Получить число процессов, ожидающих увеличения значение семафора, и выдать его в качестве резуль- тата. GETZCNT Получить число процессов, ожидающих обнуления зна- чения семафора, и выдать его в качестве результа- та. GETALL Прочитать значения всех семафоров множества и по- местить их в массив, на который указывает arg.ar- ray. SETALL Установить значения всех семафоров множества рав- ными значениям элементов массива, на который ука- зывает arg.array. IPC_STAT Поместить информацию о состоянии множества семафо- ров, содержащуюся в структуре данных, ассоцииро- ванной с идентификатором semid, в пользовательскую структуру, на которую указывает arg.buf. IPC_SET В структуре данных, ассоциированной с идентифика- тором semid, переустановить значения действующих идентификаторов пользователя и группы, а также прав на операции. Нужные значения извлекаются из структуры данных, на которую указывает arg.buf. IPC_RMID Удалить из системы идентификатор semid, ликвидиро- вать множество семафоров и ассоциированную с ним структуру данных. Чтобы выполнить управляющее действие IPC_SET или IPC_RMID, про- цесс должен иметь действующий идентификатор пользователя, рав- ный либо идентификаторам создателя или владельца очереди, либо идентификатору суперпользователя. Для выполнения управляющих действий SETVAL и SETALL требуется право на изменение, а для выполнения остальных действий - право на чтение. 3.3.2. Программа-пример /* Программа иллюстрирует возможности системного вызова semctl() (управление семафорами) */ #include #include #include #include #define MAXSETSIZE 25 main () { extern int errno; struct semid_ds semid_ds; int length, rtrn, i, c; int semid, semnum, cmd, choice; union semun { int val; struct semid_ds *buf; ushort array [MAXSETSIZE]; } arg; /* Инициализация указателя на структуру данных */ arg.buf = &semid_ds; /* Ввести идентификатор множества семафоров */ printf ("Введите ид-р множества семафоров: "); scanf ("%d", &semid); /* Выбрать требуемое управляющее действие */ printf ("\nВведите номер требуемого действия:\n"); printf (" GETVAL = 1\n"); printf (" SETVAL = 2\n"); printf (" GETPID = 3\n"); printf (" GETNCNT = 4\n"); printf (" GETZCNT = 5\n"); printf (" GETALL = 6\n"); printf (" SETALL = 7\n"); printf (" IPC_STAT = 8\n"); printf (" IPC_SET = 9\n"); printf (" IPC_RMID = 10\n"); printf (" Выбор = "); scanf ("%d", &cmd); /* Проверить значения */ printf ("идентификатор = %d, команда = %d\n", semid, cmd); /* Сформировать аргументы и выполнить вызов */ switch (cmd) { case 1: /* Получить значение */ printf ("\nВведите номер семафора: "); scanf ("%d", &semnum); /* Выполнить системный вызов */ rtrn = semctl (semid, semnum, GETVAL, 0); printf ("\nЗначение семафора = %d\n", rtrn); break; case 2: /* Установить значение */ printf ("\nВведите номер семафора: "); scanf ("%d", &semnum); printf ("\nВведите значение: "); scanf ("%d", &arg.val); /* Выполнить системный вызов */ rtrn = semctl (semid, semnum, SETVAL, arg.val); break; case 3: /* Получить ид-р процесса */ rtrn = semctl (semid, 0, GETPID, 0); printf ("\Последнюю операцию выполнил: %d\n",rtrn); break; case 4: /* Получить число процессов, ожидающих увеличения значения семафора */ printf ("\nВведите номер семафора: "); scanf ("%d", &semnum); /* Выполнить системный вызов */ rtrn = semctl (semid, semnum, GETNCNT, 0); printf ("\nЧисло процессов = %d\n", rtrn); break; case 5: /* Получить число процессов, ожидающих обнуления значения семафора */ printf ("Введите номер семафора: "); scanf ("%d", &semnum); /* Выполнить системный вызов */ rtrn = semctl (semid, semnum, GETZCNT, 0 printf ("\nЧисло процессов = %d\n", rtrn); break; case 6: /* Опросить все семафоры */ /* Определить число семафоров в множестве */ rtrn = semctl (semid, 0, IPC_STAT, arg.buf); length = arg.buf->sem_nsems; if (rtrn == -1) goto ERROR; /* Получить и вывести значения всех семафоров в указанном множестве */ rtrn = semctl (semid, 0, GETALL, arg.array); for (i = 0; i < length; i++) printf (" %d", arg.array [i]); break; case 7: /* Установить все семафоры */ /* Определить число семафоров в множестве */ rtrn = semctl (semid, 0, IPC_STAT, arg.buf); length = arg.buf->sem_nsems; if (rtrn == -1) goto ERROR; printf ("\nЧисло семафоров = %d\n", length); /* Установить значения семафоров множества */ printf ("\nВведите значения:\n"); for (i = 0; i < length; i++) scanf ("%d", &arg.array [i]); /* Выполнить системный вызов */ rtrn = semctl (semid, 0, SETALL, arg.array); break; case 8: /* Опросить состояние множества */ rtrn = semctl (semid, 0, IPC_STAT, arg.buf); printf ("\nИдентификатор пользователя = %d\n", arg.buf->sem_perm.uid printf ("Идентификатор группы = %d\n", arg.buf->sem_perm.gid); printf ("Права на операции = 0%o\n", arg.buf->sem_perm.mode); printf ("Число семафоров в множестве = %d\n", arg.buf->sem_nsems); printf ("Время последней операции = %d\n", arg.buf->sem_otime); printf ("Время последнего изменения = %d\n", arg.buf->sem_ctime); break; case 9: /* Выбрать и изменить поле ассоциированной структуры данных */ /* Опросить текущее состояние */ rtrn = semctl (semid, 0, IPC_STAT, arg.buf); if (rtrn == -1) goto ERROR; printf ("\nВведите номер поля, "); printf ("которое нужно изменить: \n"); printf (" sem_perm.uid = 1\n"); printf (" sem_perm.gid = 2\n"); printf (" sem_perm.mode = 3\n"); printf (" Выбор = "); scanf ("%d", &choice); switch (choice) { case 1: /* Изменить ид-р владельца */ printf ("\nВведите ид-р владельца: "); scanf ("%d", &arg.buf->sem_perm.uid); printf ("\nИд-р владельца = %d\n", arg.buf->sem_perm.uid); break; case 2: /* Изменить ид-р группы */ printf ("\nВведите ид-р группы = "); scanf ("%d", &arg.buf->sem_perm.gid); printf ("\nИд-р группы = %d\n", arg.buf->sem_perm.uid); break; case 3: /* Изменить права на операции */ printf ("\nВведите восьмеричный код прав: "); scanf ("%o", &arg.buf->sem_perm.mode); printf ("\nПрава = 0%o\n", arg.buf->sem_perm.mode); break; } /* Внести изменения */ rtrn = semctl (semid, 0, IPC_SET, arg.buf); break; case 10: /* Удалить ид-р множества семафоров и ассоциированную структуру данных */ rtrn = semctl (semid, 0, IPC_RMID, 0); } if (rtrn == -1) { /* Сообщить о неудачном завершении */ ERROR: printf ("\nsemctl завершился неудачей!\n"); printf ("\nКод ошибки = %d\n", errno); } else { printf ("\nmsgctl завершился успешно,\n"); printf ("идентификатор semid = %d\n", semid); } exit (0); } 3.4. Операции над множествами семафоров В данном пункте детально описывается использование системного вызова semop(2) и приводится программа-пример, позволяющая по- упражняться со всеми его возможностями. Более подробно и строго функционирование системного вызова semop(2) описано в Справоч- нике программиста. 3.4.1. Использование semop В статье semop(2) Справочника программиста синтаксис данного системного вызова описан так: #include #include #include int semop (semid, sops, nsops) int semid; struct sembuf *sops; unsigned nsops; При успешном завершении результат системного вызова равен нулю; в случае неудачи возвращается -1. В качестве аргумента semid должен выступать идентификатор мно- жества семафоров, предварительно полученный при помощи систем- ного вызова semget(2). Аргумент sops (массив структур) определяет, над какими семафо- рами будут выполняться операции и какие именно. Структура, опи- сывающая операцию над одним семафором, определяется следующим образом: struct sembuf { short sem_num; /* Номер семафора */ short sem_op; /* Операция над семафором */ short sem_flg; /* Флаги операции */ }; (см. включаемый файл ). Номер семафора задает конкретный семафор в множестве, над кото- рым должна быть выполнена операция. Выполняемая операция определяется следующим образом: Положительное значение поля sem_op предписывает увели- чить значение семафора на величину sem_op. Отрицательное значение поля sem_op предписывает умень- шить значение семафора на абсолютную величину sem_op. Операция не может быть успешно выполнена, если в ре- зультате получится отрицательное число. Нулевое значение поля sem_op предписывает сравнить зна- чение семафора с нулем. Операция не может быть успешно выполнена, если значение семафора отлично от нуля. Допустимые значения флагов операций (поле sem_flg): IPC_NOWAIT Если какая-либо операция, для которой задан флаг IPC_NOWAIT, не может быть успешно выполнена, сис- темный вызов завершается неудачей, причем ни у од- ного из семафоров не будет изменено значение SEM_UNDO Данный флаг задает проверочный режим выполнения операции; он предписывает аннулировать ее резуль- тат даже в случае успешного завершения системного вызова semop(2). Иными словами, блокировка всех операций (в том числе и тех, для которых задан флаг SEM_UNDO) выполняется обычным образом, но когда наконец все операции могут быть успешно вы- полнены, операции с флагом SEM_UNDO игнорируются. Аргумент nsops специфицирует число структур в массиве. Макси- мально допустимый размер массива определяется системным пара- метром SEMOPM, то есть в каждом системном вызове semop(2) можно выполнить не более SEMOPM операций. 3.4.2. Программа-пример /* Программа иллюстрирует возможности системного вызова semop() (операции над множеством семафоров) */ #include #include #include #include #define MAXOPSIZE 10 main () { extern int errno; struct sembuf sops [MAXOPSIZE]; int semid, flags, i, rtrn; unsigned nsops; /* Ввести идентификатор множества семафоров */ printf ("\nВведите идентификатор множества семафоров,"); printf ("\nнад которым будут выполняться операции: "); scanf ("%d", &semid); printf ("\nИд-р множества семафоров = %d", semid); /* Ввести число операций */ printf ("\nВведите число операций "); printf ("над семафорами из этого множества: \n"); scanf ("%d", &nsops); printf ("\nЧисло операций = %d", nsops); /* Инициализировать массив операций */ for (i = 0; i < nsops; i++) { /* Выбрать семафор из множества */ printf ("\nВведите номер семафора: "); scanf ("%d", &sops [i].sem_num); printf ("\nНомер = %d", sops [i].sem_num); /* Ввести число, задающее операцию */ printf ("\nЗадайте операцию над семафором: "); scanf ("%d", &sops [i].sem_op); printf ("\nОперация = %d", sops [i].sem_op); /* Указать требуемые флаги */ printf ("\nВведите код, "); printf ("соответствующий требуемым флагам:\n"); printf (" Нет флагов = 0\n"); printf (" IPC_NOWAIT = 1\n"); printf (" SEM_UNDO = 2\n"); printf (" IPC_NOWAIT и SEM_UNDO = 3\n"); printf (" Выбор = "); scanf ("%d", &flags); switch (flags) { case 0: sops [i].sem_flg = 0; break; case 1: sops [i].sem_flg = IPC_NOWAIT; break; case 2: sops [i].sem_flg = SEM_UNDO; break; case 3: sops [i].sem_flg = IPC_NOWAIT | SEM_UNDO; break; } printf ("\nФлаги = 0%o", sops [i].sem_flg); } /* Распечатать все структуры массива */ printf ("\nМассив операций:\n"); for (i = 0; i < nsops; i++) { printf (" Номер семафора = %d\n", sops [i].sem_num); printf (" Операция = %d\n", sops [i].sem_op); printf (" Флаги = 0%o\n", sops [i].sem_flg); } /* Выполнить системный вызов */ rtrn = semop (semid, sops, nsops); if (rtrn == -1) { printf ("\nsemop завершился неудачей!\n"); printf ("Код ошибки = %d\n", errno); } else { printf ("\nsemop завершился успешно.\n"); printf ("Идентификатор semid = %d\n", semid); printf ("Возвращенное значение = %d\n", rtrn); } exit (0); } 4. РАЗДЕЛЯЕМЫЕ СЕГМЕНТЫ ПАМЯТИ Разделяемые сегменты памяти как средство межпроцессной связи позволяют процессам иметь общие области виртуальной памяти и, как следствие, разделять содержащуюся в них информацию. Едини- цей разделяемой памяти являются сегменты, свойства которых за- висят от аппаратных особенностей управления памятью. Разделение памяти обеспечивает наиболее быстрый обмен данными между процессами. Работа с разделяемой памятью начинается с того, что процесс при помощи системного вызова shmget(2) создает разделяемый сегмент, специфицируя первоначальные права доступа к сегменту (чтение и/или запись) и его размер в байтах. Чтобы затем получить дос- туп к разделяемому сегменту, его нужно присоединить посредством системного вызова shmat() [см. shmop(2)], который разместит сегмент в виртуальном пространстве процесса. После присоедине- ния, в соответствии с правами доступа, процессы могут читать данные из сегмента и записывать их (быть может, синхронизируя свои действия с помощью семафоров). Когда разделяемый сегмент становится ненужным, его следует от- соединить, воспользовавшись системным вызовом shmdt(). Для выполнения управляющих действий над разделяемыми сегментами памяти служит системный вызов shmctl(2). В число управляющих действий входит предписание удерживать сегмент в оперативной памяти и обратное предписание о снятии удержания. После того, как последний процесс отсоединил разделяемый сегмент, следует выполнить управляющее действие по удалению сегмента из системы. 4.1. Использование разделяемых сегментов памяти Прежде чем воспользоваться разделением памяти, нужно создать разделяемый сегмент с уникальным идентификатором и ассоцииро- ванную с ним структуру данных. Уникальный идентификатор называ- ется идентификатором разделяемого сегмента памяти (shmid); он используется для обращений к ассоциированной структуре данных, которая определяется следующим образом: struct shmid_ds { struct ipc_perm shm_perm; /* Структура прав на выполнение операций */ int shm_segsz; /* Размер сегмента */ struct region *shm_reg; /* Указатель на структуру области памяти */ char pad [4]; /* Информация для подкачки */ ushort shm_lpid; /* Ид-р процесса, вып. последнюю операцию */ ushort shm_cpid; /* Ид-р процесса, создавшего сегмент */ ushort shm_nattch; /* Число присоединивших сегмент */ ushort shm_cnattch; /* Число удерживающих сегмент в памяти */ time_t shm_atime; /* Время последнего присоединения */ time_t shm_dtime; /* Время последнего отсоединения */ time_t shm_ctime; /* Время последнего изменения */ }; (см. включаемый файл ). Следующая таблица содержит информацию о возможных состояниях разделяемых сегментов памяти: ~------------- ------------ -------------- -------------------- │Бит удержания│Бит подкачки│Бит размещения│ Состояние │ ------------- ------------ -------------- -------------------- │ 0 │ 0 │ 0 │Неразмещенный сегм-т│ ------------- ------------ -------------- -------------------- │ 0 │ 0 │ 1 │В памяти │ ------------- ------------ -------------- -------------------- │ 0 │ 1 │ 0 │Не используется │ ------------- ------------ -------------- -------------------- │ 0 │ 1 │ 1 │На диске │ ------------- ------------ -------------- -------------------- │ 1 │ 0 │ 0 │не используется │ ------------- ------------ -------------- -------------------- │ 1 │ 0 │ 1 │Удержан в памяти │ ------------- ------------ -------------- -------------------- │ 1 │ 1 │ 0 │не используется │ ------------- ------------ -------------- -------------------- │ 1 │ 1 │ 1 │не используется │ ------------- ------------ -------------- -------------------- Состояния, упомянутые в таблице, таковы: Неразмещенный сегмент - разделяемый сегмент, ассоцииро- ванный с данным идентификатором, не размещен для ис- пользования. В памяти - сегмент размещен для использования. Это оз- начает, что сегмент существует и в данный момент нахо- дится в оперативной памяти. На диске - сегмент в данный момент вытолкнут на устрой- ство подкачки. Удержан в памяти - сегмент удержан в оперативной памяти и не будет рассматриваться в качестве кандидата на вы- талкивание, пока не будет снято удержание. Удерживать и освобождать разделяемые сегменты может только супер- пользователь. Не используется - состояние в настоящий момент не ис- пользуется и при работе обычного пользователя с разде- ляемыми сегментами памяти возникнуть не может. Системный вызов shmget(2) аналогичен вызову semget(2) (разуме- ется, с заменой слов "множество семафоров" на "разделяемый сег- мент памяти"). Он также предназначен для получения нового или опроса существующего идентификатора, а нужное действие опреде- ляется значением аргумента key. В аналогичных ситуациях shmget(2) терпит неудачу. Единственное отличие состоит в том, что задается не число семафоров в множестве, а размер сегмента в байтах. После того, как создан уникальный идентификатор разделяемого сегмента памяти и ассоциированная с ним структура данных, можно использовать системные вызовы семейства shmop(2) (операции над разделяемыми сегментами) и shmctl(2) (управление разделяемыми сегментами). 4.2. Создание разделяемых сегментов памяти Для создания разделяемого сегмента памяти служит системный вы- зов shmget(2). В статье shmget(2) Справочника программиста син- таксис данного системного вызова описан так: #include #include #include int shmget (key, size, shmflg) key_t key; int size, shmflg; Целочисленное значение, возвращаемое в случае успешного завер- шения, есть идентификатор разделяемого сегмента (shmid). В слу- чае неудачи результат равен -1. Смысл аргументов key и shmflg тот же, что и у соответствующих аргументов системного вызова semget(2). Аргумент size задает размер разделяемого сегмента в байтах. Системный параметр SHMMNI определяет максимально допустимое число уникальных идентификаторов разделяемых сегментов памяти (shmid) в системе. Попытка его превышения ведет к неудачному завершению системного вызова. Системный вызов завершится неудачей и тогда, когда значение ар- гумента size меньше, чем SHMMIN, либо больше, чем SHMMAX. Дан- ные системные параметры специфицируют, соответственно, мини- мальный и максимальный размеры разделяемого сегмента памяти. В статье shmget(2) Справочника программиста описывается началь- ное значение ассоциированной структуры данных, формируемое в случае успешного завершения системного вызова. 4.3. Управление разделяемыми сегментами памяти В данном пункте детально описывается использование системного вызова shmctl(2) и приводится программа-пример, позволяющая по- упражняться со всеми его возможностями. 4.3.1. Использование shmctl В статье shmctl(2) Справочника программиста синтаксис данного системного вызова описан так: #include #include #include int shmctl (shmid, cmd, buf) int shmid, cmd; struct shmid_ds *buf; При успешном завершении результат равен нулю; в случае неудачи возвращается -1. В качестве аргумента shmid должен выступать идентификатор раз- деляемого сегмента памяти, предварительно полученный при помощи системного вызова shmget(2). Управляющее действие определяется значением аргумента cmd. До- пустимы следующие значения: IPC_STAT Поместить информацию о состоянии разделяемого сег- мента, содержащуюся в структуре данных, ассоцииро- ванной с идентификатором shmid, в пользовательскую структуру, на которую указывает аргумент buf. IPC_SET В структуре данных, ассоциированной с идентифика- тором shmid, переустановить значения действующих идентификаторов пользователя и группы, а также прав на операции. Нужные значения извлекаются из структуры данных, на которую указывает аргумент buf. IPC_RMID Удалить из системы идентификатор shmid, ликвидиро- вать разделяемый сегмент памяти и ассоциированную с ним структуру данных. SHM_LOCK Удерживать в памяти разделяемый сегмент, заданный идентификатором shmid. SHM_UNLOCK Освободить (перестать удерживать в памяти) разде- ляемый сегмент, заданный идентификатором shmid. Чтобы выполнить управляющее действие IPC_SET или IPC_RMID, про- цесс должен иметь действующий идентификатор пользователя, рав- ный либо идентификаторам создателя или владельца очереди, либо идентификатору суперпользователя. Управляющие действия SHM_LOCK и SHM_UNLOCK может выполнить только суперпользователь. Для вы- полнения управляющего действия IPC_STAT процессу требуется пра- во на чтение. 4.3.2. Программа-пример /* Программа иллюстрирует возможности системного вызова shmctl() (операции управления разделяемыми сегментами) */ #include #include #include #include main () { extern int errno; int rtrn, shmid, command, choice; struct shmid_ds shmid_ds, *buf; buf = &shmid_ds; /* Ввести идентификатор сегмента и действие */ printf ("Введите идентификатор shmid: "); scanf ("%d", &shmid); printf ("Введите номер требуемого действия:\n"); printf (" IPC_STAT = 1\n"); printf (" IPC_SET = 2\n"); printf (" IPC_RMID = 3\n"); printf (" SHM_LOCK = 4\n"); printf (" SHM_UNLOCK = 5\n"); printf (" Выбор = "); scanf ("%d", &command); /* Проверить значения */ printf ("\nидентификатор = %d, действие = %d\n", shmid, command); switch (command) { case 1: /* Скопировать информацию о состоянии разделяемого сегмента в пользовательскую структуру и вывести ее */ rtrn = shmctl (shmid, IPC_STAT, buf); printf ("\nИд-р пользователя = %d\n", buf->shm_perm.uid); printf ("Ид-р группы пользователя = %d\n", buf->shm_perm.gid); printf ("Ид-р создателя = %d\n", buf->shm_perm.cuid); printf ("Ид-р группы создателя = %d\n", buf->shm_perm.cgid); printf ("Права на операции = 0%o\n", buf->shm_perm.mode); printf ("Последовательность номеров "); buf->shm_perm.cgid); printf ("используемых слотов = 0%x\n", buf->shm_perm.seq); printf ("Ключ = 0%x\n", buf->shm_perm.key); printf ("Размер сегмента = %d\n", buf->shm_segsz); printf ("Выполнил последнюю операцию = %d\n", buf->shm_lpid); printf ("Создал сегмент = %d\n", buf->shm_cpid); printf ("Число присоединивших сегмент = %d\n", buf->shm_nattch); printf ("Число удерживаюших в памяти = %d\n", buf->shm_cnattch); printf ("Последнее присоединение = %d\n", buf->shm_atime); printf ("Последнее отсоединение = %d\n", buf->shm_dtime); printf ("Последнее изменение = %d\n", buf->shm_ctime); break; case 2: /* Выбрать и изменить поле (поля) ассоциированной структуры данных */ /* Получить исходные значения структуры данных */ rtrn = shmctl (shmid, IPC_STAT, buf); printf ("Введите номер изменяемого поля:\n"); printf (" shm_perm.uid = 1\n"); printf (" shm_perm.gid = 2\n"); printf (" shm_perm.mode = 3\n"); printf (" Выбор = "); scanf ("%d", &choice); switch (choice) { case 1: printf ("\nВведите ид-р пользователя: "), scanf ("%d", &buf->shm_perm.uid); printf ("\nИд-р пользователя = %d\n", buf->shm_perm.uid); break; case 2: printf ("\nВведите ид-р группы: "), scanf ("%d", &buf->shm_perm.gid); printf ("\nИд-р группы = %d\n", buf->shm_perm.uid); break; case 3: printf ("\nВведите восьмеричный код прав: "); scanf ("%o", &buf->shm_perm.mode); printf ("\nПрава на операции = 0%o\n", buf->shm_perm.mode); break; } /* Внести изменения */ rtrn = shmctl (shmid, IPC_SET, buf); break; case 3: /* Удалить идентификатор и ассоциированную структуру данных */ rtrn = shmctl (shmid, IPC_RMID, NULL); break; case 4: /* Удерживать разделяемый сегмент в памяти */ rtrn = shmctl (shmid, SHM_LOCK, NULL); break; case 5: /* Перестать удерживать сегмент в памяти */ rtrn = shmctl (shmid, SHM_UNLOCK, NULL); } if (rtrn == -1) { /* Сообщить о неудачном завершении */ printf ("\nshmctl завершился неудачей!\n"); printf ("\nКод ошибки = %d\n", errno); } else { /* При успешном завершении сообщить ид-р shmid */ printf ("\nshmctl завершился успешно, "); printf ("идентификатор shmid = %d\n", shmid); } exit (0); } 4.4. Операции над разделяемыми сегментами памяти В данном пункте детально описывается использование системных вызовов shmat(2) и shmdt(2) и приводится программа-пример, поз- воляющая поупражняться со всеми их возможностями. 4.4.1. Использование операций В статье shmop(2) Справочника программиста синтаксис упомянутых системных вызовов описан так: #include #include #include int shmat (shmid, shmaddr, shmflg) int shmid; char *shmaddr; int shmflg; int shmdt (shmaddr) char *shmaddr; 4.4.1.1. Присоединение сегментов При успешном завершении системного вызова shmat() результат ра- вен адресу, который получил присоединенный сегмент; в случае неудачи возвращается -1. Разумеется, чтобы использовать резуль- тат shmat() как указатель, его нужно преобразовать к требуемому типу. В качестве аргумента shmid должен выступать идентификатор раз- деляемого сегмента, предварительно полученный при помощи сис- темного вызова shmget(2). Аргумент shmaddr задает адрес, по которому сегмент должен быть присоединен, то есть тот адрес в виртуальном пространстве поль- зователя, который получит начало сегмента. Не всякий адрес яв- ляется приемлемым. Можно порекомендовать адреса вида 0x80000000 0x80040000 0x80080000 . . . Если значение shmaddr равно нулю, система выбирает адрес присо- единения по своему усмотрению. Аргумент shmflg используется для передачи системному вызову shmat() флагов SHM_RND и SHM_RDONLY. Наличие первого из них оз- начает, что адрес shmaddr следует округлить до некоторй систем- но-зависимой величины [подробнее см. статью shmop(2) в Справоч- нике программиста]. Второй флаг предписывает присоединить сег- мент только для чтения; если он не установлен, присоединенный сегмент будет доступен и на чтение, и на запись (если процесс обладает соответствующими правами). 4.4.1.2. Отсоединение сегментов При успешном завершении системного вызова shmdt() результат ра- вен нулю; в случае неудачи возвращается -1. Аргумент shmaddr задает начальный адрес отсоединяемого сегмен- та. Напомним, что после того, как последний процесс отсоединил раз- деляемый сегмент памяти, этот сегмент вместе с идентификатором и ассоциированной структурой данных следует удалить с помощью системного вызова shmctl(2). 4.4.2. Программа-пример /* Программа иллюстрирует возможности системных вызовов shmat() и shmdt() (операции над разделяемыми сегментами памяти) */ #include #include #include #include main () { extern int errno; int shmid, shmaddr, shmflg; int flags, attach, detach, rtrn, i; /* Цикл присоединений для данного процесса */ printf ("\nВведите число присоединений "); printf ("для процесса (1-4): "); scanf ("%d", &attach); printf ("\nЧисло присоединений = %d\n", attach); for (i = 0; i < attach; i++) { /* Ввести идентификатор разделяемого сегмента */ printf ("\nВведите ид-р разделяемого сегмента,\n"); printf ("над которым нужно выполнить операции: "); scanf ("%d", &shmid); printf ("\nИд-р сегмента = %d\n", shmid); /* Ввести адрес присоединения */ printf ("\nВведите адрес присоединения "); printf ("в шестнадцатеричной записи: "); scanf ("%x", &shmaddr); printf ("\nАдрес присоединения = 0x%x\n", shmaddr); /* Выбрать требуемые флаги */ printf ("\nВведите номер нужной комбинации флагов:\n"); printf (" SHM_RND = 1\n"); printf (" SHM_RDONLY = 2\n"); printf (" SHM_RND и SHM_RDONLY = 3\n"); printf (" Выбор = "); scanf ("%d", &flags); switch (flags) { case 1: shmflg = SHM_RND; break; case 2: shmflg = SHM_RDONLY; break; case 3: shmflg = SHM_RND | SHM_RDONLY; break; } printf ("\nФлаги = 0%o", shmflg); /* Выполнить системный вызов shmat */ rtrn = shmat (shmid, shmaddr, shmflg); if (rtrn == -1) { printf ("\nshmat завершился неудачей!\n"); printf ("\Код ошибки = %d\n", errno); } else { printf ("\nshmat завершился успешно.\n"); printf ("Идентификатор shmid = %d\n", shmid); printf ("Адрес = 0x%x\n", rtrn); } } /* Цикл отсоединений для данного процесса */ printf ("\nВведите число отсоединений "); printf ("для процесса (1-4): "); scanf ("%d", &detach); printf ("\nЧисло отсоединений = %d\n", detach); for (i = 0; i < detach; i++) { /* Ввести адрес отсоединения */ printf ("\nВведите адрес отсоединяемого сегмента "); printf ("в шестнадцатеричной записи: "); scanf ("%x", &shmaddr); printf ("\nАдрес отсоединения = 0x%x\n", shmaddr); /* Выполнить системный вызов shmdt */ rtrn = shmdt (shmaddr); if (rtrn == -1) { printf ("\nshmdt завершился неудачей!\n"); printf ("\Код ошибки = %d\n", errno); } else { printf ("\nshmdt завершился успешно,\n"); printf ("идентификатор shmid = %d\n", shmid); } } exit (0); }