The design of the unix operating system by Maurice J

Вид материалаРеферат
Подобный материал:
1   ...   21   22   23   24   25   26   27   28   ...   55


* Сигналы, посылаемые процессу, который выполняется в режиме

задачи, например, сигнал тревоги (alarm), посылаемый по исте-

чении определенного периода времени, или произвольные сигна-

лы, которыми обмениваются процессы, использующие функцию

kill;

* Сигналы, связанные с терминальным взаимодействием, например,

с "зависанием" терминала (когда сигнал-носитель на терминаль-

ной линии прекращается по любой причине) или с нажатием кла-

виш "break" и "delete" на клавиатуре терминала;

* Сигналы, с помощью которых производится трассировка выполне-

ния процесса.

Условия применения сигналов каждой группы будут рассмотрены в

этой и последующих главах.

Концепция сигналов имеет несколько аспектов, связанных с тем,

каким образом ядро посылает сигнал процессу, каким образом про-

цесс обрабатывает сигнал и управляет реакцией на него. Посылая

сигнал процессу, ядро устанавливает в единицу разряд в поле сиг-

нала записи таблицы процессов, соответствующий типу сигнала. Если

процесс находится в состоянии приостанова с приоритетом, допуска-

ющим прерывания, ядро возобновит его выполнение. На этом роль от-

правителя сигнала (процесса или ядра) исчерпывается. Процесс мо-

жет запоминать сигналы различных типов, но не имеет возможности

запоминать количество получаемых сигналов каждого типа. Например,

если процесс получает сигнал о "зависании" или об удалении про-

цесса из системы, он устанавливает в единицу соответствующие раз-

ряды в поле сигналов таблицы процессов, но не может сказать,

сколько экземпляров сигнала каждого типа он получил.

Ядро проверяет получение сигнала, когда процесс собирается

перейти из режима ядра в режим задачи, а также когда он переходит

в состояние приостанова или выходит из этого состояния с доста-

точно низким приоритетом планирования (см. Рисунок 7.6). Ядро об-

рабатывает сигналы только тогда, когда процесс возвращается из

режима ядра в режим задачи. Таким образом, сигнал не оказывает

немедленного воздействия на поведение процесса, исполняемого в

режиме ядра. Если процесс исполняется в режиме задачи, а ядро тем

временем обрабатывает прерывание, послужившее поводом для посылки

процессу сигнала, ядро распознает и обработает сигнал по выходе

из прерывания. Таким образом, процесс не будет исполняться в ре-

жиме задачи, пока какие-то сигналы остаются необработанными.

На Рисунке 7.7 представлен алгоритм, с помощью которого ядро

определяет, получил ли процесс сигнал или нет. Условия, в которых

формируются сигналы типа "гибель потомка", будут рассмотрены поз-

же. Мы также увидим, что процесс может игнорировать отдельные

сигналы, если воспользуется функцией signal. В алгоритме issig

ядро просто гасит индикацию тех сигналов, на которые процесс не

желает обращать внимание, и привлекает внимание процесса ко всем

остальным сигналам.


Выполняется в

режиме задачи

--------┐

│ │ Проверка

│ 1 │ и

Вызов функ- │ │ - - обработка

ции, преры- LT------- - - сигналов

вание │ - --

Преры- ------┐ --------- │- -│- - -

вание, │ │ │ --------- L---┐ Возврат в

возврат│ │ │ │ Возврат │ режим задачи

из пре-│ │ │ │ │

рыва-│ v v │ Выполняет- │

--------┐ ния │ -------+┐ся в режи- -+------┐

│ │ L-->│ │ме ядра │ │

│ 9 │<-----------+ 2 +------------>│ 7 │

│ │ Выход │ │ Резервирует-│ │

L-------- LT------- ся L--------

Прекращение │ Зарезер-

существования │- - -│- - - - - - - - ┐ вирован

│ │- - - - - - - ┐ L - -┐

----------------- L------┐ Проверка

│ Приостанов Запуск │ L - - - сигналов

v │

При---------┐ --+-----┐ Готов к

ос- │ │ Возобновление │ │ запуску

та- │ 4 +----------------------->│ 3 │ в памяти

нов-│ │ │ │

лен L---T---- LT-------

в па- │ │

мяти │ │ │ │ Достаточно

│ │ │ │ памяти

│ │ │ L---┐

│ Вы- Вы- │ │ │

│ грузка грузка │ │ │ Создан

│ │ │За- -+------┐

│ │ │груз-│ │ fork

│ │ │ка │ 8 │<-----

