The design of the unix operating system by Maurice J

Вид материалаРеферат
Подобный материал:
1   ...   17   18   19   20   21   22   23   24   ...   55

│ │ │

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

│ │ текст пользовательского │

│ │ уровня │

Вызов системной функции L--------------------------------















Исполнение в режиме задачи


Рисунок 6.11. Примеры прерываний


На Рисунке 6.11 показан пример, в котором процесс запрашивает

выполнение системной функции (см. следующий раздел) и получает

прерывание от диска при ее выполнении. Запустив программу обра-

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

и вызывает уже программу обработки прерывания по таймеру. Каждый

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

функцию), она создает в стеке новый контекстный уровень и сохра-

няет регистровый контекст предыдущего уровня.


6.4.2 Взаимодействие с операционной системой через вызовы

системных функций


Такого рода взаимодействие с ядром было предметом рассмотре-

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

Очевидно, что обычная последовательность команд обращения к функ-

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

чи на режим ядра. Компилятор с языка Си использует библиотеку

функций, имена которых совпадают с именами системных функций,

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

были бы ссылками на неопределенные имена. В библиотечных функциях

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

жим ядра и побуждающая ядро к запуску исполняемого кода системной

функции. В дальнейшем эта команда именуется "внутренним прерыва-

нием операционной системы". Библиотечные процедуры исполняются в

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

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

особый случай программы обработки прерывания. Библиотечные функ-

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

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

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

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

ции.


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

│ алгоритм syscall /* алгоритм запуска системной функции */│

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

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

│ { │

│ найти запись в таблице системных функций, соответствую-│

│ щую указанному номеру функции; │

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

│ скопировать параметры из адресного пространства задачи │

│ в пространство процесса; │

│ сохранить текущий контекст для аварийного завершения │

│ (см. раздел 6.44); │

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

│ если (во время выполнения функции произошла ошибка) │

│ { │

│ установить номер ошибки в нулевом регистре сохра- │

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

│ включить бит переноса в регистре PS сохраненного │

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

│ } │

│ в противном случае │

│ занести возвращаемые функцией значения в регистры 0 │

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

│ } │

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


Рисунок 6.12. Алгоритм обращения к системным функциям


Обрабатывая внутреннее прерывание операционной системы, ядро

по номеру системной функции ведет в таблице поиск адреса соот-

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

ции, и количества передаваемых функции параметров (Рисунок 6.12).

Ядро вычисляет адрес (пользовательский) первого параметра функ-

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

чения стека) смещение к указателю вершины стека задачи (аналогич-

но для всех параметров функции). Наконец, ядро копирует параметры

задачи в пространство процесса и вызывает соответствующую проце-

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

цедуры ядро выясняет, не было ли ошибки. Если ошибка была, ядро

делает соответствующие установки в сохраненном регистровом кон-

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

переноса, а в нулевой регистр заносится номер ошибки. Если при

выполнении системной функции не было ошибок, ядро очищает в ре-

гистре PS бит переноса и заносит возвращаемые функцией значения в

регистры 0 и 1 в сохраненном регистровом контексте задачи. Когда

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

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

ную инструкцию после прерывания. Библиотечная функция интерпрети-

рует возвращенные ядром значения и передает их программе пользо-

вателя.

В качестве примера рассмотрим программу, которая создает файл

с разрешением чтения и записи в него для всех пользователей (ре-

жим доступа 0666) и которая приведена в верхней части Рисунка

6.13. Далее на рисунке изображен отредактированный фрагмент сге-

нерированного кода программы после компиляции и дисассемблирова-

ния (создания по объектному коду эквивалентной программы на языке

ассемблера) в системе Motorola 68000. На Рисунке 6.14 изображена

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

нерирует программу помещения в стек задачи двух параметров, один

из которых содержит установку прав доступа (0666), а другой - пе-

ременную "имя файла" (**). Затем из адреса 64 процесс вызывает

библиотечную функцию creat (адрес 7a), аналогичную соответствую-

щей системной функции. Адрес точки возврата из функции - 6a, этот

адрес помещается процессом в стек. Библиотечная функция creat за-

сылает в регистр 0 константу 8 и исполняет команду прерывания

(trap), которая переключает процесс из режима задачи в режим ядра

и заставляет его обратиться к системной функции. Заметив, что

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

номер функции (8) и определяет таким образом, что вызвана функция

creat. Просматривая внутреннюю таблицу, ядро обнаруживает, что

системной функции creat необходимы два параметра; восстанавливая

регистровый контекст предыдущего уровня, ядро копирует параметры

из пользовательского пространства в пространство процесса. Проце-

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

определенных местах адресного пространства процесса. По заверше-

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

рамме обработки обращений к операционной системе, которая прове-

