). Пользовательский контекст состоит из команд и данных процесса, стека задачи и содержимого совместно используемого пространства памяти в виртуальных адресах процесса. Те части виртуального адресного прос- транства процесса, которые периодически отсутствуют в оперативной памяти вследствие выгрузки или замещения страниц, также включаются в пользователь- ский контекст. Регистровый контекст состоит из следующих компонент: * Счетчика команд, указывающего адрес следующей команды, которую будет вы- полнять центральный процессор; этот адрес является виртуальным адресом внутри пространства ядра или пространства задачи. * Регистра состояния процессора (PS), который указывает аппаратный статус машины по отношению к процессу. Регистр PS, например, обычно содержит подполя, которые указывают, является ли результат последних вычислений нулевым, положительным или отрицательным, переполнен ли регистр с уста- новкой бита переноса и т.д. Операции, влияющие на установку регистра PS, выполняются для отдельного процесса, потому-то в регистре PS и содержит- ся аппаратный статус машины по отношению к процессу. В других имеющих важное значение подполях регистра PS указывается текущий уровень преры- вания процессора, а также текущий и предыдущий режимы выполнения процес- са (режим ядра/задачи). По значению подполя текущего режима выполнения процесса устанавливается, может ли процесс выполнять привилегированные команды и обращаться к адресному пространству ядра. * Указателя вершины стека, в котором содержится адрес следующего элемента стека ядра или стека задачи, в соответствии с режимом выполнения процес- са. В зависимости от архитектуры машины указатель вершины стека показы- вает на следующий свободный элемент стека или на последний используемый элемент. От архитектуры машины также зависит направление увеличения сте- ка (к старшим или младшим адресам), но для нас сейчас эти вопросы несу- щественны. * Регистров общего назначения, в которых содержится информация, сгенериро- ванная процессом во время его выполнения. Чтобы облегчить последующие объяснения, выделим среди них два регистра - регистр 0 и регистр 1 - для дополнительного использования при передаче информации между процессами и ядром. Системный контекст процесса имеет "статическую часть" (первые три элемента в нижеследующем списке) и "динамическую часть" (последние два элемента). На протяжении всего времени выполнения процесс постоянно рас- полагает одной статической частью системного контекста, но может иметь переменное число динамических частей. Динамическую часть системного кон- текста можно представить в виде стека, элементами которого являются контекстные уровни, которые помеща- ются в стек ядром или выталкиваются из стека при наступлении различных событий. Системный контекст включает в себя следующие компоненты: * Запись в таблице процессов, описывающая состояние процесса (раздел 6.1) и содержащая различную управляющую информацию, к которой ядро всегда мо- жет обратиться. * Часть адресного пространства задачи, выделенная процессу, где хранится управляющая информация о процессе, доступная только в контексте процес- са. Общие управляющие параметры, такие как приоритет процесса, хранятся в таблице процессов, поскольку обращение к ним должно производиться за пределами контекста процесса. --------------------------------------- (*) Используемые в данном разделе термины "пользовательский контекст" (user-level context), "регистровый контекст" (register context), "сис- темный контекст" (system-level context) и "контекстные уровни" (context layers) введены автором. 148 * Записи частной таблицы областей процесса, общие таблицы областей и таб- лицы страниц, необходимые для преобразования виртуальных адресов в физи- ческие, в связи с чем в них описываются области команд, данных, стека и другие области, принадлежащие процессу. Если несколько процессов совмес- тно используют общие области, эти области входят составной частью в кон- текст каждого процесса, поскольку каждый процесс работает с этими облас- тями независимо от других процессов. В задачи управления памятью входит идентификация участков виртуального адресного пространства процесса, не являющихся резидентными в памяти. * Стек ядра, в котором хранятся записи процедур ядра, если процесс выпол- няется в режиме ядра. Несмотря на то, что все процессы пользуются одними и теми же программами ядра, каждый из них имеет свою собственную копию стека ядра для хранения индивидуальных обращений к функциям ядра. Пусть, например, один процесс вызывает функцию creat и приостанавливается в ожидании назначения нового индекса, а другой процесс вызывает функцию read и приостанавливается в ожидании завершения передачи данных с диска в память. Оба процесса обращаются к функциям ядра и у каждого из них имеется в наличии отдельный стек, в котором хранится последовательность выполненных обращений. Ядро должно иметь возможность восстанавливать со- держимое стека ядра и положение указателя вершины стека для того, чтобы возобновлять выполнение процесса в режиме ядра. В различных системах стек ядра часто располагается в пространстве процесса, однако этот стек является логически-независимым и, таким образом, может помещаться в са- мостоятельной области памяти. Когда процесс выполняется в режиме задачи, соответствующий ему стек ядра пуст. * Динамическая часть системного контекста процесса, состоящая из несколь- ких уровней и имеющая вид стека, который освобождается от элементов в порядке, обратном порядку их поступления. На каждом уровне системного контекста содержится информация, необходимая для восстановления предыду- щего уровня и включающая в себя регистровый контекст предыдущего уровня. Ядро помещает контекстный уровень в стек при возникновении прерывания, при обращении к системной функции или при переключении контекста процесса. Контекстный уровень выталкивается из стека после завершения обработки преры- вания, при возврате процесса в режим задачи после выполнения системной функ- ции, или при переключении контекста. Таким образом, переключение контекста влечет за собой как помещение контекстного уровня в стек, так и извлечение уровня из стека: ядро помещает в стек контекстный уровень старого процесса, а извлекает из стека контекстный уровень нового процесса. Информация, необ- ходимая для восстановления текущего контекстного уровня, хранится в записи таблицы процессов. На Рисунке 6.8 изображены компоненты контекста процесса. Слева на рисун- ке изображена статическая часть контекста. В нее входят: пользовательский контекст, состоящий из программ процесса (машинных инструкций), данных, стека и разделяемой памяти (если она имеет- ся), а также статическая часть системного контекста, состоящая из записи таблицы процессов, пространства процесса и записей частной таблицы областей (информации, необходимой для трансляции виртуальных адресов пользовательско- го контекста). Справа на рисунке изображена динамическая часть контекста. Она имеет вид стека и включает в себя несколько элементов, хранящих регист- ровый контекст предыдущего уровня и стек ядра для текущего уровня. Нулевой контекстный уровень представляет собой пустой уровень, относящийся к пользо- вательскому контексту; увеличение стека здесь идет в адресном пространстве задачи, стек ядра недействителен. Стрелка, соединяющая между собой статичес- кую часть системного контекста и верхний уровень динамической части контекс- та, означает то, что в таблице процессов хранится информация, позволяющая ядру восстанавливать текущий контекстный уровень процесса. 149 Статическая часть контекста Динамическая часть контекста +-------------------------+ логичес- | - | |Пользовательский контекст| кий ука- | - | | +---------------------+ | затель на| - | | | Программы процесса | | текущий | - | | | Данные | |+-------->+----------------+ | | Стек | || контек- | Стек ядра для | | | Разделяемые данные | || стный | уровня 3 | | +---------------------+ || уровень | | | || | Сохраненный ре-| | Статическая часть ||Уровень 3| гистровый кон- | | системного контекста || | текст уровня 2 | | +---------------------+ || +----------------+ | | Запись таблицы про- | || | Стек ядра для | | | цессов +-++ | уровня 2 | | |Пространство процесса| | | | | | Частная таблица об- | | | Сохраненный ре-| | | ластей процесса | | Уровень 2| гистровый кон- | | +---------------------+ | | текст уровня 1 | +-------------------------+ +----------------+ | Стек ядра для | | уровня 1 | | | | Сохраненный ре-| Уровень 1| гистровый кон- | | текст уровня 0 | +----------------+ Контекстный| | уровень| (Пользователь- | ядра 0| ский уровень) | +----------------+ Рисунок 6.8. Компоненты контекста процесса Процесс выполняется в рамках своего контекста или, если говорить более точно, в рамках своего текущего контекстного уровня. Количество контекстных уровней ограничивается числом поддерживаемых в машине уровней прерывания. Например, если в машине поддерживаются разные уровни прерываний для прог- рамм, терминалов, дисков, всех остальных периферийных устройств и таймера, то есть 5 уровней прерывания, то, следовательно, у процесса может быть не более 7 контекстных уровней: по одному на каждый уровень прерывания, 1 для системных функций и 1 для пользовательского контекста. 7 уровней будет дос- таточно, даже если прерывания будут поступать в "наихудшем" из возможных по- рядков, поскольку прерывание данного уровня блокируется (то есть его обра- ботка откладывается центральным процессором) до тех пор, пока ядро не обра- ботает все прерывания этого и более высоких уровней. Несмотря на то, что ядро всегда исполняет контекст какого-нибудь процес- са, логическая функция, которую ядро реализует в каждый момент, не всегда имеет отношение к данному процессу. Например, если возвращая данные, диско- вое запоминающее устройство посылает прерывание, то прерывается выполнение текущего процесса и ядро обрабатывает прерывание на новом контекстном уровне этого процесса, даже если данные относятся к другому процессу. Программы об- работки прерываний обычно не обращаются к статическим составляющим контекста процесса и не видоизменяют их, так как эти части не связаны с прерываниями. 6.4 СОХРАНЕНИЕ КОНТЕКСТА ПРОЦЕССА Как уже говорилось ранее, ядро сохраняет контекст процесса, помещая в 150 стек новый контекстный уровень. В частности, это имеет место, когда система получает прерывание, когда процесс вызывает системную функцию или когда ядро выполняет переключение контекста. Каждый из этих случаев подробно рассматри- вается в этом разделе. 6.4.1 Прерывания и особые ситуации Система отвечает за обработку всех прерываний, поступили ли они от аппа- ратуры (например, от таймера или от периферийных устройств), от программ (в связи с выполнением инструкций, вызывающих возникновение "программных преры- ваний") или явились результатом особых ситуаций (таких как обращение к от- сутствующей странице). Если центральный процессор ведет обработку на более низком уровне по сравнению с уровнем поступившего прерывания, то перед вы- полнением следующей инструкции его работа прерывается, а уровень прерывания процессора повышается, чтобы другие прерывания с тем же (или более низким) уровнем не могли иметь места до тех пор, пока ядро не обработает текущее прерывание, благодаря чему обеспечивается сохранение целостности структур данных ядра. В процессе обработки прерывания ядро выполняет следующую после- довательность действий: 1. Сохраняет текущий регистровый контекст выполняющегося процесса и создает в стеке (помещает в стек) новый контекстный уровень. 2. Устанавливает "источник" прерывания, идентифицируя тип прерывания (нап- ример, прерывание по таймеру или от диска) и номер устройства, вызвавше- го прерывание (например, если прерывание вызвано дисковым запоминающим устройством). При возникновении прерывания система получает от машины число, которое использует в качестве смещения в таблице векторов преры- вания. Содержимое векторов прерывания в разных машинах различно, но, как правило, в них хранится адрес программы обработки прерывания, соответст- вующей источнику прерывания, и указывается путь поиска параметра для программы. В качестве примера рассмотрим таблицу векторов прерывания, приведенную на Рисунке 6.9. Если источником прерывания явился терминал, ядро получает от аппаратуры номер прерывания, равный 2, и вызывает прог- +-----------------------------------------+ | Номер прерывания Программа обработки | | прерывания | | | | 0 clockintr | | 1 diskintr | | 2 ttyintr | | 3 devintr | | 4 softintr | | 5 otherintr | +-----------------------------------------+ Рисунок 6.9. Пример векторов прерывания рамму обработки прерываний от терминала, именуемую ttyintr. 3. Вызов программы обработки прерывания. Стек ядра для нового контекстного уровня, если рассуждать логически, должен отличаться от стека ядра пре- дыдущего контекстного уровня. В некоторых разработках стек ядра текущего процесса используется для хранения элементов, соответствующих программам обработки прерываний, в других разработках эти элементы хранятся в гло- бальном стеке прерываний, благодаря чему обеспечивается возврат из прог- раммы без переключения контекста. 4. Программа завершает свою работу и возвращает управление ядру. Ядро ис- полняет набор машинных команд по восстановлению регистрового контекста и 151 стека ядра предыдущего контекстного уровня в том виде, который они имели в момент прерывания, после чего возобновляет выполнение восстановленного контекстного уровня. Программа обработки прерываний может повлиять на поведение процесса, поскольку она может внести изменения в глобальные структуры данных ядра и возобновить выполнение приостановленных процес- сов. Однако, обычно процесс продолжает выполняться так, как если бы пре- рывание никогда не происходило. +-----------------------------------------------------+ | алгоритм inthand /* обработка прерываний */ | | входная информация: отсутствует | | выходная информация: отсутствует | | { | | сохранить (поместить в стек) текущий контекстный | | уровень; | | установить источник прерывания; | | найти вектор прерывания; | | вызвать программу обработки прерывания; | | восстановить (извлечь из стека) предыдущий кон- | | текстный уровень; | | } | +-----------------------------------------------------+ Рисунок 6.10. Алгоритм обработки прерываний На Рисунке 6.10 кратко изложено, каким образом ядро обрабатывает преры- вания. С помощью использования в отдельных случаях последовательности машин- ных операций или микрокоманд на некоторых машинах достигается больший эффект по сравнению с тем, когда все операции выполняются программным обеспечением, однако имеются узкие места, связанные с числом сохраняемых контекстных уров- ней и скоростью выполнения машинных команд, реализующих сохранение контекс- та. По этой причине определенные операции, выполнения которых требует реали- зация системы UNIX, являются машинно-зависимыми. На Рисунке 6.11 показан пример, в котором процесс запрашивает выполнение системной функции (см. следующий раздел) и получает прерывание от диска при ее выполнении. Запустив программу обработки прерывания от диска, система по- лучает прерывание по таймеру и вызывает уже программу обработки прерывания по таймеру. Каждый раз, когда система получает прерывание (или вызывает сис- темную функцию), она создает в стеке новый контекстный уровень и сохраняет регистровый контекст предыдущего уровня. 6.4.2 Взаимодействие с операционной системой через вызовы системных функций Такого рода взаимодействие с ядром было предметом рассмотрения в преды- дущих главах, где шла речь об обычном вызове функций. Очевидно, что обычная последовательность команд обращения к функции не в состоянии переключить вы- полнения процесса с режима задачи на режим ядра. Компилятор с языка Си ис- пользует библиотеку функций, имена которых совпадают с именами системных функций, иначе ссылки на системные функции в пользовательских программах бы- ли бы ссылками на неопределенные имена. В библиотечных функциях обычно ис- полняется команда, переводящая выполнение процесса в режим ядра и побуждаю- щая ядро к запуску исполняемого кода системной функции. В дальнейшем эта ко- манда именуется "внутренним прерыванием операционной системы". Библиотечные процедуры исполняются в режиме задачи, а взаимодействие с операционной сис- темой через вызов системной функции можно определить в нескольких словах как 152 Последовательность прерываний +-------------------------------+ | Контекстный уровень ядра 3 | | Исполнить программу обра- | | ботки прерывания по таймеру | | | | Сохранить регистровый кон- | | текст программы обработки | | прерывания от диска | Прерывание по таймеру --------+-------------------------------+ ^ | Контекстный уровень ядра 2 | | | Исполнить программу обра- | | | ботки прерывания от диска | | | | | | Сохранить регистровый кон- | | | текст обращения к системной | | | функции | Прерывание от диска ----------+-------------------------------+ ^ | Контекстный уровень ядра 1 | | | Исполнить обращение к сис- | | | темной функции | | | | | | Сохранить регистровый кон- | | | текст пользовательского | | | уровня | Вызов системной функции ------+-------------------------------+ ^ | | Исполнение в режиме задачи Рисунок 6.11. Примеры прерываний особый случай программы обработки прерывания. Библиотечные функции передают ядру уникальный номер системной функции одним из машинно-зависимых способов - либо как параметр внутреннего прерывания операционной системы, либо через отдельный регистр, либо через стек - а ядро таким образом определяет тип вы- зываемой функции. Обрабатывая внутреннее прерывание операционной системы, ядро по номеру системной функции ведет в таблице поиск адреса соответствующей процедуры яд- ра, то есть точки входа системной функции, и количества передаваемых функции параметров (Рисунок 6.12). Ядро вычисляет адрес (пользовательский) первого параметра функции, прибавляя (или вычитая, в зависимости от направления уве- личения стека) смещение к указателю вершины стека задачи (аналогично для всех параметров функции). Наконец, ядро копирует параметры задачи в прост- ранство процесса и вызывает соответствующую процедуру, которая выполняет системную функцию. После исполнения процедуры ядро выясняет, не было ли ошибки. Если ошибка была, ядро делает соответствующие установки в сохранен- ном регистровом контексте задачи, при этом в регистре PS обычно устанавлива- ется бит переноса, а в нулевой регистр заносится номер ошибки. Если при вы- полнении системной функции не было ошибок, ядро очищает в регистре PS бит переноса и заносит возвращаемые функцией значения в регистры 0 и 1 в сохра- ненном регистровом контексте задачи. Когда ядро возвращается после обработки внутреннего прерывания операционной системы в режим задачи, оно попадает в следующую библиотечную инструкцию после прерывания. Библиотечная функция ин- терпретирует возвращенные ядром значения и передает их программе пользовате- ля. 153 +------------------------------------------------------------+ | алгоритм syscall /* алгоритм запуска системной функции */| | входная информация: номер системной функции | | выходная информация: результат системной функции | | { | | найти запись в таблице системных функций, соответствую-| | щую указанному номеру функции; | | определить количество параметров, передаваемых функции;| | скопировать параметры из адресного пространства задачи | | в пространство процесса; | | сохранить текущий контекст для аварийного завершения | | (см. раздел 6.44); | | запустить в ядре исполняемый код системной функции; | | если (во время выполнения функции произошла ошибка) | | { | | установить номер ошибки в нулевом регистре сохра- | | ненного регистрового контекста задачи; | | включить бит переноса в регистре PS сохраненного | | регистрового контекста задачи; | | } | | в противном случае | | занести возвращаемые функцией значения в регистры 0 | | и 1 в сохраненном регистровом контексте задачи; | | } | +------------------------------------------------------------+ Рисунок 6.12. Алгоритм обращения к системным функциям В качестве примера рассмотрим программу, которая создает файл с разреше- нием чтения и записи в него для всех пользователей (режим доступа 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 ядро заносит код за- вершения. Возвращая уп- --------------------------------------- (**) Очередность, в которой компилятор вычисляет и помещает в стек параметры функции, зависит от реализации системы. 154 +----------------------------------------+ | char name[] = "file"; | | main() | | { | | int fd; | | fd = creat(name,0666); | | } | +----------------------------------------+ +---------------------------------------------------------------+ | Фрагменты ассемблерной программы, сгенерированной в | | системе 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 # возврат из подпрограммы | +---------------------------------------------------------------+ Рисунок 6.13. Системная функция creat и сгенерированная прог- рамма ее выполнения в системе Motorola 68000 равление из программы обработки обращений к операционной системе в режим за- дачи, библиотечная функция проверяет состояние бита переноса в регистре PS (по адресу 7): если бит установлен, управление передается по адресу 13c, из нулевого регистра выбирается код ошибки и помещается в глобальную переменную errno по адресу 20, в регистр 0 заносится -1, и управление возвращается на следующую после адреса 64 (где производится вызов функции) команду. Код за- вершения функции имеет значение -1, что указывает на ошибку в выполнении 155 системной функции. Если же бит переноса в регистре PS при переходе из режима ядра в режим задачи имеет нулевое значение, процесс с адреса 7 переходит по адресу 86 и возвращает управление вызвавшей программе (адрес 64); регистр 0 содержит возвращаемое функцией значение. +---------+ | - | | - | | - | | - | | - | | - | |стек ядра для кон-| | - | |текстного уровня 1| +---------+ | | | 1b6 | код режима доступа |последовательность| | | (666 в восьмиричной системе) |команд обращения к| | 204 | адрес переменной "имя файла" | функции creat | | 6a | адрес точки возврата после +------------------+ | | вызова библиотечной функции |сохраненный регис-| +---------+<-----+ | тровый контекст | | внутрен-| | | для уровня 0 | | нее пре-| | |(пользовательско- | | рывание | значение указателя | го) | | в | вершины стека в мо- | | | 7c | мент внутреннего пре- | счетчик команд, | +---------+ рывания операционной | установленный на | направление системы | 7e | увеличения стека | | | |указатель вершины | | | стека | v | | | регистр PS | | | |регистр 0 (введено| | значение 8) | | | | другие регистры | |общего назначения | +------------------+ Рисунок 6.14. Конфигурация стека для системной функции creat Несколько библиотечных функций могут отображаться на одну точку входа в список системных функций. Каждая точка входа определяет точные синтаксис и семантику обращения к системной функции, однако более удобный интерфейс обеспечивается с помощью библиотек. Существует, например, несколько конст- рукций системной функции exec, таких как execl и execle, выполняющих одни и те же действия с небольшими отличиями. Библиотечные функции, соответствующие этим конструкциям, при обработке параметров реализуют заявленные свойства, но в конечном итоге, отображаются на одну и ту же функцию ядра. 6.4.3 Переключение контекста Если обратиться к диаграмме состояний процесса (Рисунок 6.1), можно уви- деть, что ядро разрешает производить переключение контекста в четырех случа- ях: когда процесс приостанавливает свое выполнение, когда он завершается, когда он возвращается после вызова системной функции в режим задачи, но не является наиболее подходящим для запуска, или когда он возвращается в режим задачи после завершения ядром обработки прерывания, но так же не является наиболее подходящим для запуска. Как уже было показано в главе 2, ядро под- 156 держивает целостность и согласованность своих внутренних структур данных, запрещая произвольно переключать контекст. Прежде чем переключать контекст, ядро должно удостовериться в согласованности своих структур данных: то есть в том, что сделаны все необходимые корректировки, все очереди выстроены над- лежащим образом, установлены соответствующие блокировки, позволяющие избе- жать вмешательства со стороны других процессов, что нет излишних блокировок и т.д. Например, если ядро выделяет буфер, считывает блок из файла и приос- танавливает выполнение до завершения передачи данных с диска, оно оставляет буфер заблокированным, чтобы другие процессы не смогли обратиться к буферу. Но если процесс исполняет системную функцию link, ядро снимает блокировку с первого индекса перед тем, как снять ее со второго индекса, и тем самым пре- дотвращает возникновение тупиковых ситуаций (взаимной блокировки). Ядро выполняет переключение контекста по завершении системной функции exit, поскольку в этом случае больше ничего не остается делать. Кроме того, переключение контекста допускается, когда процесс приостанавливает свою ра- боту, поскольку до момента возобновления может пройти немало времени, в те- чение которого могли бы выполняться другие процессы. Переключение контекста допускается и тогда, когда процесс не имеет преимуществ перед другими про- цессами при исполнении, с тем, чтобы обеспечить более справедливое планиро- вание процессов: если по выходе процесса из системной функции или из преры- вания обнаруживается, что существует еще один процесс, который имеет более высокий приоритет и ждет выполнения, то было бы несправедливо оставлять его в ожидании. Процедура переключения контекста похожа на процедуры обработки прерыва- ний и обращения к системным функциям, если не считать того, что ядро вместо предыдущего контекстного уровня текущего процесса восстанавливает контекст- ный уровень другого процесса. Причины, вызвавшие переключение контекста, при этом не имеют значения. На механизм переключения контекста не влияет и метод выбора следующего процесса для исполнения. +--------------------------------------------------------+ | 1. Принять решение относительно необходимости переклю- | | чения контекста и его допустимости в данный момент. | | 2. Сохранить контекст "прежнего" процесса. | | 3. Выбрать процесс, наиболее подходящий для исполнения,| | используя алгоритм диспетчеризации процессов, приве-| | денный в главе 8. | | 4. Восстановить его контекст. | +--------------------------------------------------------+ Рисунок 6.15. Последовательность шагов, выполняемых при пе- реключении контекста Текст программы, реализующей переключение контекста в системе UNIX, из всех программ операционной системы самый трудный для понимания, ибо при рас- смотрении обращений к функциям создается впечатление, что они в одних случа- ях не возвращают управление, а в других - возникают непонятно откуда. Причи- ной этого является то, что ядро во многих системных реализациях сохраняет контекст процесса в одном месте программы, но продолжает работу, выполняя переключение контекста и алгоритмы диспетчеризации в контексте "прежнего" процесса. Когда позднее ядро восстанавливает контекст процесса, оно возоб- новляет его выполнение в соответствии с ранее сохраненным контекстом. Чтобы различать между собой те случаи, когда ядро восстанавливает контекст нового процесса, и когда оно продолжает исполнять ранее сохраненный контекст, можно варьировать значения, возвращаемые критическими функциями, или устанавливать искусственным образом текущее значение счетчика команд. На Рисунке 6.16 приведена схема переключения контекста. Функция save_context сохраняет информацию о контексте исполняемого процесса и возв- 157 ращает значение 1. Кроме всего прочего, ядро сохраняет текущее значение счетчика команд (в функции save_context) и значение 0 в нулевом регистре при выходе из функции. Ядро продолжает исполнять контекст "прежнего" процесса (A), выбирая для выполнения следующий процесс (B) и вызывая функцию resume_context +------------------------------------------------------------+ | if (save_context()) /* сохранение контекста выполняющегося| | процесса */ | | { | | /* выбор следующего процесса для выполнения */ | | - | | - | | - | | resume_context(new_process); | | /* сюда программа не попадает ! */ | | } | | /* возобновление выполнение процесса начинается отсюда */ | +------------------------------------------------------------+ Рисунок 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 сохраняет контекст в пространстве процесса и после выхода из него вы- полнение продолжается в прежнем контекстном уровне. Когда ядру понадобится восстановить контекст, --------------------------------------- (***) Эти алгоритмы не следует путать с имеющими те же названия библиотечны- ми функциями, которые могут вызываться непосредственно из пользова- тельских про