│ │ │ │ │

│ │ │ LT-------

│ │ │ │

│ │ │ │ Недоста-

│ │ │ ----- точно

│ │ │ │ памяти

│ │ │ │ (только система

│ │ │ │ подкачки)

v v │ v

--------┐ ----+---┐

│ │ Возобновление │ │

│ 6 +----------------------->│ 5 │

│ │ │ │

L-------- L--------

Приостановлен, Готов к запуску,

выгружен выгружен


Рисунок 7.6. Диаграмма переходов процесса из состояние в сос-

тояние с указанием моментов проверки и обработки

сигналов


-------------------------------------------------------------┐

│ алгоритм issig /* проверка получения сигналов */ │

│ входная информация: отсутствует │

│ выходная информация: "истина", если процесс получил сигна- │


│ лы, которые его интересуют │

│ "ложь" - в противном случае │

│ { │

│ выполнить пока (поле в записи таблицы процессов, содер- │

│ жащее индикацию о получении сигнала, хранит ненулевое │

│ значение) │

│ { │

│ найти номер сигнала, посланного процессу; │

│ если (сигнал типа "гибель потомка") │

│ { │

│ если (сигналы данного типа игнорируются) │

│ освободить записи таблицы процессов, которые │

│ соответствуют потомкам, прекратившим существо-│

│ вание; │

│ в противном случае если (сигналы данного типа при-│

│ нимаются) │

│ возвратить (истину); │

│ } │

│ в противном случае если (сигнал не игнорируется) │

│ возвратить (истину); │

│ сбросить (погасить) сигнальный разряд, установленный │

│ в соответствующем поле таблицы процессов, хранящем │

│ индикацию получения сигнала; │

│ } │

│ возвратить (ложь); │

│ } │

L-------------------------------------------------------------


Рисунок 7.7. Алгоритм опознания сигналов


7.2.1 Обработка сигналов


Ядро обрабатывает сигналы в контексте того процесса, который

получает их, поэтому чтобы обработать сигналы, нужно запустить

процесс. Существует три способа обработки сигналов: процесс за-

вершается по получении сигнала, не обращает внимание на сигнал

или выполняет особую (пользовательскую) функцию по его получении.

Реакцией по умолчанию со стороны процесса, исполняемого в режиме

ядра, является вызов функции exit, однако с помощью функции

signal процесс может указать другие специальные действия, прини-

маемые по получении тех или иных сигналов.

Синтаксис вызова системной функции signal:

oldfunction = signal(signum,function);

где signum - номер сигнала, при получении которого будет выполне-

но действие, связанное с запуском пользовательской функции,

function - адрес функции, oldfunction - возвращаемое функцией


значение. Вместо адреса функции процесс может передавать вызывае-

мой процедуре signal числа 1 и 0: если function = 1, процесс бу-

дет игнорировать все последующие поступления сигнала с номером

signum (особый случай, связанный с игнорированием сигнала "гибель

потомка", рассматривается в разделе 7.4), если = 0 (значение по

умолчанию), процесс по получении сигнала в режиме ядра завершает-

ся. В пространстве процесса поддерживается массив полей для обра-

ботки сигналов, по одному полю на каждый определенный в системе

сигнал. В поле, соответствующем сигналу с указанным номером, ядро

сохраняет адрес пользовательской функции, вызываемой по получении

сигнала процессом. Способ обработки сигналов одного типа не вли-

яет на обработку сигналов других типов.


-------------------------------------------------------------┐

│ алгоритм psig /* обработка сигналов после проверки их │

│ существования */ │

│ входная информация: отсутствует │

│ выходная информация: отсутствует │

│ { │

│ выбрать номер сигнала из записи таблицы процессов; │

│ очистить поле с номером сигнала; │

│ если (пользователь ранее вызывал функцию signal, с по- │

│ мощью которой сделал указание игнорировать сигнал дан- │

│ ного типа) │

│ возвратить управление; │

│ если (пользователь указал функцию, которую нужно выпол- │

│ нить по получении сигнала) │

│ { │

│ из пространства процесса выбрать пользовательский │

│ виртуальный адрес функции обработки сигнала; │

│ /* следующий оператор имеет нежелательные побочные │

│ эффекты */ │

│ очистить поле в пространстве процесса, содержащее │

│ адрес функции обработки сигнала; │

│ внести изменения в пользовательский контекст: │

│ искусственно создать в стеке задачи запись, ими- │

│ тирующую обращение к функции обработки сигнала; │

│ внести изменения в системный контекст: │

│ записать адрес функции обработки сигнала в поле │

│ счетчика команд, принадлежащее сохраненному ре- │

│ гистровому контексту задачи; │

│ возвратить управление; │

│ } │

│ если (сигнал требует дампирования образа процесса в па- │

│ мяти) │

│ { │

│ создать в текущем каталоге файл с именем "core"; │

│ переписать в файл "core" содержимое пользовательско-│

│ го контекста; │

│ } │

│ немедленно запустить алгоритм exit; │

│ } │