ряет, установлено ли поле ошибки в пространстве процесса (то есть

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

программа устанавливает в регистре PS бит переноса, заносит в ре-

гистр 0 код ошибки и возвращает управление ядру. Если ошибок не

было, в регистры 0 и 1 ядро заносит код завершения. Возвращая уп-


---------------------------------------

(**) Очередность, в которой компилятор вычисляет и помещает в

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


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

│ char name[] = "file"; │

│ main() │

│ { │

│ int fd; │

│ fd = creat(name,0666); │

│ } │

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


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

│ Фрагменты ассемблерной программы, сгенерированной в │

│ системе Motorola 68000 │

│ │

│ Адрес Команда │

│ │

│ │

│ # текст главной программы │

│ │

│ 58: mov &Ox1b6,(%sp) # поместить код 0666 в стек │

│ 5e: mov &Ox204,-(%sp) # поместить указатель вершины │

│ # стека и переменную "имя файла"│

│ # в стек │

│ 64: jsr Ox7a # вызов библиотечной функции │

│ # создания файла │

│ │

│ │

│ # текст библиотечной функции создания файла │

│ 7a: movq &Ox8,%d0 # занести значение 8 в регистр 0│

│ 7c: trap &Ox0 # внутреннее прерывание операци-│

│ # онной системы │

│ 7e: bcc &Ox6 <86> # если бит переноса очищен, │

│ # перейти по адресу 86 │

│ 80: jmp Ox13c # перейти по адресу 13c │

│ 86: rts # возврат из подпрограммы │

│ │

│ │

│ # текст обработки ошибок функции │

│ 13c: mov %d0,&Ox20e # поместить содержимое регистра │

│ # 0 в ячейку 20e (переменная │

│ # errno) │

│ 142: movq &-Ox1,%d0 # занести в регистр 0 константу │

│ # -1 │

│ 144: mova %d0,%a0 │

│ 146: rts # возврат из подпрограммы │

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

Рисунок 6.13. Системная функция creat и сгенерированная прог-

рамма ее выполнения в системе Motorola 68000


равление из программы обработки обращений к операционной системе

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

реноса в регистре PS (по адресу 7): если бит установлен, управле-

ние передается по адресу 13c, из нулевого регистра выбирается код

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

регистр 0 заносится -1, и управление возвращается на следующую

после адреса 64 (где производится вызов функции) команду. Код за-

вершения функции имеет значение -1, что указывает на ошибку в вы-

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

при переходе из режима ядра в режим задачи имеет нулевое значе-

ние, процесс с адреса 7 переходит по адресу 86 и возвращает уп-

равление вызвавшей программе (адрес 64); регистр 0 содержит возв-

ращаемое функцией значение.


----------┐ │ │

│ │ │ │

│ │ │ │

│ │ │стек ядра для кон-│

│ │ │текстного уровня 1│

+---------+ │ │

│ 1b6 │ код режима доступа │последовательность│

│ │ (666 в восьмиричной системе) │команд обращения к│

│ 204 │ адрес переменной "имя файла" │ функции creat │

│ 6a │ адрес точки возврата после +------------------+

│ │ вызова библиотечной функции │сохраненный регис-│

+---------+<-----┐ │ тровый контекст │

│ внутрен-│ │ │ для уровня 0 │

│ нее пре-│ │ │(пользовательско- │

│ рывание │ значение указателя │ го) │

│ в │ вершины стека в мо- │ │

│ 7c │ мент внутреннего пре- │ счетчик команд, │

L---------- рывания операционной │ установленный на │

направление системы │ 7e │

увеличения стека │ │

│ │указатель вершины │

│ │ стека │

v │ │

│ регистр PS │

│ │

│регистр 0 (введено│

│ значение 8) │

│ │

│ другие регистры │

│общего назначения │

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


Рисунок 6.14. Конфигурация стека для системной функции creat


Несколько библиотечных функций могут отображаться на одну

точку входа в список системных функций. Каждая точка входа опре-

деляет точные синтаксис и семантику обращения к системной функ-

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

лиотек. Существует, например, несколько конструкций системной

функции exec, таких как execl и execle, выполняющих одни и те же

действия с небольшими отличиями. Библиотечные функции, соответс-

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

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

же функцию ядра.


6.4.3 Переключение контекста


Если обратиться к диаграмме состояний процесса (Рисунок 6.1),

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

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

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

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

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

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

ся наиболее подходящим для запуска. Как уже было показано в главе

2, ядро поддерживает целостность и согласованность своих внутрен-

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

Прежде чем переключать контекст, ядро должно удостовериться в

согласованности своих структур данных: то есть в том, что сделаны

все необходимые корректировки, все очереди выстроены надлежащим

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

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

них блокировок и т.д. Например, если ядро выделяет буфер, считы-

вает блок из файла и приостанавливает выполнение до завершения

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

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

цесс исполняет системную функцию link, ядро снимает блокировку с

первого индекса перед тем, как снять ее со второго индекса, и тем

самым предотвращает возникновение тупиковых ситуаций (взаимной

блокировки).

Ядро выполняет переключение контекста по завершении системной

функции exit, поскольку в этом случае больше ничего не остается

делать. Кроме того, переключение контекста допускается, когда

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

новления может пройти немало времени, в течение которого могли бы

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

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

ми при исполнении, с тем, чтобы обеспечить более справедливое

планирование процессов: если по выходе процесса из системной

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

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

то было бы несправедливо оставлять его в ожидании.

Процедура переключения контекста похожа на процедуры обработ-

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

того, что ядро вместо предыдущего контекстного уровня текущего

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

Причины, вызвавшие переключение контекста, при этом не имеют зна-

чения. На механизм переключения контекста не влияет и метод выбо-

ра следующего процесса для исполнения.


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

│ 1. Принять решение относительно необходимости переклю- │

│ чения контекста и его допустимости в данный момент. │

│ 2. Сохранить контекст "прежнего" процесса. │

│ 3. Выбрать процесс, наиболее подходящий для исполнения,│

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

│ денный в главе 8. │

│ 4. Восстановить его контекст. │

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


Рисунок 6.15. Последовательность шагов, выполняемых при пе-

реключении контекста


Текст программы, реализующей переключение контекста в системе

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

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

чатление, что они в одних случаях не возвращают управление, а в

других - возникают непонятно откуда. Причиной этого является то,

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

цесса в одном месте программы, но продолжает работу, выполняя пе-

реключение контекста и алгоритмы диспетчеризации в контексте

"прежнего" процесса. Когда позднее ядро восстанавливает контекст

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

сохраненным контекстом. Чтобы различать между собой те случаи,

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

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

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

ливать искусственным образом текущее значение счетчика команд.

На Рисунке 6.16 приведена схема переключения контекста. Функ-

ция save_context сохраняет информацию о контексте исполняемого

процесса и возвращает значение 1. Кроме всего прочего, ядро сох-

раняет текущее значение счетчика команд (в функции save_context)

и значение 0 в нулевом регистре при выходе из функции. Ядро про-

должает исполнять контекст "прежнего" процесса (A), выбирая для

выполнения следующий процесс (B) и вызывая функцию resume_context


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

│ if (save_context()) /* сохранение контекста выполняющегося│

│ процесса */ │

│ { │

│ /* выбор следующего процесса для выполнения */ │

│ │

│ │

│ │

│ resume_context(new_process); │

│ /* сюда программа не попадает ! */ │

│ } │

│ /* возобновление выполнение процесса начинается отсюда */ │

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


Рисунок 6.16. Псевдопрограмма переключения контекста


для восстановления его контекста. После восстановления контекста

система выполняет процесс B; прежний процесс (A) больше не испол-

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

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

рет процесс A (если только, разумеется, он не был завершен). В

результате восстановления контекста A ядро присвоит счетчику ко-

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

функции save_context, и возвратит в регистре 0 значение 0. Ядро

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

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

ралось еще до функции resume_context. В конечном итоге, процесс A

возвращается из функции save_context со значением 0 (в нулевом

регистре) и возобновляет выполнение после строки комментария "во-

зобновление выполнение процесса начинается отсюда".


6.4.4 Сохранение контекста на случай аварийного завершения


Существуют ситуации, когда ядро вынуждено аварийно прерывать

текущий порядок выполнения и немедленно переходить к исполнению

ранее сохраненного контекста. В последующих разделах, где пойдет

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

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

свой контекст; в данном же разделе рассматривается механизм ис-

полнения предыдущего контекста. Алгоритм сохранения контекста на-

зывается setjmp, а алгоритм восстановления контекста - longjmp

(***). Механизм работы алгоритма setjmp похож на механизм функции

save_context, рассмотренный в предыдущем разделе, если не считать

того, что функция save_context помещает новый контекстный уровень

в стек, в то время как setjmp сохраняет контекст в пространстве

процесса и после выхода из него выполнение продолжается в прежнем

контекстном уровне. Когда ядру понадобится восстановить контекст,


---------------------------------------

(***) Эти алгоритмы не следует путать с имеющими те же названия

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

редственно из пользовательских программ (см. [SVID 85]).

Однако действие этих функций похоже.


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

алгоритм longjmp, который восстанавливает контекст из пространс-

тва процесса и имеет, как и setjmp, код завершения, равный 1.


6.4.5 Копирование данных между адресным пространством сис-

темы и адресным пространством задачи


До сих пор речь шла о том, что процесс выполняется в режиме

ядра или в режиме задачи без каких-либо перекрытий (пересечений)