апуску та- | 4 +----------------------->| 3 | в памяти нов-| | | | лен +---+---+ ++------+ в па- | | ^ ^ мяти | | | | Достаточно | | | | памяти | | | +---+ | Вы- Вы- | | | | грузка грузка | | | Создан | | |За- ++------+ | | |груз-| | fork | | |ка | 8 |<----- | | | | | | | | ++------+ | | | | | | | | Недоста- | | | +---+ точно | | | | памяти | | | | (только система | | | | подкачки) v v | v +-------+ +---+---+ | | Возобновление | | | 6 +----------------------->| 5 | | | | | +-------+ +-------+ Приостановлен, Готов к запуску, выгружен выгружен Рисунок 7.6. Диаграмма переходов процесса из состояние в состояние с указанием моментов проверки и обработки сигналов 189 получил ли процесс сигнал или нет. Условия, в которых формируются сигналы типа "гибель потомка", будут рассмотрены позже. Мы также увидим, что процесс может игнорировать отдельные сигналы, если воспользуется функцией signal. В алгоритме issig ядро просто гасит индикацию тех сигналов, на которые процесс не желает обращать внимание, и привлекает внимание процесса ко всем осталь- ным сигналам. +------------------------------------------------------------+ | алгоритм issig /* проверка получения сигналов */ | | входная информация: отсутствует | | выходная информация: "истина", если процесс получил сигна- | | лы, которые его интересуют | | "ложь" - в противном случае | | { | | выполнить пока (поле в записи таблицы процессов, содер- | | жащее индикацию о получении сигнала, хранит ненулевое | | значение) | | { | | найти номер сигнала, посланного процессу; | | если (сигнал типа "гибель потомка") | | { | | если (сигналы данного типа игнорируются) | | освободить записи таблицы процессов, которые | | соответствуют потомкам, прекратившим существо-| | вание; | | в противном случае если (сигналы данного типа при-| | нимаются) | | возвратить (истину); | | } | | в противном случае если (сигнал не игнорируется) | | возвратить (истину); | | сбросить (погасить) сигнальный разряд, установленный | | в соответствующем поле таблицы процессов, хранящем | | индикацию получения сигнала; | | } | | возвратить (ложь); | | } | +------------------------------------------------------------+ Рисунок 7.7. Алгоритм опознания сигналов 7.2.1 Обработка сигналов Ядро обрабатывает сигналы в контексте того процесса, который получает их, поэтому чтобы обработать сигналы, нужно запустить процесс. Существует три способа обработки сигналов: процесс завершается по получении сигнала, не обращает внимание на сигнал или выполняет особую (пользовательскую) функцию по его получении. Реакцией по умолчанию со стороны процесса, исполняемого в режиме ядра, является вызов функции exit, однако с помощью функции signal процесс может указать другие специальные действия, принимаемые по получении тех или иных сигналов. Синтаксис вызова системной функции signal: oldfunction = signal(signum,function); где signum - номер сигнала, при получении которого будет выполнено действие, связанное с запуском пользовательской функции, function - адрес функции, oldfunction - возвращаемое функцией значение. Вместо адреса функции процесс может передавать вызываемой процедуре signal числа 1 и 0: если function = 1, процесс будет игнорировать все последующие поступления сигнала с номером 190 signum (особый случай, связанный с игнорированием сигнала "гибель потомка", рассматривается в разделе 7.4), если = 0 (значение по умолчанию), процесс по получении сигнала в режиме ядра завершается. В пространстве процесса поддер- живается массив полей для обработки сигналов, по одному полю на каждый опре- деленный в системе сигнал. В поле, соответствующем сигналу с указанным номе- ром, ядро сохраняет адрес пользовательской функции, вызываемой по получении сигнала процессом. Способ обработки сигналов одного типа не влияет на обра- ботку сигналов других типов. +------------------------------------------------------------+ | алгоритм psig /* обработка сигналов после проверки их | | существования */ | | входная информация: отсутствует | | выходная информация: отсутствует | | { | | выбрать номер сигнала из записи таблицы процессов; | | очистить поле с номером сигнала; | | если (пользователь ранее вызывал функцию signal, с по- | | мощью которой сделал указание игнорировать сигнал дан- | | ного типа) | | возвратить управление; | | если (пользователь указал функцию, которую нужно выпол- | | нить по получении сигнала) | | { | | из пространства процесса выбрать пользовательский | | виртуальный адрес функции обработки сигнала; | | /* следующий оператор имеет нежелательные побочные | | эффекты */ | | очистить поле в пространстве процесса, содержащее | | адрес функции обработки сигнала; | | внести изменения в пользовательский контекст: | | искусственно создать в стеке задачи запись, ими- | | тирующую обращение к функции обработки сигнала; | | внести изменения в системный контекст: | | записать адрес функции обработки сигнала в поле | | счетчика команд, принадлежащее сохраненному ре- | | гистровому контексту задачи; | | возвратить управление; | | } | | если (сигнал требует дампирования образа процесса в па- | | мяти) | | { | | создать в текущем каталоге файл с именем "core"; | | переписать в файл "core" содержимое пользовательско-| | го контекста; | | } | | немедленно запустить алгоритм exit; | | } | +------------------------------------------------------------+ Рисунок 7.8. Алгоритм обработки сигналов Обрабатывая сигнал (Рисунок 7.8), ядро определяет тип сигнала и очищает (гасит) разряд в записи таблицы процессов, соответствующий данному типу сиг- нала и установленный в момент получения сигнала процессом. Если функции об- работки сигнала присвоено значение по умолчанию, ядро в отдельных случаях перед завершением процесса сбрасывает на внешний носитель (дампирует) образ процесса в памяти (см. упражнение 7.7). Дампирование удобно для программис- 191 тов тем, что позволяет установить причину завершения процесса и посредством этого вести отладку программ. Ядро дампирует состояние памяти при поступле- нии сигналов, которые сообщают о каких-нибудь ошибках в выполнении процес- сов, как например, попытка исполнения запрещенной команды или обращение к адресу, находящемуся за пределами виртуального адресного пространства про- цесса. Ядро не дампирует состояние памяти, если сигнал не связан с программ- ной ошибкой. Например, прерывание, вызванное нажатием клавиш "delete" или "break" на терминале, имеет своим результатом посылку сигнала, который сооб- щает о том, что пользователь хочет раньше времени завершить процесс, в то время как сигнал о "зависании" является свидетельством нарушения связи с ре- гистрационным терминалом. Эти сигналы не связаны с ошибками в протекании процесса. Сигнал о выходе (quit), однако, вызывает сброс состояния памяти, несмотря на то, что он возникает за пределами выполняемого процесса. Этот сигнал, обычно вызываемый одновременным нажатием клавиш , дает прог- раммисту возможность получать дамп состояния памяти в любой момент после за- пуска процесса, что бывает необходимо, если процесс попадает в бесконечный цикл выполнения одних и тех же команд (зацикливается). Если процесс получает сигнал, на который было решено не обращать внима- ние, выполнение процесса продолжается так, словно сигнала и не было. Пос- кольку ядро не сбрасывает значение соответствующего поля, свидетельствующего о необходимости игнорирования сигнала данного типа, то когда сигнал поступит вновь, процесс опять не обратит на него внимание. Если процесс получает сиг- нал, реагирование на который было признано необходимым, сразу по возвращении процесса в режим задачи выполняется заранее условленное действие, однако прежде чем перевести процесс в режим задачи, ядро еще должно предпринять следующие шаги: 1. Ядро обращается к сохраненному регистровому контексту задачи и выбирает значения счетчика команд и указателя вершины стека, которые будут возв- ращены пользовательскому процессу. 2. Сбрасывает в пространстве процесса прежнее значение поля функции обра- ботки сигнала и присваивает ему значение по умолчанию. 3. Создает новую запись в стеке задачи, в которую, при необходимости выде- ляя дополнительную память, переписывает значения счетчика команд и ука- зателя вершины стека, выбранные ранее из сохраненного регистрового кон- текста задачи. Стек задачи будет выглядеть так, как будто процесс произ- вел обращение к пользовательской функции (обработки сигнала) в той точ- ке, где он вызывал системную функцию или где ядро прервало его выполне- ние (перед опознанием сигнала). 4. Вносит изменения в сохраненный регистровый контекст задачи: устанавлива- ет значение счетчика команд равным адресу функции обработки сигнала, а значение указателя вершины стека равным глубине стека задачи. Таким образом, по возвращении из режима ядра в режим задачи процесс приступит к выполнению функции обработки сигнала; после ее завершения управ- ление будет передано на то место в программе пользователя, где было произве- дено обращение к системной функции или произошло прерывание, тем самым как бы имитируется выход из системной функции или прерывания. В качестве примера можно привести программу (Рисунок 7.9), которая при- нимает сигналы о прерывании (SIGINT) и сама посылает их (в результате выпол- нения функции kill). На Рисунке 7.10 представлены фрагменты программного ко- да, полученные в результате дисассемблирования загрузочного модуля в опера- ционной среде VAX 11/780. При выполнении процесса обращение к библиотечной процедуре kill имеет адрес (шестнадцатиричный) ee; эта процедура в свою оче- редь, прежде чем вызвать системную функцию kill, исполняет команду chmk (пе- ревести процесс в режим ядра) по адресу 10a. Адрес возврата из системной функции - 10c. Во время исполнения системной функции ядро посылает процессу сигнал о прерывании. Ядро обращает внимание на этот сигнал тогда, когда про- цесс собирается вернуться в режим задачи, выбирая из сохраненного регистро- вого контекста адрес возврата 10c и помещая его в стек задачи. При этом ад- 192 рес функции обработки сигнала, 104, ядро помещает в сохраненный регистровый контекст задачи. На Рисунке 7.11 показаны различные состояния стека задачи и сохраненного регистрового контекста. В рассмотренном алгоритме обработки сигналов имеются некоторые несоот- ветствия. Первое из них и наиболее важное связано с очисткой перед возвраще- нием процесса в режим задачи того поля в пространстве процесса, которое со- держит адрес пользовательской функции обработки сигнала. Если процессу снова понадобится обработать сигнал, ему опять придется прибегнуть к помощи сис- темной функции signal. При этом могут возникнуть нежелательные последс- +-------------------------------------------+ | #include | | main() | | { | | extern catcher(); | | signal(SIGINT,catcher); | | kill(0,SIGINT); | | } | | | | catcher() | | { | | } | +-------------------------------------------+ Рисунок 7.9. Исходный текст программы приема сигналов +--------------------------------------------------------+ | **** VAX DISASSEMBLER **** | | | | _main() | | e4: | | e6: pushab Ox18(pc) | | ec: pushl $Ox2 | | # в следующей строке вызывается функция signal | | ee: calls $Ox2,Ox23(pc) | | f5: pushl $Ox2 | | f7: clrl -(sp) | | # в следующей строке вызывается библиотечная процеду-| | ра kill | | f9: calls $Ox2,Ox8(pc) | | 100: ret | | 101: halt | | 102: halt | | 103: halt | | _catcher() | | 104: | | 106: ret | | 107: halt | | _kill() | | 108: | | # в следующей строке вызывается внутреннее прерывание| | операционной системы | | 10a: chmk $Ox25 | | 10c: bgequ Ox6 | | 10e: jmp Ox14(pc) | | 114: clrl r0 | | 116: ret | +--------------------------------------------------------+ Рисунок 7.10. Результат дисассемблирования программы приема сигналов 193 До После | | | | | | +-->+--------------------+ | | Вершина | | Новая запись с вы- | | | +-- стека --+ | зовом функции | | | | задачи | | | | | ---->|Адрес возврата (10c)| +--------------------+<--+ - +--------------------+ | Стек задачи | - | Стек задачи | | до | - | до | | получения сигнала | - | получения сигнала | +--------------------+ - +--------------------+ Стек задачи - Стек задачи - +--------------------+ - +--------------------+ | Адрес возврата | - | Адрес возврата | | в процессе (10c) -|---------------- | в процессе (104) | +--------------------+ +--------------------+ | Сохраненный регист-| | Сохраненный регист-| | ровый контекст за- | | ровый контекст за- | | дачи | | дачи | +--------------------+ +--------------------+ Системный контекстный Системный контекстный уровень 1 уровень 1 Область сохранения Область сохранения регистров регистров Рисунок 7.11. Стек задачи и область сохранения структур ядра до и после получения сигнала твия: например, могут создасться условия для конкуренции, если второй раз сигнал поступит до того, как процесс получит возможность запустить системную функцию. Поскольку процесс выполняется в режиме задачи, ядру следовало бы произвести переключение контекста, чтобы увеличить тем самым шансы процесса на получение сигнала до момента сброса значения поля функции обработки сиг- нала. Эту ситуацию можно разобрать на примере программы, представленной на Ри- сунке 7.12. Процесс обращается к системной функции signal для того, чтобы дать указание принимать сигналы о прерываниях и исполнять по их получении функцию sigcatcher. Затем он порождает новый процесс, запускает системную функцию nice, позволяющую сделать приоритет запуска процесса-родителя ниже приоритета его потомка (см. главу 8), и входит в бесконечный цикл. Порожден- ный процесс задерживает свое выполнение на 5 секунд, чтобы дать родительско- му процессу время исполнить системную функцию nice и снизить свой приоритет. После этого порожденный процесс входит в цикл, в каждой итерации которого он посылает родительскому процессу сигнал о прерывании (посредством обращения к функции kill). Если в результате ошибки, например, из-за того, что родитель- ский процесс больше не существует, kill завершается, то завершается и порож- денный процесс. Вся идея состоит в том, что родительскому процессу следует запускать функцию обработки сигнала при каждом получении сигнала о прерыва- нии. Функция обработки сигнала выводит сообщение и снова обращается к функ- ции signal при очередном появлении сигнала о прерывании, родительский же процесс продолжает 194 +------------------------------------------------------------+ | #include | | sigcatcher() | | { | | printf("PID %d принял сигнал\n",getpid()); /* печать | | PID */ | | signal(SIGINT,sigcatcher); | | } | | | | main() | | { | | int ppid; | | | | signal(SIGINT,sigcatcher); | | | | if (fork() == 0) | | { | | /* дать процессам время для выполнения установок */ | | sleep(5); /* библиотечная функция приостанова на| | 5 секунд */ | | ppid = getppid(); /* получить идентификатор родите- | | ля */ | | for (;;) | | if (kill(ppid,SIGINT) == -1) | | exit(); | | } | | | | /* чем ниже приоритет, тем выше шансы возникновения кон-| | куренции */ | | nice(10); | | for (;;) | | ; | | } | +------------------------------------------------------------+ Рисунок 7.12. Программа, демонстрирующая возникновение соперничества между процессами в ходе обработки сигналов исполнять циклический набор команд. Однако, возможна и следующая очередность наступления событий: 1. Порожденный процесс посылает родительскому процессу сигнал о прерывании. 2. Родительский процесс принимает сигнал и вызывает функцию обработки сиг- нала, но резервируется ядром, которое производит переключение контекста до того, как функция signal будет вызвана повторно. 3. Снова запускается порожденный процесс, который посылает родительскому процессу еще один сигнал о прерывании. 4. Родительский процесс получает второй сигнал о прерывании, но перед тем он не успел сделать никаких распоряжений относительно способа обработки сигнала. Когда выполнение родительского процесса будет возобновлено, он завершится. В программе описывается именно такое поведение процессов, поскольку вы- зов родительским процессом функции nice приводит к тому, что ядро будет чаще запускать на выполнение порожденный процесс. По словам Ричи (эти сведения были получены в частной беседе), сигналы были задуманы как события, которые могут быть как фатальными, так и проходя- щими незаметно, которые не всегда обрабатываются, поэтому в ранних версиях системы конкуренция процессов, связанная с посылкой сигналов, не фиксирова- лась. Тем не менее, она представляет серьезную проблему в тех программах, где осуществляется прием сигналов. Эта проблема была бы устранена, если бы 195 поле описания сигнала не очищалось по его получении. Однако, такое решение породило бы новую проблему: если поступающий сигнал принимается, а поле очи- щено, вложенные обращения к функции обработки сигнала могут переполнить стек. С другой стороны, ядро могло бы сбросить значение функции обработки сигнала, тем самым делая распоряжение игнорировать сигналы данного типа до тех пор, пока пользователь вновь не укажет, что нужно делать по получении подобных сигналов. Такое решение предполагает потерю информации, так как процесс не в состоянии узнать, сколько сигналов им было получено. Однако, информации при этом теряется не больше, чем в том случае, когда процесс по- лучает большое количество сигналов одного типа до того, как получает возмож- ность их обработать. В системе BSD, наконец, процесс имеет возможность бло- кировать получение сигналов и снимать блокировку при новом обращении к сис- темной функции; когда процесс снимает блокировку сигналов, ядро посылает процессу все сигналы, отложенные (повисшие) с момента установки блокировки. Когда процесс получает сигнал, ядро автоматически блокирует получение следу- ющего сигнала до тех пор, пока функция обработки сигнала не закончит работу. В этих действиях ядра наблюдается аналогия с тем, как ядро реагирует на ап- паратные прерывания: оно блокирует появление новых прерываний на время обра- ботки предыдущих. Второе несоответствие в обработке сигналов связано с приемом сигналов, поступающих во время исполнения системной функции, когда процесс приостанов- лен с допускающим прерывания приоритетом. Сигнал побуждает процесс выйти из приостанова (с помощью longjump), вернуться в режим задачи и вызвать функцию обработки сигнала. Когда функция обработки сигнала завершает работу, проис- ходит то, что процесс выходит из системной функции с ошибкой, сообщающей о прерывании ее выполнения. Узнав об ошибке, пользователь запускает системную функцию повторно, однако более удобно было бы, если бы это действие автома- тически выполнялось ядром, как в системе BSD. Третье несоответствие проявляется в том случае, когда процесс игнорирует поступивший сигнал. Если сигнал поступает в то время, когда процесс находит- ся в состоянии приостанова с допускающим прерывания приоритетом, процесс во- зобновляется, но не выполняет longjump. Другими словами, ядро узнает о том, что процесс проигнорировал поступивший сигнал только после возобновления его выполнения. Логичнее было бы оставить процесс в состоянии приостанова. Одна- ко, в момент посылки сигнала к пространству процесса, в котором ядро хранит адрес функции обработки сигнала, может отсутствовать доступ. Эта проблема может быть решена путем запоминания адреса функции обработки сигнала в запи- си таблицы процессов, обращаясь к которой, ядро получало бы возможность ре- шать вопрос о необходимости возобновления процесса по получении сигнала. С другой стороны, процесс может немедленно вернуться в состояние приостанова (по алгоритму sleep), если обнаружит, что в его возобновлении не было необ- ходимости. Однако, пользовательские процессы не имеют возможности осознавать собственное возобновление, поскольку ядро располагает точку входа в алгоритм sleep внутри цикла с условием продолжения (см. главу 2), переводя процесс вновь в состояние приостанова, если ожидаемое процессом событие в действи- тельности не имело места. Ко всему сказанному выше следует добавить, что ядро обрабатывает сигналы типа "гибель потомка" не так, как другие сигналы. В частности, когда процесс узнает о получении сигнала "гибель потомка", он выключает индикацию сигнала в соответствующем поле записи таблицы процессов и по умолчанию действует так, словно никакого сигнала и не поступало. Назначение сигнала "гибель по- томка" состоит в возобновлении выполнения процесса, приостановленного с до- пускающим прерывания приоритетом. Если процесс принимает такой сигнал, он, как и во всех остальных случаях, запускает функцию обработки сигнала. Дейст- вия, предпринимаемые ядром в том случае, когда процесс игнорирует поступив- ший сигнал этого типа, будут описаны в разделе 7.4. Наконец, когда процесс вызвал функцию signal с параметром "гибель потомка" (death of child), ядро посылает ему соответствующий сигнал, если он имеет потомков, прекративших существование. В разделе 7.4 на этом моменте мы остановимся более подробно. 196 7.2.2 Группы процессов Несмотря на то, что в системе UNIX процессы идентифицируются уникальным кодом (PID), системе иногда приходится использовать для идентификации про- цессов номер "группы", в которую они входят. Например, процессы, имеющие об- щего предка в лице регистрационного shell'а, взаимосвязаны, и поэтому когда пользователь нажимает клавиши "delete" или "break", или когда терминальная линия "зависает", все эти процессы получают соответствующие сигналы. Ядро использует код группы процессов для идентификации группы взаимосвязанных процессов, которые при наступлении определенных событий должны получать об- щий сигнал. Код группы запоминается в таблице процессов; процессы из одной группы имеют один и тот же код группы. Для того, чтобы присвоить коду группы процессов начальное значение, при- равняв его коду идентификации процесса, следует воспользоваться системной функцией setpgrp. Синтаксис вызова функции: grp = setpgrp(); где grp - новый код группы процессов. При выполнении функции fork про- цесс-потомок наследует код группы своего родителя. Использование функции setpgrp при назначении для процесса операторского терминала имеет важные особенности, на которые стоит обратить внимание (см. раздел 10.3.5). 7.2.3 Посылка сигналов процессами Для посылки сигналов процессы используют системную функцию kill. Синтак- сис вызова функции: kill(pid,signum) где в pid указывается адресат посылаемого сигнала (область действия сигна- ла), а в signum - номер посылаемого сигнала. Связь между значением pid и со- вокупностью выполняющихся процессов следующая: * Если pid - положительное целое число, ядро посылает сигнал процессу с идентификатором pid. * Если значение pid равно 0, сигнал посылается всем процессам, входящим в одну группу с процессом, вызвавшим функцию kill. * Если значение pid равно -1, сигнал посылается всем процессам, у которых реальный код идентификации пользователя совпадает с тем, под которым ис- полняется процесс, вызвавший функцию kill (об этих кодах более подробно см. в разделе 7.6). Если процесс, пославший сигнал, исполняется под ко- дом идентификации суперпользователя, сигнал рассылается всем процессам, кроме процессов с идентификаторами 0 и 1. * Если pid - отрицательное целое число, но не -1, сигнал посылается всем процессам, входящим в группу с номером, равным абсолютному значению pid. Во всех случаях, если процесс, пославший сигнал, исполняется под кодом идентификации пользователя, не являющегося суперпользователем, или если коды идентификации пользователя (реальный и исполнительный) у этого процесса не совпадают с соответствующими кодами процесса, принимающего сигнал, kill за- вершается неудачно. В программе, приведенной на Рисунке 7.13, главный процесс сбрасывает ус- тановленное ранее значение номера группы и порождает 10 новых процессов. При рождении каждый процесс-потомок наследует номер группы процессов своего ро- дителя, однако, процессы, созданные в нечетных итерациях цикла, сбрасывают это значение. Системные функции getpid и getpgrp возвращают значения кода идентификации выполняемого процесса и номера группы, в которую он входит, а функция pause приостанавливает выполнение процесса до момента получения сиг- нала. В конечном итоге родительский процесс запускает функцию kill и посыла- ет сигнал о прерывании всем процессам, входящим в одну с ним группу. Ядро 197 +------------------------------------------------------------+ | #include | | main() | | { | | register int i; | | | | setpgrp(); | | for (i = 0; i < 10; i++) | | { | | if (fork() == 0) | | { | | /* порожденный процесс */ | | if (i & 1) | | setpgrp(); | | printf("pid = %d pgrp = %d\n",getpid(),getpgrp());| | pause(); /* системная функция приостанова вы- | | полнения */ | | } | | } | | kill(0,SIGINT); | | } | +------------------------------------------------------------+ Рисунок 7.13. Пример использования функции setpgrp посылает сигнал пяти "четным" процессам, не сбросившим унаследованное значе- ние номера группы, при этом пять "нечетных" процессов продолжают свое выпол- нение. 7.3 ЗАВЕРШЕНИЕ ВЫПОЛНЕНИЯ ПРОЦЕССА В системе UNIX процесс завершает свое выполнение, запуская системную функцию exit. После этого процесс переходит в состояние "прекращения сущест- вования" (см. Рисунок 6.1), освобождает ресурсы и ликвидирует свой контекст. Синтаксис вызова функции: exit(status); где status - значение, возвращаемое функцией родительскому процессу. Процес- сы могут вызывать функцию exit как в явном, так и в неявном виде (по оконча- нии выполнения программы: начальная процедура (startup), компонуемая со все- ми программами на языке Си, вызывает функцию exit на выходе программы из функции main, являющейся общей точкой входа для всех программ). С другой стороны, ядро может вызывать функцию exit по своей инициативе, если процесс не принял посланный ему сигнал (об этом мы уже говорили выше). В этом случае значение параметра status равно номеру сигнала. Система не накладывает никакого ограничения на продолжительность выпол- нения процесса, и зачастую процессы существуют в течение довольно длительно- го времени. Нулевой процесс (программа подкачки) и процесс 1 (init), к при- меру, существуют на протяжении всего времени жизни системы. Продолжительными процессами являются также getty-процессы, контролирующие работу терминальной линии, ожидая регистрации пользователей, и процессы общего назначения, вы- полняемые под руководством администратора. На Рисунке 7.14 приведен алгоритм функции exit. Сначала ядро отменяет обработку всех сигналов, посылаемых процессу, поскольку ее продолжение ста- новится бессмысленным. Если процесс, вызывающий функцию exit, возглавляет 198 +------------------------------------------------------------+ | алгоритм exit | | входная информация: код, возвращаемый родительскому про- | | цессу | | выходная информация: отсутствует | | { | | игнорировать все сигналы; | | если (процесс возглавляет группу процессов, ассоцииро- | | ванную с операторским терминалом) | | { | | послать всем процессам, входящим в группу, сигнал о | | "зависании"; | | сбросить в ноль код группы процессов; | | } | | закрыть все открытые файлы (внутренняя модификация алго-| | ритма close); | | освободить текущий каталог (алгоритм iput); | | освободить области и память, ассоциированную с процессом| | (алгоритм freereg); | | создать запись с учетной информацией; | | прекратить существование процесса (перевести его в соот-| | ветствующее состояние); | | назначить всем процессам-потомкам в качестве родителя | | процесс init (1); | | если кто-либо из потомков прекратил существование, | | послать процессу init сигнал "гибель потомка"; | | послать сигнал "гибель потомка" родителю данного процес-| | са; | | переключить контекст; | | } | +------------------------------------------------------------+ Рисунок 7.14. Алгоритм функции exit группу процессов, ассоциированную с операторским терминалом (см. раздел 10.3.5), ядро делает предположение о том, что пользователь прекращает рабо- ту, и посылает всем процессам в группе сигнал о "зависании". Таким образом, если пользователь в регистрационном shell'е нажмет последовательность кла- виш, означающую "конец файла" (Ctrl-d), при этом с терминалом остались свя- занными некоторые из существующих процессов, процесс, выполняющий функцию exit, пошлет им всем сигнал о "зависании". Кроме того, ядро сбрасывает в ноль значение кода группы процессов для всех процессов, входящих в данную группу, поскольку не исключена возможность того, что позднее текущий код идентификации процесса (процесса, который вызвал функцию exit) будет присво- ен другому процессу и тогда последний возглавит группу с указанным кодом. Процессы, входившие в старую группу, в новую группу входить не будут. После этого ядро просматривает дескрипторы открытых файлов, закрывает каждый из этих файлов по алгоритму close и освобождает по алгоритму iput индексы теку- щего каталога и корня (если он изменялся). Наконец, ядро освобождает всю выделенную задаче память вместе с соответ- ствующими областями (по алгоритму detachreg) и переводит процесс в состояние прекращения существования. Ядро сохраняет в таблице процессов код возврата функции exit (status), а также суммарное время исполнения процесса и его по- томков в режиме ядра и режиме задачи. В разделе 7.4 при рассмотрении функции wait будет показано, каким образом процесс получает информацию о времени вы- полнения своих потомков. Ядро также создает в глобальном учетном файле за- пись, которая содержит различную статистическую информацию о выполнении про- цесса, такую как код идентификации пользователя, использование ресурсов цен- трального процессора и памяти, объем потоков ввода-вывода, связанных с про- 199 цессом. Пользовательские программы могут в любой момент обратиться к учетно- му файлу за статистическими данными, представляющими интерес с точки зрения слежения за функционированием системы и организации расчетов с пользователя- ми. Ядро удаляет процесс из дерева процессов, а его потомков передает про- цессу 1 (init). Таким образом, процесс 1 становится законным родителем всех продолжающих существование потомков завершающегося процесса. Если кто-либо из потомков прекращает существование, завершающийся процесс посылает процес- су init сигнал "гибель потомка" для того, чтобы процесс начальной загрузки мог удалить запись о потомке из таблицы процессов (см. раздел 7.9); кроме того, завершающийся процесс посылает этот сигнал своему родителю. В типичной ситуации родительский процесс синхронизирует свое выполнение с завершающимся потомком с помощью системной функции wait. Прекращая существование, процесс переключает контекст и ядро может теперь выбирать для исполнения следующий процесс; ядро с этих пор уже не будет исполнять процесс, прекративший сущес- твов