L-------------------------------------------------------------


Рисунок 7.8. Алгоритм обработки сигналов


Обрабатывая сигнал (Рисунок 7.8), ядро определяет тип сигнала

и очищает (гасит) разряд в записи таблицы процессов, соответству-

ющий данному типу сигнала и установленный в момент получения сиг-

нала процессом. Если функции обработки сигнала присвоено значение

по умолчанию, ядро в отдельных случаях перед завершением процесса

сбрасывает на внешний носитель (дампирует) образ процесса в памя-

ти (см. упражнение 7.7). Дампирование удобно для программистов

тем, что позволяет установить причину завершения процесса и пос-


редством этого вести отладку программ. Ядро дампирует состояние

памяти при поступлении сигналов, которые сообщают о каких-нибудь

ошибках в выполнении процессов, как например, попытка исполнения

запрещенной команды или обращение к адресу, находящемуся за пре-

делами виртуального адресного пространства процесса. Ядро не дам-

пирует состояние памяти, если сигнал не связан с программной

ошибкой. Например, прерывание, вызванное нажатием клавиш

"delete" или "break" на терминале, имеет своим результатом посыл-

ку сигнала, который сообщает о том, что пользователь хочет раньше

времени завершить процесс, в то время как сигнал о "зависании"

является свидетельством нарушения связи с регистрационным терми-

налом. Эти сигналы не связаны с ошибками в протекании процесса.

Сигнал о выходе (quit), однако, вызывает сброс состояния памяти,

несмотря на то, что он возникает за пределами выполняемого про-

цесса. Этот сигнал, обычно вызываемый одновременным нажатием кла-

виш , дает программисту возможность получать дамп состоя-

ния памяти в любой момент после запуска процесса, что бывает

необходимо, если процесс попадает в бесконечный цикл выполнения

одних и тех же команд (зацикливается).

Если процесс получает сигнал, на который было решено не обра-

щать внимание, выполнение процесса продолжается так, словно сиг-

нала и не было. Поскольку ядро не сбрасывает значение соответс-

твующего поля, свидетельствующего о необходимости игнорирования

сигнала данного типа, то когда сигнал поступит вновь, процесс

опять не обратит на него внимание. Если процесс получает сигнал,

реагирование на который было признано необходимым, сразу по возв-

ращении процесса в режим задачи выполняется заранее условленное

действие, однако прежде чем перевести процесс в режим задачи, яд-

ро еще должно предпринять следующие шаги:

1. Ядро обращается к сохраненному регистровому контексту задачи

и выбирает значения счетчика команд и указателя вершины сте-

ка, которые будут возвращены пользовательскому процессу.

2. Сбрасывает в пространстве процесса прежнее значение поля

функции обработки сигнала и присваивает ему значение по умол-

чанию.

3. Создает новую запись в стеке задачи, в которую, при необходи-

мости выделяя дополнительную память, переписывает значения

счетчика команд и указателя вершины стека, выбранные ранее из

сохраненного регистрового контекста задачи. Стек задачи будет

выглядеть так, как будто процесс произвел обращение к пользо-

вательской функции (обработки сигнала) в той точке, где он

вызывал системную функцию или где ядро прервало его выполне-

ние (перед опознанием сигнала).

4. Вносит изменения в сохраненный регистровый контекст задачи:

устанавливает значение счетчика команд равным адресу функции

обработки сигнала, а значение указателя вершины стека равным

глубине стека задачи.

Таким образом, по возвращении из режима ядра в режим задачи

процесс приступит к выполнению функции обработки сигнала; после

ее завершения управление будет передано на то место в программе

пользователя, где было произведено обращение к системной функции

или произошло прерывание, тем самым как бы имитируется выход из

системной функции или прерывания.

В качестве примера можно привести программу (Рисунок 7.9),

которая принимает сигналы о прерывании (SIGINT) и сама посылает

их (в результате выполнения функции kill). На Рисунке 7.10 предс-

тавлены фрагменты программного кода, полученные в результате дис-

ассемблирования загрузочного модуля в операционной среде VAX

