Гордеев А.В. Операционные системы (2-е изд., 2004) (1186250), страница 86
Текст из файла (страница 86)
После этого индивидуальный семафор идентифицируется дескриптором набора семафоров и номером семафора в этом наборе. Если к моментувыполнения системного вызова semget набор семафоров с указанным ключом ужесуществует, то обращающийся процесс получит соответствующий дескриптор, нотак и не узнает о реальном числе семафоров в группе (хотя позже это все-такиможно узнать с помощью системного вызова semctl).Основным системным вызовом для манипулирования семафором является semop:oldval = semopdd.
oplist, count);Здесь id — это ранее полученный дескриптор группы семафоров, oplist — массивописателей операций над семафорами группы, a count— размер этого массива.Значение, возвращаемое системным вызовом, является значением последнего обработанного семафора. Каждый элемент массива oplist имеет следующую структуру:Q номер семафора в указанном наборе семафоров;•операция;Q флаги.Если проверка прав доступа проходит нормально и указанные в массиве oplist номера семафоров не выходят за пределы общего размера набора семафоров, то системный вызов выполняется следующим образом. Для каждого элемента массиваoplist значение соответствующего семафора изменяется в соответствии со значением поля операции, как показано ниже.• Если значение поля операции положительно, то значение семафора увеличивается на единицу, а все процессы, ожидающие увеличения значения семафора, активизируются {пробуждаются — в терминологии UNIX).Семейство операционных систем UNIX331а Если значение поля операции равно нулю и значение семафора также равнонулю, выбирается следующий элемент массива oplist.
Если же значение поляоперации равно нулю, а значение семафора отлично от нуля, то ядро увеличивает на единицу число процессов, ожидающих нулевого значения семафора,причем обратившийся процесс переводится в состояние ожидания (засыпает —в терминологии UNIX).О Если значение поля операции отрицательно и его абсолютное значение меньшеили равно значению семафора, то ядро прибавляет это отрицательное значениек значению семафора.
Если в результате значение семафора стало нулевым, тоядро активизирует (пробуждает) все процессы, ожидающие нулевого значенияэтого семафора. Если же значение семафора оказывается меньше абсолютнойвеличины поля операции, то ядро увеличивает на единицу число процессов,ожидающих увеличения значения семафора, и откладывает (усыпляет) текущий процесс до наступления этого события.Интересно заметить, что основным поводом для введения массовых операций надсемафорами было стремление дать программистам возможность избегать тупиковых ситуаций, возникающих в связи с семафорной синхронизацией. Это обеспечивается тем, что системный вызов semop, каким бы длинным он ни был (по причинепотенциально неограниченной длины массива oplist), выполняется как атомарнаяоперация, то есть во время выполнения semop ни один другой процесс не можетизменить значение какого-либо семафора.Наконец, среди флагов-параметров системного вызова semop может содержатьсяфлаг с символическим именем IPC_NOWAIT, наличие которого заставляет ядро UNIXне блокировать текущий процесс, а лишь сообщать в ответных параметрах о возникновении ситуации, приведшей к блокированию процесса в случае отсутствияфлага IPC_NOWAIT.
Мы не будем обсуждать здесь возможности корректного завершения работы с семафорами при незапланированном завершении процесса; заметим только, что такие возможности обеспечиваются.Системный вызов semctl имеет следующий формат:semctKid. number, cmd. arg);Здесь id — это дескриптор группы семафоров, number — номер семафора в группе,cmd — код операции, arg — указатель на структуру, содержимое которой интерпретируется по-разному в зависимости от операции.
В частности, с помощью вызоваsemctl можно уничтожить индивидуальный семафор в указанной группе. ОднакоДетали этого системного вызова настолько громоздки, что лучше рекомендовать вслучае необходимости обращаться к технической документации используемоговарианта операционной системы.Программные каналыМы уже познакомились с программными каналами в главе 7. Рассмотрим этотмеханизм еще раз, так сказать, в его исходном, изначальном толковании.Программные каналы (pipes) в системе UNIX являются очень важным средствомвзаимодействия и синхронизации процессов.
Теоретически программный канал332Глава 10. Краткий обзор современных операционных системпозволяет взаимодействовать любому числу процессов, обеспечивая дисциплинуFIFO (First In First Out — первый пришедший первым и выбывает). Другими словами, процесс, читающий из программного канала, прочитает те данные, которыебыли записаны в программный канал раньше других.
В традиционной реализациипрограммных каналов для хранения данных использовались файлы. В современных версиях операционных систем семейства UNIX для реализации программныхканалов применяются другие средства взаимодействия между процессами (в частности, очереди сообщений).В UNIX различаются два вида программных каналов — именованные и неименованные. Именованный программный канал может служить для общения и синхронизации произвольных процессов, знающих имя данного программного каналаИ имеющих соответствующие права доступа.
Неименованным программным каналом могут пользоваться только породивший его процесс и его потомки (необязательно прямые).Для создания именованного программного канала (или получения к нему доступа) используется обычный файловый системный вызов open. Для создания же неименованного программного канала существует специальный системный вызов pipe(исторически более ранний). Однако после получения соответствующих дескрипторов оба вида программных каналов используются единообразно с помощью стандартных файловых системных вызовов read, write и close.Системный вызов pipe имеет следующий синтаксис:pipe(fdptr);Здесь fdptr — это указатель на массив из двух целых чисел, в который после создания неименованного программного канала будут помещены дескрипторы, предназначенные для чтения из программного канала (с помощью системного вызоваread) и записи в программный канал (с помощью системного вызова write).
Дескрипторы неименованного программного канала — это обычные дескрипторы файлов, то есть такому программному каналу соответствуют два элемента таблицыоткрытых файлов процесса. Поэтому при последующих системных вызовах read иwrite процесс совершенно не обязан отличать случай использования программныхканалов от случая использования обычных файлов (собственно, на этом и основана идея перенаправления ввода-вывода и организации конвейеров).Для создания именованных программных каналов (или получения доступа к ужесуществующим каналам) используется обычный системный вызов open. Основным отличием от случая открытия обычного файла является то, что если именованный программный канал открывается для записи и ни один процесс не открылтот же программный канал для чтения, то обращающийся процесс блокируется дотех пор, пока некоторый процесс не откроет данный программный канал для чтения.
Аналогично обрабатывается открытие для чтения.Запись данных в программный канал и чтение данных из программного канала (независимо от того, именованный он или не именованный) выполняются с помощьюсистемных вызовов read и write. Отличие от случая использования обычных файловсостоит лишь в том, что при записи данные помещаются в начало канала, а при чтении выбираются (освобождая соответствующую область памяти) из конца канала.Семейство операционных систем UNIXОкончание работы процесса с программным каналом (независимо от того, именованный он или не именованный) производится с помощью системного вызова close.Очереди сообщенийДля обмена данными между процессами используется механизм очередей сообщений, который поддерживается следующими системными вызовами:a msgget — образование новой очереди сообщений или получение дескрипторасуществующей очереди;a msgsnd — отправка сообщения (точнее, его постановка в указанную очередь сообщений);a msgrcv — прием сообщения (точнее, выборка сообщения из очереди сообщений);• msgctl — выполнение ряда управляющих действий.Ядро хранит сообщения в виде связного списка (очереди), а дескриптор очередисообщений является индексом в массиве заголовков очередей сообщений.Системный вызов msgget имеет следующий синтаксис:msgqid = msgget(key.
flag):Здесь параметры key и flag имеют то же значение, что и в вызове semget при запросесемафора.При выполнении системного вызова msgget ядро UNIX-системы либо создает новую очередь сообщений, помещая ее заголовок в таблицу очередей сообщений ивозвращая пользователю дескриптор вновь созданной очереди, либо находит элемент таблицы очередей сообщений, содержащий указанный ключ, и возвращаетсоответствующий дескриптор очереди.Для отправки сообщения используется системный вызов msgsnd:msgsnd(msgqid, msg, count, flag):Здесь msg — указатель на структуру, содержащую определяемый пользователемцелочисленный тип сообщения и символьный массив (собственно сообщение);count — размер сообщения в байтах; flag — значение, которое определяет действияядра при выходе за пределы допустимых размеров внутренней буферной памяти.Для приема сообщения используется системный вызов msgrcv:count » msgrcvdd, msg. maxcount, type, flag);Здесь msg — указатель на структуру данных в адресном пространстве пользователя, предназначенную для размещения принятого сообщения; maxcount — размеробласти данных (массива байтов) в структуре msg; type — тип сообщения, котороетребуется принять; flag — значение, которое указывает ядру, что следует предпринять, если в указанной очереди сообщений отсутствует сообщение с указаннымтипом.