| | exit(); | | } | +------------------------------------------------------------+ Рисунок 11.8. Обслуживающий процесс (сервер) Сообщения имеют форму "тип - текст", где текст представляет собой поток 340 байтов. Указание типа дает процессам возможность выбирать сообщения только определенного рода, что в файловой системе не так легко сделать. Таким обра- зом, процессы могут выбирать из очереди сообщения определенного типа в по- рядке их поступления, причем эта очередность гарантируется ядром. Несмотря на то, что обмен сообщениями может быть реализован на пользовательском уров- не средствами файловой системы, представленный вашему вниманию механизм обеспечивает более эффективную организацию передачи данных между процессами. С помощью системной функции msgctl процесс может запросить информацию о статусе дескриптора сообщения, установить этот статус или удалить дескриптор сообщения из системы. Синтаксис вызова функции: msgctl(id,cmd,mstatbuf) где id - дескриптор сообщения, cmd - тип команды, mstatbuf - адрес пользова- тельской структуры, в которой будут храниться управляющие параметры или ре- зультаты обработки запроса. Более подробно об аргументах функции пойдет речь в Приложении. Вернемся к примеру, представленному на Рисунке 11.8. Обслуживающий про- цесс принимает сигналы и с помощью функции cleanup удаляет очередь сообщений из системы. Если же им не было поймано ни одного сигнала или был получен сигнал SIGKILL, очередь сообщений остается в системе, даже если на нее не ссылается ни один из процессов. Дальнейшие попытки исключительно создания новой очереди сообщений с данным ключом (идентификатором) не будут иметь ус- пех до тех пор, пока старая очередь не будет удалена из системы. 11.2.2 Разделение памяти Процессы могут взаимодействовать друг с другом непосредственно путем разделения (совместного использования) участков виртуального адресного прос- транства и обмена данными через разделяемую память. Системные функции для работы с разделяемой памятью имеют много сходного с системными функциями для работы с сообщениями. Функция shmget создает новую область разделяемой памя- ти или возвращает адрес уже существующей области, функция shmat логически присоединяет область к виртуальному адресному пространству процесса, функция shmdt отсоединяет ее, а функция shmctl имеет дело с различными параметрами, связанными с разделяемой памятью. Процессы ведут чтение и запись данных в области разделяемой памяти, используя для этого те же самые машинные коман- ды, что и при работе с обычной памятью. После присоединения к виртуальному адресному пространству процесса область разделяемой памяти становится дос- тупна так же, как любой участок виртуальной памяти; для доступа к находящим- ся в ней данным не нужны обращения к каким-то дополнительным системным функ- циям. Синтаксис вызова системной функции shmget: shmid = shmget(key,size,flag); где size - объем области в байтах. Ядро использует key для ведения поиска в таблице разделяемой памяти: если подходящая запись обнаружена и если разре- шение на доступ имеется, ядро возвращает вызывающему процессу указанный в записи дескриптор. Если запись не найдена и если пользователь установил флаг IPC_CREAT, указывающий на необходимость создания новой области, ядро прове- ряет нахождение размера области в установленных системой пределах и выделяет область по алгоритму allocreg (раздел 6.5.2). Ядро записывает установки прав доступа, размер области и указатель на соответствующую запись таблицы облас- тей в таблицу разделяемой памяти (Рисунок 11.9) и устанавливает флаг, свиде- тельствующий о том, что с областью не связана отдельная память. Области вы- деляется память (таблицы страниц и т.п.) только тогда, когда процесс присое- диняет область к своему адресному пространству. Ядро устанавливает также 341 флаг, говорящий о том, что по завершении последнего связанного с областью процесса область не должна освобождаться. Таким образом, данные в разделяе- мой памяти остаются в сохранности, даже если она не принадлежит ни одному из процессов (как часть виртуального адресного пространства последнего). Таблица раз- Таблица процессов - деляемой па- Таблица областей частная таблица об- мяти ластей процесса +----------+ +--------------+ +---------+ | ----+----+ | | +----+---- | +----------+ +|->+--------------+<----+ +---------+ | ----+---+| | | +---+---- | +----------+ | +--------------+<----+| +---------+ | ----+--+ | | | +|---+---- | +----------+ | | +--------------+ | +---------+ | - | | | | | | | | | - | | +->+--------------+ | +---------+ | - | | | | | | | | - | +--->+--------------+<-----+ +---------+ | - | | | (после | | | - | +--------------+ shmat) +---------+ | - | | - | | | | - | | - | +---------+ | - | +--------------+ | - | | - | | - | +----------+ +---------+ Рисунок 11.9. Структуры данных, используемые при разделении памяти Процесс присоединяет область разделяемой памяти к своему виртуальному адресному пространству с помощью системной функции shmat: virtaddr = shmat(id,addr,flags); Значение id, возвращаемое функцией shmget, идентифицирует область разделяе- мой памяти, addr является виртуальным адресом, по которому пользователь хо- чет подключить область, а с помощью флагов (flags) можно указать, предназна- чена ли область только для чтения и нужно ли ядру округлять значение указан- ного пользователем адреса. Возвращаемое функцией значение, virtaddr, предс- тавляет собой виртуальный адрес, по которому ядро произвело подключение об- ласти и который не всегда совпадает с адресом, указанным пользователем. В начале выполнения системной функции shmat ядро проверяет наличие у процесса необходимых прав доступа к области (Рисунок 11.10). Оно исследует указанный пользователем адрес; если он равен 0, ядро выбирает виртуальный адрес по своему усмотрению. Область разделяемой памяти не должна пересекаться в виртуальном адресном пространстве процесса с другими областями; следовательно, ее выбор должен производиться разумно и осторожно. Так, например, процесс может увеличить размер принадлежащей ему области данных с помощью системной функции brk, и новая область данных будет содержать адреса, смежные с прежней областью; по- этому, ядру не следует присоединять область разделяемой памяти слишком близ- ко к области данных процесса. Так же не следует размещать область разделяе- мой памяти вблизи от вершины стека, чтобы стек при своем последующем увели- чении не залезал за ее пределы. Если, например, стек растет в направлении увеличения адресов, лучше всего разместить область разделяемой памяти непос- редственно перед началом области стека. Ядро проверяет возможность размещения области разделяемой памяти в ад- 342 +------------------------------------------------------------+ | алгоритм shmat /* подключить разделяемую память */ | | входная информация: (1) дескриптор области разделяемой | | памяти | | (2) виртуальный адрес для подключения | | области | | (3) флаги | | выходная информация: виртуальный адрес, по которому область| | подключена фактически | | { | | проверить правильность указания дескриптора, права до- | | ступа к области; | | если (пользователь указал виртуальный адрес) | | { | | округлить виртуальный адрес в соответствии с фла- | | гами; | | проверить существование полученного адреса, размер| | области; | | } | | в противном случае /* пользователь хочет, чтобы ядро | | * само нашло подходящий адрес */ | | ядро выбирает адрес: в случае неудачи выдается | | ошибка; | | присоединить область к адресному пространству процесса | | (алгоритм attachreg); | | если (область присоединяется впервые) | | выделить таблицы страниц и отвести память под нее | | (алгоритм growreg); | | вернуть (виртуальный адрес фактического присоединения | | области); | | } | +------------------------------------------------------------+ Рисунок 11.10. Алгоритм присоединения разделяемой памяти ресном пространстве процесса и присоединяет ее с помощью алгоритма attachreg. Если вызывающий процесс является первым процессом, который присо- единяет область, ядро выделяет для области все необходимые таблицы, исполь- зуя алгоритм growreg, записывает время присоединения в соответствующее поле таблицы разделяемой памяти и возвращает процессу виртуальный адрес, по кото- рому область была им подключена фактически. Отсоединение области разделяемой памяти от виртуального адресного прост- ранства процесса выполняет функция shmdt(addr) где addr - виртуальный адрес, возвращенный функцией shmat. Несмотря на то, что более логичной представляется передача идентификатора, процесс использу- ет виртуальный адрес разделяемой памяти, поскольку одна и та же область раз- деляемой памяти может быть подключена к адресному пространству процесса нес- колько раз, к тому же ее идентификатор может быть удален из системы. Ядро производит поиск области по указанному адресу и отсоединяет ее от адресного пространства процесса, используя алгоритм detachreg (раздел 6.5.7). Посколь- ку в таблицах областей отсутствуют обратные указатели на таблицу разделяемой памяти, ядру приходится просматривать таблицу разделяемой памяти в поисках записи, указывающей на данную область, и записывать в соответствующее поле время последнего отключения области. Рассмотрим программу, представленную на Рисунке 11.11. В ней описывается 343 процесс, создающий область разделяемой памяти размером 128 Кбайт и дважды присоединяющий ее к своему адресному пространству по разным виртуальным ад- ресам. В "первую" область он записывает данные, а читает их из "второй" об- ласти. На Рисунке 11.12 показан другой процесс, присоединяющий ту же область (он получает только 64 Кбайта, таким образом, каждый процесс может использо- вать разный объем области разделяемой памяти); он ждет момента, когда первый процесс запишет в первое принадлежащее области слово любое отличное от нуля значение, и затем принимается считывать данные из области. Первый процесс делает "паузу" (pause), предоставляя второму процессу возможность выполне- ния; когда первый процесс принимает сигнал, он удаляет область разделяемой памяти из системы. Процесс запрашивает информацию о состоянии области разделяемой памяти и производит установку параметров для нее с помощью системной функции shmctl: shmctl(id,cmd,shmstatbuf); Значение id идентифицирует запись таблицы разделяемой памяти, cmd определяет тип операции, а shmstatbuf является адресом пользовательской структуры, в которую помещается информация о состоянии области. Ядро трактует тип опера- ции точно так же, как и при управлении сообщениями. Удаляя область разделяе- мой памяти, ядро освобождает соответствующую ей запись в таблице разделяемой памяти и просматривает таблицу областей: если область не была присоединена ни к одному из процессов, ядро освобождает запись таблицы и все выделенные области ресурсы, используя для этого алгоритм freereg (раздел 6.5.6). Если же область по-прежнему подключена к каким-то процессам (значение счетчика ссылок на нее больше 0), ядро только сбрасывает флаг, говорящий о том, что по завершении последнего связанного с нею процесса область не должна осво- бождаться. Процессы, уже использующие область разделяемой памяти, продолжают работать с ней, новые же процессы не могут присоединить ее. Когда все про- цессы отключат область, ядро освободит ее. Это похоже на то, как в файловой системе после разрыва связи с файлом процесс может вновь открыть его и про- должать с ним работу. 11.2.3 Семафоры Системные функции работы с семафорами обеспечивают синхронизацию выпол- нения параллельных процессов, производя набор действий единственно над груп- пой семафоров (средствами низкого уровня). До использования семафоров, если процессу нужно было заблокировать некий ресурс, он прибегал к созданию с по- мощью системной функции creat специального блокирующего файла. Если файл уже существовал, функция creat завершалась неудачно, и процесс делал вывод о том, что ресурс уже заблокирован другим процессом. Главные недостатки такого подхода заключались в том, что процесс не знал, в какой момент ему следует предпринять следующую попытку, а также в том, что блокирующие файлы случайно оставались в системе в случае ее аварийного завершения или перезагрузки. Дийкстрой был опубликован алгоритм Деккера, описывающий реализацию сема- форов как целочисленных объектов, для которых определены две элементарные операции: P и V (см. [Dijkstra 68]). Операция P заключается в уменьшении значения семафора в том случае, если оно больше 0, операция V - в увеличении этого значения (и там, и там на единицу). Поскольку операции элементарные, в любой момент времени для каждого семафора выполняется не более одной опера- ции P или V. Связанные с семафорами системные функции являются обобщением операций, предложенных Дийкстрой, в них допускается одновременное выполнение нескольких операций, причем операции уменьшения и увеличения выполняются над значениями, превышающими 1. Ядро выполняет операции комплексно; ни один из посторонних процессов не сможет переустанавливать значения семафоров, пока 344 все операции не будут выполнены. Если ядро по каким-либо причинам не может выполнить все операции, оно не выполняет ни одной; процесс приостанавливает свою работу до тех пор, пока эта возможность не будет предоставлена. Семафор в версии V системы UNIX состоит из следующих элементов: * Значение семафора, * Идентификатор последнего из процессов, работавших с семафором, * Количество процессов, ожидающих увеличения значения семафора, * Количество процессов, ожидающих момента, когда значение семафора станет равным 0. Для создания набора семафоров и получения доступа к ним используется системная функция semget, для выполнения различных управляющих операций над набором - функция semctl, для работы со значениями семафоров - функция semop. +------------------------------------------------------------+ | #include | | #include | | #include | | #define SHMKEY 75 | | #define K 1024 | | int shmid; | | | | main() | | { | | int i, *pint; | | char *addr1, *addr2; | | extern char *shmat(); | | extern cleanup(); | | | | for (i = 0; i < 20; i++) | | signal(i,cleanup); | | shmid = shmget(SHMKEY,128*K,0777|IPC_CREAT); | | addr1 = shmat(shmid,0,0); | | addr2 = shmat(shmid,0,0); | | printf("addr1 Ox%x addr2 Ox%x\n",addr1,addr2); | | pint = (int *) addr1; | | | | for (i = 0; i < 256, i++) | | *pint++ = i; | | pint = (int *) addr1; | | *pint = 256; | | | | pint = (int *) addr2; | | for (i = 0; i < 256, i++) | | printf("index %d\tvalue %d\n",i,*pint++); | | | | pause(); | | } | | | | cleanup() | | { | | shmctl(shmid,IPC_RMID,0); | | exit(); | | } | +------------------------------------------------------------+ Рисунок 11.11. Присоединение процессом одной и той же области разделяемой памяти дважды 345 +-----------------------------------------------------+ | #include | | #include | | #include | | | | #define SHMKEY 75 | | #define K 1024 | | int shmid; | | | | main() | | { | | int i, *pint; | | char *addr; | | extern char *shmat(); | | | | shmid = shmget(SHMKEY,64*K,0777); | | | | addr = shmat(shmid,0,0); | | pint = (int *) addr; | | | | while (*pint == 0) | | ; | | for (i = 0; i < 256, i++) | | printf("%d\n",*pint++); | | } | +-----------------------------------------------------+ Рисунок 11.12. Разделение памяти между процессами Таблица семафоров Массивы семафоров +-------+ | | +---+---+---+---+---+---+---+ | +------->| 0 | 1 | 2 | 3 | 4 | 5 | 6 | | | +---+---+---+---+---+---+---+ +-------+ | | +---+---+---+ | +------->| 0 | 1 | 2 | | | +---+---+---+ +-------+ | | +---+ | +------->| 0 | | | +---+ +-------+ | | +---+---+---+ | +------->| 0 | 1 | 2 | | | +---+---+---+ +-------+ | - | | - | | - | | - | | - | +-------+ Рисунок 11.13. Структуры данных, используемые в работе над семафорами 346 Синтаксис вызова системной функции semget: id = semget(key,count,flag); где key, flag и id имеют тот же смысл, что и в других механизмах взаимодейс- твия процессов (обмен сообщениями и разделение памяти). В результате выпол- нения функции ядро выделяет запись, указывающую на массив семафоров и содер- жащую счетчик count (Рисунок 11.13). В записи также хранится количество се- мафоров в массиве, время последнего выполнения функций semop и semctl. Сис- темная функция semget на Рисунке 11.14, например, создает семафор из двух элементов. Синтаксис вызова системной функции semop: oldval = semop(id,oplist,count); где id - дескриптор, возвращаемый функцией semget, oplist - указатель на список операций, count - размер списка. Возвращаемое функцией значение oldval является прежним значением семафора, над +------------------------------------------------------------+ | #include | | #include | | #include | | | | #define SEMKEY 75 | | int semid; | | unsigned int count; | | /* определение структуры sembuf в файле sys/sem.h | | * struct sembuf { | | * unsigned shortsem_num; | | * short sem_op; | | * short sem_flg; | | }; */ | | struct sembuf psembuf,vsembuf; /* операции типа P и V */| | | | main(argc,argv) | | int argc; | | char *argv[]; | | { | | int i,first,second; | | short initarray[2],outarray[2]; | | extern cleanup(); | | | | if (argc == 1) | | { | | for (i = 0; i < 20; i++) | | signal(i,cleanup); | | semid = semget(SEMKEY,2,0777|IPC_CREAT); | | initarray[0] = initarray[1] = 1; | | semctl(semid,2,SETALL,initarray); | | semctl(semid,2,GETALL,outarray); | | printf("начальные значения семафоров %d %d\n", | | outarray[0],outarray[1]); | | pause(); /* приостанов до получения сигнала */ | | } | | | | /* продолжение на следующей странице */ | +------------------------------------------------------------+ Рисунок 11.14. Операции установки и снятия блокировки 347 которым производилась операция. Каждый элемент списка операций имеет следую- щий формат: * номер семафора, идентифицирующий элемент массива семафоров, над которым выполняется операция, * код операции, * флаги. +------------------------------------------------------------+ | else if (argv[1][0] == 'a') | | { | | first = 0; | | second = 1; | | } | | else | | { | | first = 1; | | second = 0; | | } | | | | semid = semget(SEMKEY,2,0777); | | psembuf.sem_op = -1; | | psembuf.sem_flg = SEM_UNDO; | | vsembuf.sem_op = 1; | | vsembuf.sem_flg = SEM_UNDO; | | | | for (count = 0; ; count++) | | { | | psembuf.sem_num = first; | | semop(semid,&psembuf,1); | | psembuf.sem_num = second; | | semop(semid,&psembuf,1); | | printf("процесс %d счетчик %d\n",getpid(),count); | | vsembuf.sem_num = second; | | semop(semid,&vsembuf,1); | | vsembuf.sem_num = first; | | semop(semid,&vsembuf,1); | | } | | } | | | | cleanup() | | { | | semctl(semid,2,IPC_RMID,0); | | exit(); | | } | +------------------------------------------------------------+ Рисунок 11.14. Операции установки и снятия блокировки (продолжение) Ядро считывает список операций oplist из адресного пространства задачи и проверяет корректность номеров семафоров, а также наличие у процесса необхо- димых разрешений на чтение и корректировку семафоров (Рисунок 11.15). Если таких разрешений не имеется, системная функция завершается неудачно. Если ядру приходится приостанавливать свою работу при обращении к списку опера- ций, оно возвращает семафорам их прежние значения и находится в состоянии приостанова до наступления ожидаемого события, после чего систем- ная функция запускается вновь. Поскольку ядро хранит коды операций над сема- форами в глобальном списке, оно вновь считывает этот список из пространства 348 +------------------------------------------------------------+ | алгоритм semop /* операции над семафором */ | | входная информация: (1) дескриптор семафора | | (2) список операций над семафором | | (3) количество элементов в списке | | выходная информация: исходное значение семафора | | { | | проверить корректность дескриптора семафора; | | start: считать список операций над семафором из простран- | | ства задачи в пространство ядра; | | проверить наличие разрешений на выполнение всех опера- | | ций; | | | | для (каждой операции в списке) | | { | | если (код операции имеет положительное значение) | | { | | прибавить код операции к значению семафора; | | если (для данной операции установлен флаг UNDO)| | скорректировать структуру восстановления | | для данного процесса; | | вывести из состояния приостанова все процессы, | | ожидающие увеличения значения семафора; | | } | | в противном случае если (код операции имеет отрица-| | тельное значение) | | { | | если (код операции + значение семафора >= 0) | | { | | прибавить код операции к значению семафо- | | ра; | | если (флаг UNDO установлен) | | скорректировать структуру восстанов- | | ления для данного процесса; | | если (значение семафора равно 0) | | /* продолжение на следующей страни- | | * це */ | +------------------------------------------------------------+ Рисунок 11.15. Алгоритм выполнения операций над семафором задачи, когда перезапускает системную функцию. Таким образом, операции вы- полняются комплексно - или все за один сеанс или ни одной. Ядро меняет значение семафора в зависимости от кода операции. Если код операции имеет положительное значение, ядро увеличивает значение семафора и выводит из состояния приостанова все процессы, ожидающие наступления этого события. Если код операции равен 0, ядро проверяет значение семафора: если оно равно 0, ядро переходит к выполнению других операций; в противном случае ядро увеличивает число приостановленных процессов, ожидающих, когда значение семафора станет нулевым, и "засыпает". Если код операции имеет отрицательное значение и если его абсолютное значение не превышает значение семафора, ядро прибавляет код операции (отрицательное число) к значению семафора. Если ре- зультат равен 0, ядро выводит из состояния приостанова все процессы, ожидаю- щие обнуления значения семафора. Если результат меньше абсолютного значения кода операции, ядро приостанавливает процесс до тех пор, пока зна- чение семафора не увеличится. Если процесс приостанавливается посреди опера- ции, он имеет приоритет, допускающий прерывания; следовательно, получив сиг- нал, он выходит из этого состояния. 349 +------------------------------------------------------------+ | вывести из состояния приостанова все | | процессы, ожидающие обнуления значе-| | ния семафора; | | продолжить; | | } | | выполнить все произведенные над семафором в | | данном сеансе операции в обратной последова- | | тельности (восстановить старое значение сема- | | фора); | | если (флаги не велят приостанавливаться) | | вернуться с ошибкой; | | приостановиться (до тех пор, пока значение се- | | мафора не увеличится); | | перейти на start; /* повторить цикл с самого | | * начала * / | | } | | в противном случае /* код операции равен нулю */| | { | | если (значение семафора отлично от нуля) | | { | | выполнить все произведенные над семафором | | в данном сеансе операции в обратной по- | | следовательности (восстановить старое | | значение семафора); | | если (флаги не велят приостанавливаться) | | вернуться с ошибкой; | | приостановиться (до тех пор, пока значение| | семафора не станет нулевым); | | перейти на start; /* повторить цикл */ | | } | | } | | } /* конец цикла */ | | /* все операции над семафором выполнены */ | | скорректировать значения полей, в которых хранится вре-| | мя последнего выполнения операций и идентификаторы | | процессов; | | вернуть исходное значение семафора, существовавшее в | | момент вызова функции semop; | | } | +------------------------------------------------------------+ Рисунок 11.15. Алгоритм выполнения операций над семафором (продолжение) Перейдем к программе, представленной на Рисунке 11.14, и предположим, что пользователь исполняет ее (под именем a.out) три раза в следующем поряд- ке: a.out & a.out a & a.out b & Если программа вызывается без параметров, процесс создает набор семафо- ров из двух элементов и присваивает каждому семафору значение, равное 1. За- тем процесс вызывает функцию pause и приостанавливается для получения сигна- ла, после чего удаляет семафор из системы (cleanup). При выполнении програм- мы с параметром 'a' процесс (A) производит над семафорами в цикле четыре операции: он уменьшает на единицу значение семафора 0, то же самое делает с семафором 1, выполняет команду вывода на печать и вновь увеличивает значения семафоров 0 и 1. Если бы процесс попытался уменьшить значение семафора, рав- 350 ное 0, ему пришлось бы приостановиться, следовательно, семафор можно считать захваченным (недоступным для уменьшения). Поскольку исходные значения сема- форов были равны 1 и поскольку к семафорам не было обращений со стороны дру- гих процессов, процесс A никогда не приостановится, а значения семафоров бу- дут изменяться только между 1 и 0. При выполнении программы с параметром 'b' процесс (B) уменьшает значения семафоров 0 и 1 в порядке, обратном ходу вы- полнения процесса A. Когда процессы A и B выполняются параллельно, может сложиться ситуация, в которой процесс A захватил семафор 0 и хочет захватить семафор 1, а процесс B захватил семафор 1 и хочет захватить семафор 0. Оба процесса перейдут в состояние приостанова, не имея возможности продолжить свое выполнение. Возникает взаимная блокировка, из которой процессы могут выйти только по получении сигнала. Чтобы предотвратить возникновение подобных проблем, процессы могут вы- полнять одновременно несколько операций над семафорами. В последнем примере желаемый эффект достигается благодаря использованию следующих операторов: struct sembuf psembuf[2]; psembuf[0].sem_num = 0; psembuf[1].sem_num = 1; psembuf[0].sem_op = -1; psembuf[1].sem_op = -1; semop(semid,psembuf,2); Psembuf - это список операций, выполняющих одновременное уменьшение значений семафоров 0 и 1. Если какая-то операция не может выполняться, процесс приос- танавливается. Так, например, если значение семафора 0 равно 1, а значение семафора 1 равно 0, ядро оставит оба значения неизменными до тех пор, пока не сможет уменьшить и то, и другое. Установка флага IPC_NOWAIT в функции semop имеет следующий смысл: если ядро попадает в такую ситуацию, когда процесс должен приостановить свое вы- полнение в ожидании увеличения значения семафора выше определенного уровня или, наоборот, снижения этого значения до 0, и если при этом флаг IPC_NOWAIT установлен, ядро выходит из функции с извещением об ошибке. Таким образом, если не приостанавливать процесс в случае невозможности выполнения отдельной операции, можно реализовать условный тип семафора. Если процесс выполняет операцию над семафором, захватывая при этом неко- торые ресурсы, и завершает свою работу без приведения семафора в исходное состояние, могут возникнуть опасные ситуации. Причинами возникновения таких ситуаций могут быть как ошибки программирования, так и сигналы, приводящие к внезапному завершению выполнения процесса. Если после того, как процесс уменьшит значения семафоров, он получит сигнал kill, восстановить прежние значения процессу уже не удастся, поскольку сигналы данного типа не анализи- руются процессом. Следовательно, другие процессы, пытаясь обратиться к сема- форам, обнаружат, что последние заблокированы, хотя сам заблокировавший их процесс уже прекратил свое существование. Чтобы избежать возникновения по- добных ситуаций, в функции semop процесс может установить флаг SEM_UNDO; когда процесс завершится, ядро даст обратный ход всем операциям, выполненным процессом. Для этого в распоряжении у ядра имеется таблица, в которой каждо- му процессу в системе отведена отдельная запись. Запись таблицы содержит указатель на группу структур восстановле- ния, по одной структуре на каждый используемый процессом семафор (Рисунок 11.16). Каждая структура восстановления состоит из трех элементов - иденти- фикатора семафора, его порядкового номера в наборе и установочного значения. Ядро выделяет структуры восстановления динамически, во время первого вы- полнения системной функции semop с установленным флагом SEM_UNDO. При после- дующих обращениях к функции с тем же флагом ядро просматривает структуры восстановления для процесса в поисках структуры с тем же самым идентификато- 351 Заголовки частных структур восстановления Структуры восстановления +------+ | - | | - | | - | | - | +----------+ +----------+ +----------+ +------+ |Дескриптор| |Дескриптор| |Дескриптор| | +-->| Номер +-->| Номер +-->| Номер | +------+ | Значение | | Значение | | Значение | | | +----------+ +----------+ +----------+ | | +----------+ +------+ |Дескриптор| | +-->| Номер | +------+ | Значение | | - | +----------+ | - | | - | | - | +------+ Рисунок 11.16. Структуры восстановления семафоров ром и порядковым номером семафора, что и в формате вызова фун