Операционная система UNIX System V использует простую, но обладающую широкими возможностями организацию процессов, за работой которой может следить пользователь. UNIX работает в соответствии с моделью, изображенной на рис. 3.12,6, согласно которой большинство программ операционной системы выполняется в среде пользовательских процессов. Таким образом, в UNIX требуются оба режима — пользовательский режим и режим ядра. В операционной системе UNIX используются две категории процессов: системные и пользовательские. Системные процессы выполняют код операционной системы в режиме ядра, осуществляя различные административные функции, такие, как выделение памяти или свопинг процессов. Пользовательские процессы выполняют код пользовательских программ, как в пользовательском режиме, а код операционной системы — в режиме ядра. Пользовательский процесс переключается в режим ядра при вызове системной функции, генерации исключения или при обработке прерывания.
Состояния процессов
Всего в операционной системе UNIX распознается девять состояний процессов, перечисленных в табл. 3.9; соответствующая диаграмма переходов состояний показана на рис. 3.14 (в ее основе — рисунок из [ВАСН86]). Этот рисунок похож на рис. 3.6; нужно только принять во внимание, что спящие состояния в системе UNIX соответствуют блокированным состояниям. Кратко перечислим основные различия между диаграммами.
Для отражения того факта, что процесс может выполняться как в пользовательском режиме, так и в режиме ядра, в диаграмме имеется два состояния выполняющихся процессов.
Состояния вытесненных и загруженных в память и готовых к выполнению процессов отличаются друг от друга. По сути эти два состояния почти одинаковы (на что указывает соединяющая их пунктирная линия). Различие же делается, чтобы подчеркнуть, каким именно образом процесс может быть прерван в пользу другого. Если процесс выполняется в режиме ядра (в результате вызова диспетчера, прерывания по таймеру или прерывания по команде ввода-вывода), рано или поздно наступает момент, когда ядро завершает свою работу и готово возвратить управление пользовательской программе. В это время ядро может принять решение передать управление процессу с более высоким приоритетом, чем у выполнявшегося до этого. В таком случае текущий процесс переходит в состояние вытесненного, но с точки зрения диспетчеризации эти процессы одинаковы. Процесс, прерванный в пользу другого, и находящийся в состоянии готовности к выполнению, находятся в одной очереди.
Таблица 3.9. Состояния процессов в UNIX
Такое вытеснение процесса может произойти только в момент переключения режима выполнения из режима ядра в пользовательский режим. При выполнении процесса в режиме ядра он не может быть вытеснен, поэтому операционная система UNIX не приспособлена для работы в режиме реального времени. Обсуждение требований к системам, выполняющим обработку запросов в реальном времени, можно найти в главе, "Многопроцессорное планирование и планирование реального времени".
В UNIX есть два процесса, которых нет ни в каких других операционных системах. Процесс 0 — это специальный процесс, который создается при загрузке системы. В сущности, он предопределен как структура данных, которая загружается вместе с системой. Этот процесс является процессом свопинга. Кроме того, процесс 0 порождает процесс 1, который называется инициализирующим процессом (init process). Этот процесс является родительским по отношению ко всем остальным. Когда в систему входит новый интерактивный пользователь, именно процесс 1 создает новый процесс для этого пользователя. Далее пользовательский процесс может создавать ветвящиеся дочерние процессы. Таким образом, каждое приложение может состоять из ряда взаимосвязанных процессов.
Создание процесса
Рис. 3.14. Диаграмма переходов состояний процессов в системе UNIX
Описание процессов
В операционной системе UNIX процессы представлены довольно сложными структурами данных, которые предоставляют операционной системе всю необходимую для управления и диспетчеризации процессов информацию. В табл. 3.10 приведены элементы образа процесса, разделенные на три части: контекст пользовательского уровня, контекст регистров и контекст системного уровня.
Таблица 3.10. Образ процесса в UNIX
В контекст пользовательского уровня (user-level context) входят основные элементы пользовательских программ; он может генерироваться непосредственно из скомпилированных объектных файлов. Каждая пользовательская программа разделена на две части, одна из которых размещается в текстовой области, а другая — в области данных. Текстовая область предназначена только для чтения; в ней хранятся команды пользовательских программ. Во время выполнения процессор использует пользовательский стек для вызовов и возвратов из процедур, а также для передачи параметров. Совместно используемая область памяти — это область данных, доступ к которой одновременно предоставляется различным процессам. Хотя в системе имеется только одна физическая копия совместно используемой области памяти, при использовании виртуальной памяти эта область находится в адресном пространстве каждого процесса, который ее использует.
Когда процесс не выполняется, соответствующая информация по состоянию процессора хранится в области контекста регистров (register context).
В контексте системного уровня (system-level context) находится остальная информация, которая нужна операционной системе для управления процессом. Эта информация состоит из статической части фиксированного размера, который остается неизменным на протяжении всего времени жизни процесса, и динамической части, размер которой меняется. Одним из компонентов статической части является запись таблицы процессов, которая фактически является частью таблицы процессов, поддерживаемой операционной системой, в которой каждому процессу соответствует одна запись. Запись таблицы процессов содержит информацию по управлению процессом, доступную ядру в любой момент времени. Таким образом, в системе виртуальной памяти все записи таблицы процессов постоянно остаются в основной памяти. В табл. 3.11 перечислены компоненты записи таблицы процессов. Пользовательская область содержит дополнительную управляющую информацию, которая нужна ядру при работе в контексте этого процесса; эта информация используется также при загрузке и выгрузке страниц процесса из основной памяти. В табл. 3.12 приведено содержимое этой таблицы.
Таблица 3.11. Элемент таблицы процессов в системе UNIX
Таблица 3.12. Пользовательская область UNIX
Различия между записью таблицы процессов и пользовательской областью отражают тот факт, что ядро системы UNIX всегда выполняется в контексте какого-нибудь процесса. Большую часть времени ядро работает с контекстом текущего процесса, однако иногда ядру нужен доступ к информации и о других процессах, например при работе планировщика.
Третьей статической частью, входящей в контекст системного уровня, является таблица областей процесса, которая используется системой управления памятью. И, наконец, стек ядра — это динамическая часть контекста системного уровня. Этот стек используется при выполнении процесса в режиме ядра и содержит информацию, которую нужно сохранять и восстанавливать во время вызовов процедур и прерываний.
Управление процессами
В операционной системе UNIX процессы создаются с помощью вызова системной функции ядра под названием fork (). При вызове этой функции процессом операционная система выполняет следующие действия [ВАСН86].
- Выделяет в таблице процессов место для нового процесса.
- Назначает этому процессу уникальный идентификатор.
- Создает копию образа родительского процесса, за исключением совместно используемых областей памяти.
- Увеличивает показания счетчиков всех файлов, принадлежащих родительскому процессу, что отражает тот факт, что новый процесс также владеет этими файлами.
- Назначает процессу состояние готовности к выполнению.
- Возвращает родительскому процессу идентификатор дочернего процесса, а дочернему процессу — значение 0.
Все перечисленные выше действия выполняются в рамках родительского процесса в режиме ядра. После того как ядро закончит выполнение этих функций, оно может перейти к выполнению одного из следующих действий как части программы диспетчера.
- Оставаясь в рамках родительского процесса, переключить процессор в пользовательский режим; процесс будет продолжен с той команды, которая следует после вызова функции fork().
- Передать управление дочернему процессу. Дочерний процесс начинает выполняться с того же места кода, что и родительский: с точки возврата после вызова функции fork ().
- Передать управление другому процессу. При этом и родительский и дочерний процессы переходят в состояние готовности к выполнению.
Возможно, такой метод создания процессов трудно изобразить наглядно, потому что и родительский и дочерний процессы в момент создания выполняют один и тот же проход по коду. Различаются они возвращаемым функцией fork () значением: если оно равно нулю, то это дочерний процесс. Таким образом, можно выполнить команду ветвления, которая приведет к выполнению дочерней программы или продолжению выполнения основной ветви.