Для того, чтобы перевести систему из неактивное состояние в активное, администратор выполняет процедуру "начальной загрузки". На разных машинах эта процедура имеет свои особенности, однако во всех случаях она реализует одну и ту же цель: загрузить копию операционной системы в основную память машины и запустить ее на исполнение. Обычно процедура начальной загрузки включает в себя несколько этапов. Переключением клавиш на пульте машины администратор может указать адрес специальной программы аппаратной загрузки, а может, нажав только одну клавишу, дать команду машине запустить процедуру загрузки, исполненную в виде микропрограммы. Эта программа может состоять из нескольких команд, подготавливающих запуск другой программы. В системе UNIX процедура начальной загрузки заканчивается считыванием с диска в память блока начальной загрузки (нулевого блока). Программа, содержащаяся в этом блоке, загружает из файловой системы ядро ОС (например, из файла с именем "/unix" или с другим именем, указанным администратором). После загрузки ядра системы в память управление передается по стартовому адресу ядра и ядро запускается на выполнение (алгоритм start, Рисунок 7.30).
Ядро инициализирует свои внутренние структуры данных. Среди прочих структур ядро создает связные списки свободных буферов и индексов, хеш-очереди для буферов и индексов, инициализирует структуры областей, точки входа в таблицы страниц и т.д. По окончании этой фазы ядро монтирует корневую файловую систему и формирует среду выполнения нулевого процесса, среди всего прочего создавая пространство процесса, инициализируя нулевую точку входа в таблице процесса и делая корневой каталог текущим для процесса.
Когда формирование среды выполнения процесса заканчивается, система исполняется уже в виде нулевого процесса. Нулевой процесс "ветвится", запуская алгоритм fork прямо из ядра, поскольку сам процесс исполняется в режиме ядра. Порожденный нулевым новый процесс, процесс 1, запускается в том же режиме и создает свой пользовательский контекст, формируя область данных и присоединяя ее к своему адресному пространству. Он увеличивает размер области до надлежащей величины и переписывает программу загрузки из адресного пространства ядра в новую область: эта программа теперь будет определять контекст процесса 1. Затем процесс 1 сохраняет регистровый контекст задачи, "возвращается" из режима ядра в режим задачи и исполняет только что переписанную программу. В отличие от нулевого процесса, который является процессом системного уровня, выполняющимся в режиме ядра, процесс 1 относится к пользовательскому уровню. Код, исполняемый процессом 1, включает в себя вызов системной функции exec, запускающей на выполнение программу из файла "/etc/init". Обычно процесс 1 именуется процессом init, поскольку он отвечает за инициализацию новых процессов.
алгоритм start /* процедура начальной загрузки системы */
входная информация: отсутствует
выходная информация: отсутствует
{
проинициализировать все структуры данных ядра;
псевдо-монтирование корня;
сформировать среду выполнения процесса 0;
создать процесс 1;
{
/* процесс 1 */
выделить область;
подключить область к адресному пространству процесса
init;
увеличить размер области для копирования в нее ис-
полняемого кода;
скопировать из пространства ядра в адресное прост-
ранство процесса код программы, исполняемой процес-
сом;
изменить режим выполнения: вернуться из режима ядра
в режим задачи;
/* процесс init далее выполняется самостоятельно --
* в результате выхода в режим задачи,
* init исполняет файл "/etc/init" и становится
* "обычным" пользовательским процессом, производя-
* щим обращения к системным функциям
*/
}
/* продолжение нулевого процесса */
породить процессы ядра;
/* нулевой процесс запускает программу подкачки, управ-
* ляющую распределением адресного пространства процес-
* сов между основной памятью и устройствами выгрузки.
* Это бесконечный цикл; нулевой процесс обычно приоста-
* навливает свою работу, если необходимости в нем боль-
* ше нет.
*/
исполнить программу, реализующую алгоритм подкачки;
}
|
Рисунок 7.30. Алгоритм загрузки системы
Казалось бы, зачем ядру копировать программу, запускаемую с помощью функции exec, в адресное пространство процесса 1? Он мог бы обратиться к внутреннему варианту функции прямо из ядра, однако, по сравнению с уже описанным алгоритмом это было бы гораздо труднее реализовать, ибо в этом случае функции exec пришлось бы производить анализ имен файлов в пространстве ядра, а не в пространстве задачи. Подобная деталь, требующаяся только для процесса init, усложнила бы программу реализации функции exec и отрицательно отразилась бы на скорости выполнения функции в более общих случаях.
Процесс init (Рисунок 7.31) выступает диспетчером процессов, который порождает процессы, среди всего прочего позволяющие пользователю регистрироваться в системе. Инструкции о том, какие процессы нужно создать, считываются процессом init из файла "/etc/inittab". Строки файла включают в себя идентификатор состояния "id" (однопользовательский режим, многопользовательский и т. д.), предпринимаемое действие (см. упражнение 7.43) и спецификацию программы, реализующей это действие (см. Рисунок 7.32). Процесс init просматривает строки файла до тех пор, пока не обнаружит идентификатор состояния, соответствующего тому состоянию, в котором находится процесс, и создает процесс, исполняющий программу с указанной спецификацией. Например, при запуске в многопользовательском режиме (состояние 2) процесс init обычно порождает getty-процессы, управляющие функционированием терминальных линий, входящих в состав системы. Если регистрация пользователя прошла успешно, getty-процесс, пройдя через процедуру login, запускает на исполнение регистрационный shell (см. главу 10). Тем временем процесс init находится в состоянии ожидания (wait), наблюдая за прекращением существования своих потомков, а также "внучатых" процессов, оставшихся "сиротами" после гибели своих родителей.
Процессы в системе UNIX могут быть либо пользовательскими, либо управляющими, либо системными. Большинство из них составляют пользовательские процессы, связанные с пользователями через терминалы. Управляющие процессы не связаны с конкретными пользователями, они выполняют широкий спектр системных функций, таких как администрирование и управление сетями, различные периодические операции, буферизация данных для вывода на устройство построчной печати и т.д. Процесс init может порождать управляющие процессы, которые будут существовать на протяжении всего времени жизни системы, в различных случаях они могут быть созданы самими пользователями. Они похожи на пользовательские процессы тем, что они исполняются в режиме задачи и прибегают к услугам системы путем вызова соответствующих системных функций.
Системные процессы выполняются исключительно в режиме ядра. Они могут порождаться нулевым процессом (например, процесс замещения страниц vhand), который затем становится процессом подкачки. Системные процессы похожи на управляющие процессы тем, что они выполняют системные функции, при этом они обладают большими возможностями приоритетного выполнения, поскольку лежащие в их основе программные коды являются составной частью ядра. Они могут обращаться к структурам данных и алгоритмам ядра, не прибегая к вызову системных функций, отсюда вытекает их исключительность. Однако, они не обладают такой же гибкостью, как управляющие процессы, поскольку для того, чтобы внести изменения в их программы, придется еще раз перекомпилировать ядро.
алгоритм 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)
{
/* проверка существования потомка;
* если потомок прекратил существование, рассматри-
* вается возможность его перезапуска */
/* в противном случае, основной процесс просто про-
* должает работу */
}
}
|
Рисунок 7.31. Алгоритм выполнения процесса init
Формат: идентификатор, состояние, действие, спецификация
процесса
Поля разделены между собой двоеточиями
Комментарии в конце строки начинаются с символа '#'
co::respawn:/etc/getty console console #Консоль в машзале
46:2:respawn:/etc/getty -t 60 tty46 4800H #комментарии
|
Рисунок 7.32. Фрагмент файла inittab
Предыдущая глава || Оглавление || Следующая глава