The design of the unix operating system by Maurice J
Вид материала | Реферат |
7.9 Загрузка системы и начальный процесс G7.11 упражненияh |
- Лекция 10. Файловые системы Unix, 116.79kb.
- Уровни рассмотрения, 314.07kb.
- Курс по операционным системам (на примере ос windows) Основан на учебном курсе Windows, 29.21kb.
- Выполнил ученик 11 «А» класса, 443.51kb.
- Ос лекция 1 (2-й семестр – временно), 101.4kb.
- Operating System, 7686.97kb.
- Unix-подобные операционные системы, характеристики, особенности, разновидности, 40.63kb.
- 1. ms sql server. Общие сведения, 66.03kb.
- Shanti ananda maurice, 89.84kb.
- Методические материалы, 3002.45kb.
│ close(fildes[0]); │
│ /* стандартный вывод направляется в ка- │
│ нал */ │
│ /* команду исполняет порожденный про- │
│ цесс */ │
│ execlp(command1,command1,0); │
│ } │
│ /* вторая компонента командной строки */ │
│ close(stdin); │
│ dup(fildes[0]); │
│ close(fildes[0]); │
│ close(fildes[1]); │
│ /* стандартный ввод будет производиться из│
│ канала */ │
│ } │
│ execve(command2,command2,0); │
│ } │
│ /* с этого места продолжается выполнение родительского │
│ * процесса... │
│ * процесс-родитель ждет завершения выполнения потомка,│
│ * если это вытекает из введенной строки │
│ * / │
│ if (amper == 0) │
│ retid = wait(&status); │
│ } │
L-------------------------------------------------------------
Рисунок 7.28. Основной цикл программы shell (продолжение)
Командные строки простейшего вида содержат имя программы и
несколько параметров, например:
who
grep -n include *.c
ls -l
Shell "ветвится" (fork) и порождает новый процесс, который и за-
пускает программу, указанную пользователем в командной строке.
Родительский процесс (shell) дожидается завершения потомка и пов-
торяет цикл считывания следующей команды.
Если процесс запускается асинхронно (на фоне основной прог-
раммы), как в следующем примере
nroff -mm bigdocument &
shell анализирует наличие символа амперсанд (&) и заносит резуль-
тат проверки во внутреннюю переменную amper. В конце основного
цикла shell обращается к этой переменной и, если обнаруживает в
ней признак наличия символа, не выполняет функцию wait, а тут же
повторяет цикл считывания следующей команды.
Из рисунка видно, что процесс-потомок по завершении функции
fork получает доступ к командной строке, принятой shell'ом. Для
того, чтобы переадресовать стандартный вывод в файл, как в следу-
ющем примере
nroff -mm bigdocument > output
процесс-потомок создает файл вывода с указанным в командной стро-
ке именем; если файл не удается создать (например, не разрешен
доступ к каталогу), процесс-потомок тут же завершается. В против-
ном случае процесс-потомок закрывает старый файл стандартного вы-
вода и переназначает с помощью функции dup дескриптор этого файла
новому файлу. Старый дескриптор созданного файла закрывается и
сохраняется для запускаемой программы. Подобным же образом shell
переназначает и стандартный ввод и стандартный вывод ошибок.
------------┐
│ │
│ Shell │
│ │
L-----T------ wait
│
│ │
------+-----┐ exit
│ │
│ wc │
│ │
L-----T------ read
│
│ │
------+-----┐ write
│ │
│ ls - l │
│ │
L------------
Рисунок 7.29. Взаимосвязь между процессами, исполняющими ко-
мандную строку ls -l│wc
Из приведенного текста программы видно, как shell обрабатыва-
ет командную строку, используя один канал. Допустим, что команд-
ная строка имеет вид:
ls -l│wc
После создания родительским процессом нового процесса процесс-по-
томок создает канал. Затем процесс-потомок создает свое
ответвление; он и его потомок обрабатывают по одной компоненте
командной строки. "Внучатый" процесс исполняет первую компоненту
строки (ls): он собирается вести запись в канал, поэтому он зак-
рывает старый файл стандартного вывода, передает его дескриптор
каналу и закрывает старый дескриптор записи в канал, в котором (в
дескрипторе) уже нет необходимости. Родитель (wc) "внучатого"
процесса (ls) является потомком основного процесса, реализующего
программу shell'а (см. Рисунок 7.29). Этот процесс (wc) закрывает
свой файл стандартного ввода и передает его дескриптор каналу, в
результате чего канал становится файлом стандартного ввода. Затем
закрывается старый и уже не нужный дескриптор чтения из канала и
исполняется вторая компонента командной строки. Оба порожденных
процесса выполняются асинхронно, причем выход одного процесса
поступает на вход другого. Тем временем основной процесс дожида-
ется завершения своего потомка (wc), после чего продолжает свою
обычную работу: по завершении процесса, выполняющего команду wc,
вся командная строка является обработанной. Shell возвращается в
цикл и считывает следующую командную строку.
7.9 ЗАГРУЗКА СИСТЕМЫ И НАЧАЛЬНЫЙ ПРОЦЕСС
Для того, чтобы перевести систему из неактивное состояние в
активное, администратор выполняет процедуру "начальной загрузки".
На разных машинах эта процедура имеет свои особенности, однако во
всех случаях она реализует одну и ту же цель: загрузить копию
операционной системы в основную память машины и запустить ее на
исполнение. Обычно процедура начальной загрузки включает в себя
несколько этапов. Переключением клавиш на пульте машины админист-
ратор может указать адрес специальной программы аппаратной заг-
рузки, а может, нажав только одну клавишу, дать команду машине
запустить процедуру загрузки, исполненную в виде микропрограммы.
Эта программа может состоять из нескольких команд, подготавливаю-
щих запуск другой программы. В системе UNIX процедура начальной
загрузки заканчивается считыванием с диска в память блока началь-
ной загрузки (нулевого блока). Программа, содержащаяся в этом
блоке, загружает из файловой системы ядро ОС (например, из файла
с именем "/unix" или с другим именем, указанным администратором).
После загрузки ядра системы в память управление передается по
стартовому адресу ядра и ядро запускается на выполнение (алгоритм
start, Рисунок 7.30).
Ядро инициализирует свои внутренние структуры данных. Среди
прочих структур ядро создает связные списки свободных буферов и
индексов, хеш-очереди для буферов и индексов, инициализирует
структуры областей, точки входа в таблицы страниц и т.д. По окон-
чании этой фазы ядро монтирует корневую файловую систему и форми-
рует среду выполнения нулевого процесса, среди всего прочего соз-
давая пространство процесса, инициализируя нулевую точку входа в
таблице процесса и делая корневой каталог текущим для процесса.
Когда формирование среды выполнения процесса заканчивается,
система исполняется уже в виде нулевого процесса. Нулевой процесс
"ветвится", запуская алгоритм fork прямо из ядра, поскольку сам
процесс исполняется в режиме ядра. Порожденный нулевым новый про-
цесс, процесс 1, запускается в том же режиме и создает свой поль-
зовательский контекст, формируя область данных и присоединяя ее к
своему адресному пространству. Он увеличивает размер области до
надлежащей величины и переписывает программу загрузки из адресно-
го пространства ядра в новую область: эта программа теперь будет
определять контекст процесса 1. Затем процесс 1 сохраняет регист-
ровый контекст задачи, "возвращается" из режима ядра в режим за-
дачи и исполняет только что переписанную программу. В отличие от
нулевого процесса, который является процессом системного уровня,
выполняющимся в режиме ядра, процесс 1 относится к пользователь-
скому уровню. Код, исполняемый процессом 1, включает в себя вызов
системной функции exec, запускающей на выполнение программу из
файла "/etc/init". Обычно процесс 1 именуется процессом init,
поскольку он отвечает за инициализацию новых процессов.
Казалось бы, зачем ядру копировать программу, запускаемую с
помощью функции exec, в адресное пространство процесса 1 ? Он мог
бы обратиться к внутреннему варианту функции прямо из ядра, одна-
-------------------------------------------------------------┐
│ алгоритм start /* процедура начальной загрузки системы */│
│ входная информация: отсутствует │
│ выходная информация: отсутствует │
│ { │
│ проинициализировать все структуры данных ядра; │
│ псевдо-монтирование корня; │
│ сформировать среду выполнения процесса 0; │
│ создать процесс 1; │
│ { │
│ /* процесс 1 */ │
│ выделить область; │
│ подключить область к адресному пространству процесса│
│ init; │
│ увеличить размер области для копирования в нее ис- │
│ полняемого кода; │
│ скопировать из пространства ядра в адресное прост- │
│ ранство процесса код программы, исполняемой процес-│
│ сом; │
│ изменить режим выполнения: вернуться из режима ядра │
│ в режим задачи; │
│ /* процесс init далее выполняется самостоятельно -- │
│ * в результате выхода в режим задачи, │
│ * init исполняет файл "/etc/init" и становится │
│ * "обычным" пользовательским процессом, производя- │
│ * щим обращения к системным функциям │
│ */ │
│ } │
│ /* продолжение нулевого процесса */ │
│ породить процессы ядра; │
│ /* нулевой процесс запускает программу подкачки, управ- │
│ * ляющую распределением адресного пространства процес- │
│ * сов между основной памятью и устройствами выгрузки. │
│ * Это бесконечный цикл; нулевой процесс обычно приоста-│
│ * навливает свою работу, если необходимости в нем боль-│
│ * ше нет. │
│ */ │
│ исполнить программу, реализующую алгоритм подкачки; │
│ } │
L-------------------------------------------------------------
Рисунок 7.30. Алгоритм загрузки системы
ко, по сравнению с уже описанным алгоритмом это было бы гораздо
труднее реализовать, ибо в этом случае функции exec пришлось бы
производить анализ имен файлов в пространстве ядра, а не в прост-
ранстве задачи. Подобная деталь, требующаяся только для процесса
init, усложнила бы программу реализации функции exec и отрица-
тельно отразилась бы на скорости выполнения функции в более общих
случаях.
Процесс init (Рисунок 7.31) выступает диспетчером процессов,
который порождает процессы, среди всего прочего позволяющие поль-
зователю регистрироваться в системе. Инструкции о том, какие про-
цессы нужно создать, считываются процессом init из файла
"/etc/inittab". Строки файла включают в себя идентификатор состо-
яния "id" (однопользовательский режим, многопользовательский и т.
д.), предпринимаемое действие (см. упражнение 7.43) и специфика-
цию программы, реализующей это действие (см. Рисунок 7.32). Про-
цесс init просматривает строки файла до тех пор, пока не обнару-
жит идентификатор состояния, соответствующего тому состоянию, в
котором находится процесс, и создает процесс, исполняющий прог-
рамму с указанной спецификацией. Например, при запуске в много-
пользовательском режиме (состояние 2) процесс init обычно порож-
-------------------------------------------------------------┐
│ алгоритм init /* процесс init, в системе именуемый │
│ "процесс 1" */ │
│ входная информация: отсутствует │
│ выходная информация: отсутствует │
│ { │
│ fd = open("/etc/inittab",O_RDONLY); │
│ while (line_read(fd,buffer)) │
│ { │
│ /* читать каждую строку файлу */ │
│ if (invoked state != buffer state) │
│ continue; /* остаться в цикле while */ │
│ /* найден идентификатор соответствующего состояния │
│ */ │
│ if (fork() == 0) │
│ { │
│ execl("процесс указан в буфере"); │
│ exit(); │
│ } │
│ /* процесс init не дожидается завершения потомка */ │
│ /* возврат в цикл while */ │
│ } │
│ │
│ while ((id = wait((int*) 0)) != -1) │
│ { │
│ /* проверка существования потомка; │
│ * если потомок прекратил существование, рассматри- │
│ * вается возможность его перезапуска */ │
│ /* в противном случае, основной процесс просто про- │
│ * должает работу */ │
│ } │
│ } │
L-------------------------------------------------------------
Рисунок 7.31. Алгоритм выполнения процесса init
-------------------------------------------------------------┐
│ Формат: идентификатор, состояние, действие, спецификация │
│ процесса │
│ Поля разделены между собой двоеточиями │
│ Комментарии в конце строки начинаются с символа '#' │
│ │
│ co::respawn:/etc/getty console console #Консоль в машзале│
│ 46:2:respawn:/etc/getty -t 60 tty46 4800H #комментарии │
L-------------------------------------------------------------
Рисунок 7.32. Фрагмент файла inittab
дает getty-процессы, управляющие функционированием терминальных
линий, входящих в состав системы. Если регистрация пользователя
прошла успешно, getty-процесс, пройдя через процедуру login, за-
пускает на исполнение регистрационный shell (см. главу 10). Тем
временем процесс init находится в состоянии ожидания (wait), наб-
людая за прекращением существования своих потомков, а также "вну-
чатых" процессов, оставшихся "сиротами" после гибели своих роди-
телей.
Процессы в системе UNIX могут быть либо пользовательскими,
либо управляющими, либо системными. Большинство из них составляют
пользовательские процессы, связанные с пользователями через тер-
миналы. Управляющие процессы не связаны с конкретными пользовате-
лями, они выполняют широкий спектр системных функций, таких как
администрирование и управление сетями, различные периодические
операции, буферизация данных для вывода на устройство построчной
печати и т.д. Процесс init может порождать управляющие процессы,
которые будут существовать на протяжении всего времени жизни сис-
темы, в различных случаях они могут быть созданы самими пользова-
телями. Они похожи на пользовательские процессы тем, что они ис-
полняются в режиме задачи и прибегают к услугам системы путем
вызова соответствующих системных функций.
Системные процессы выполняются исключительно в режиме ядра.
Они могут порождаться нулевым процессом (например, процесс заме-
щения страниц vhand), который затем становится процессом подкач-
ки. Системные процессы похожи на управляющие процессы тем, что
они выполняют системные функции, при этом они обладают большими
возможностями приоритетного выполнения, поскольку лежащие в их
основе программные коды являются составной частью ядра. Они могут
обращаться к структурам данных и алгоритмам ядра, не прибегая к
вызову системных функций, отсюда вытекает их исключительность.
Однако, они не обладают такой же гибкостью, как управляющие про-
цессы, поскольку для того, чтобы внести изменения в их программы,
придется еще раз перекомпилировать ядро.
7.10 ВЫВОДЫ
В данной главе были рассмотрены системные функции, предназна-
ченные для работы с контекстом процесса и для управления выполне-
нием процесса. Системная функция fork создает новый процесс, ко-
пируя для него содержимое всех областей, подключенных к
родительскому процессу. Особенность реализации функции fork сос-
тоит в том, что она выполняет инициализацию сохраненного регист-
рового контекста порожденного процесса, таким образом этот про-
цесс начинает выполняться, не дожидаясь завершения функции, и уже
в теле функции начинает осознавать свою предназначение как потом-
ка. Все процессы завершают свое выполнение вызовом функции exit,
которая отсоединяет области процесса и посылает его родителю сиг-
нал "гибель потомка". Процесс-родитель может совместить момент
продолжения своего выполнения с моментом завершения процесса-по-
томка, используя системную функцию wait. Системная функция exec
дает процессу возможность запускать на выполнение другие програм-
мы, накладывая содержимое исполняемого файла на свое адресное
пространство. Ядро отсоединяет области, ранее занимаемые процес-
сом, и назначает процессу новые области в соответствии с потреб-
ностями исполняемого файла. Совместное использование областей ко-
манд и наличие режима "sticky-bit" дают возможность более рацио-
нально использовать память и экономить время, затрачиваемое на
подготовку к запуску программ. Простым пользователям предоставля-
ется возможность получать привилегии других пользователей, даже
суперпользователя, благодаря обращению к услугам системной функ-
ции setuid и setuid-программ. С помощью функции brk процесс может
изменять размер своей области данных. Функция signal дает процес-
сам возможность управлять своей реакцией на поступающие сигналы.
При получении сигнала производится обращение к специальной функ-
ции обработки сигнала с внесением соответствующих изменений в
стек задачи и в сохраненный регистровый контекст задачи. Процессы
могут сами посылать сигналы, используя системную функцию kill,
они могут также контролировать получение сигналов, предназначен-
ных группе процессов, прибегая к услугам функции setpgrp.
Командный процессор shell и процесс начальной загрузки init
используют стандартные обращения к системным функциям, производя
набор операций, в других системах обычно выполняемых ядром. Shell
интерпретирует команды пользователя, переназначает стандартные
файлы ввода-вывода данных и выдачи ошибок, порождает процессы,
организует каналы между порожденными процессами, синхронизирует
свое выполнение с этими процессами и формирует коды, возвращаемые
командами. Процесс init тоже порождает различные процессы, в
частности, управляющие работой пользователя за терминалом. Когда
такой процесс завершается, init может породить для выполнения той
же самой функции еще один процесс, если это вытекает из информа-
ции файла "/etc/inittab".
G7.11 УПРАЖНЕНИЯH
1. Запустите с терминала программу, приведенную на Рисунке
7.33. Переадресуйте стандартный вывод данных в файл и срав-
ните результаты между собой.
-------------------------------------┐
│ main() │
│ { │
│ printf("hello\n"); │
│ if (fork() == 0) │
│ printf("world\n"); │
│ } │
L-------------------------------------
Рисунок 7.33. Пример модуля, содержащего вызов
функции fork и обращение к стандарт-
ному выводу
2. Разберитесь в механизме работы программы, приведенной на Ри-
сунке 7.34, и сравните ее результаты с результатами програм-
мы на Рисунке 7.4.
3. Еще раз обратимся к программе, приведенной на Рисунке 7.5 и
показывающей, как два процесса обмениваются сообщениями, ис-
пользуя спаренные каналы. Что произойдет, если они попытают-
ся вести обмен сообщениями, используя один канал ?
4. Возможна ли потеря информации в случае, когда процесс полу-
чает несколько сигналов прежде чем ему предоставляется
возможность отреагировать на них надлежащим образом ? (Расс-
мотрите случай, когда процесс подсчитывает количество полу-
ченных сигналов о прерывании.) Есть ли необходимость в реше-
нии этой проблемы ?
5. Опишите механизм работы системной функции kill.
6. Процесс в программе на Рисунке 7.35 принимает сигналы типа
"гибель потомка" и устанавливает функцию обработки сигналов
в исходное состояние. Что происходит при выполнении програм-
мы ?
7. Когда процесс получает сигналы определенного типа и не обра-
батывает их, ядро дампирует образ процесса в том виде, кото-
рый был у него в момент получения сигнала. Ядро создает в
текущем каталоге процесса файл с именем "core" и копирует в