11/780. При выполнении процесса обращение к библиотечной процеду-

ре kill имеет адрес (шестнадцатиричный) ee; эта процедура в свою

очередь, прежде чем вызвать системную функцию kill, исполняет ко-

манду chmk (перевести процесс в режим ядра) по адресу 10a. Адрес

возврата из системной функции - 10c. Во время исполнения систем-

ной функции ядро посылает процессу сигнал о прерывании. Ядро об-

ращает внимание на этот сигнал тогда, когда процесс собирается

вернуться в режим задачи, выбирая из сохраненного регистрового

контекста адрес возврата 10c и помещая его в стек задачи. При

этом адрес функции обработки сигнала, 104, ядро помещает в сохра-

ненный регистровый контекст задачи. На Рисунке 7.11 показаны

различные состояния стека задачи и сохраненного регистрового кон-

текста.

В рассмотренном алгоритме обработки сигналов имеются некото-

рые несоответствия. Первое из них и наиболее важное связано с

очисткой перед возвращением процесса в режим задачи того поля в

пространстве процесса, которое содержит адрес пользовательской

функции обработки сигнала. Если процессу снова понадобится обра-

ботать сигнал, ему опять придется прибегнуть к помощи системной

функции signal. При этом могут возникнуть нежелательные последс-


--------------------------------------------┐

│ #include

│ main() │

│ { │

│ extern catcher(); │

│ signal(SIGINT,catcher); │

│ kill(0,SIGINT); │

│ } │

│ │

│ catcher() │

│ { │

│ } │

L--------------------------------------------


Рисунок 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 │

L---------------------------------------------------------

Рисунок 7.10. Результат дисассемблирования программы приема

сигналов


До После

│ │ │ │

│ │ --->+--------------------+

│ │ Вершина │ │ Новая запись с вы- │

│ │ --- стека --- │ зовом функции │

│ │ │ задачи │ │

│ │ │ >│Адрес возврата (10c)│

+--------------------+<--- +--------------------+

│ Стек задачи │ │ Стек задачи │

│ до │ │ до │

│ получения сигнала │ │ получения сигнала │

L--------------------- L---------------------

Стек задачи Стек задачи

---------------------┐ ---------------------┐

│ Адрес возврата │ │ Адрес возврата │

│ в процессе (10c) │ │ в процессе (104) │

+--------------------+ +--------------------+

│ Сохраненный регист-│ │ Сохраненный регист-│

│ ровый контекст за- │ │ ровый контекст за- │

│ дачи │ │ дачи │

L--------------------- L---------------------

Системный контекстный Системный контекстный

уровень 1 уровень 1

Область сохранения Область сохранения

регистров регистров


Рисунок 7.11. Стек задачи и область сохранения структур ядра

до и после получения сигнала


твия: например, могут создасться условия для конкуренции, если

второй раз сигнал поступит до того, как процесс получит возмож-

ность запустить системную функцию. Поскольку процесс выполняется

в режиме задачи, ядру следовало бы произвести переключение кон-

текста, чтобы увеличить тем самым шансы процесса на получение

сигнала до момента сброса значения поля функции обработки сигна-

ла.

Эту ситуацию можно разобрать на примере программы, представ-

ленной на Рисунке 7.12. Процесс обращается к системной функции

signal для того, чтобы дать указание принимать сигналы о прерыва-

ниях и исполнять по их получении функцию sigcatcher. Затем он по-

рождает новый процесс, запускает системную функцию nice, позволя-

ющую сделать приоритет запуска процесса-родителя ниже приоритета

его потомка (см. главу 8), и входит в бесконечный цикл. Порожден-

ный процесс задерживает свое выполнение на 5 секунд, чтобы дать

родительскому процессу время исполнить системную функцию nice и

снизить свой приоритет. После этого порожденный процесс входит в

цикл, в каждой итерации которого он посылает родительскому про-

цессу сигнал о прерывании (посредством обращения к функции kill).

Если в результате ошибки, например, из-за того, что родительский

процесс больше не существует, kill завершается, то завершается и

порожденный процесс. Вся идея состоит в том, что родительскому

процессу следует запускать функцию обработки сигнала при каждом

получении сигнала о прерывании. Функция обработки сигнала выводит

сообщение и снова обращается к функции signal при очередном появ-

лении сигнала о прерывании, родительский же процесс продолжает


-------------------------------------------------------------┐

│ #include

│ sigcatcher() │

│ { │

│ printf("PID %d принял сигнал\n",getpid()); /* печать │

│ PID */ │