ЖАНРЫ

Архитектура операционной системы UNIX
Шрифт:

алгоритм 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

7.10 ВЫВОДЫ

В данной главе были рассмотрены системные функции, предназначенные для работы с контекстом процесса и для управления выполнением процесса. Системная функция fork создает новый процесс, копируя для него содержимое всех областей, подключенных к родительскому процессу. Особенность реализации функции fork состоит в том, что она выполняет инициализацию сохраненного регистрового контекста порожденного процесса, таким образом этот процесс начинает выполняться, не дожидаясь завершения функции, и уже в теле функции начинает осознавать свою предназначение как потомка. Все процессы завершают свое выполнение вызовом функции exit, которая отсоединяет области процесса и посылает его родителю сигнал „гибель потомка“. Процесс-родитель может совместить момент продолжения своего выполнения с моментом завершения процесса-потомка, используя системную функцию wait. Системная функция exec дает процессу возможность запускать на выполнение другие программы, накладывая содержимое исполняемого файла на свое адресное пространство. Ядро отсоединяет области, ранее занимаемые процессом, и назначает процессу новые области в соответствии с потребностями исполняемого файла. Совместное использование областей команд и наличие режима „sticky-bit“ дают возможность более рационально использовать память и экономить время, затрачиваемое на подготовку к запуску программ. Простым пользователям предоставляется возможность получать привилегии других пользователей, даже суперпользователя, благодаря обращению к услугам системной функции setuid и setuid-программ. С помощью функции brk процесс может изменять размер своей области данных. Функция signal дает процессам возможность управлять своей реакцией на поступающие сигналы. При получении сигнала производится обращение к специальной функции обработки сигнала с внесением соответствующих изменений в стек задачи и в сохраненный регистровый контекст задачи. Процессы могут сами посылать сигналы, используя системную функцию kill, они могут также контролировать получение сигналов, предназначенных группе процессов, прибегая к услугам функции setpgrp.

Командный процессор shell и процесс начальной загрузки init используют стандартные обращения к системным функциям, производя набор операций, в других системах обычно выполняемых ядром. Shell интерпретирует команды пользователя, переназначает стандартные файлы ввода-вывода данных и выдачи ошибок, порождает процессы, организует каналы между порожденными процессами, синхронизирует свое выполнение с этими процессами и формирует коды, возвращаемые командами. Процесс init тоже порождает различные процессы, в частности, управляющие работой пользователя за терминалом. Когда такой процесс завершается, init может породить для выполнения той же самой функции еще один процесс, если это вытекает из информации файла „/etc/inittab“.

7.11 УПРАЖНЕНИЯ

1. Запустите с терминала программу, приведенную на Рисунке 7.33. Переадресуйте стандартный вывод данных в файл и сравните результаты между собой.

main {

 printf("hello\n“);

 if (fork == 0) printf("world\n“);

}

Рисунок 7.33. Пример модуля, содержащего вызов функции fork и обращение к стандартному выводу

2. Разберитесь в механизме работы программы, приведенной на Рисунке 7.34, и сравните ее результаты с результатами программы на Рисунке 7.4.

#include ‹fcntl.h›

int fdrd, fdwt;

char c;

main(argc, argv)

int argc; char *argv[];

{

 if (argc != 3) exit(1);

 fork;

 if ((fdrd = open(argv[1], O_RDONLY)) == -1) exit(1);

 if (((fdwt = creat(argv[2], 0666)) == -1) && ((fdwt = open(argv[2], O_WRONLY)) == -1)) exit(1);

 rdwrt;

}

rdwrt {

 for (;;) {

if (read(fdrd, &c, 1) != 1) return;

write(fdwt, &c, 1);

 }

}

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

3. Еще раз обратимся к программе, приведенной на Рисунке 7.5 и показывающей, как два процесса обмениваются сообщениями, используя спаренные каналы. Что произойдет, если они попытаются вести обмен сообщениями, используя один канал?

4. Возможна ли потеря информации в случае, когда процесс получает несколько сигналов прежде чем ему предоставляется возможность отреагировать на них надлежащим образом? (Рассмотрите случай, когда процесс подсчитывает количество полученных сигналов о прерывании.) Есть ли необходимость в решении этой проблемы?

5. Опишите механизм работы системной функции kill.

6. Процесс в программе на Рисунке 7.35 принимает сигналы типа „гибель потомка“ и устанавливает функцию обработки сигналов в исходное состояние. Что происходит при выполнении программы?

#include ‹signal.h›

main {

 extern catcher;

 signal(SIGCLD, catcher);

 if (fork == 0) exit;

 /* пауза до момента получения сигнала */

 pause;

}

catcher {

 printf("процесс-родитель получил сигнал\n");

 signal(SIGCLD, catcher);

}

Рисунок 7.35. Программа, в которой процесс принимает сигналы типа „гибель потомка“

7. Когда процесс получает сигналы определенного типа и не обрабатывает их, ядро дампирует образ процесса в том виде, который был у него в момент получения сигнала. Ядро создает в текущем каталоге процесса файл с именем „core“ и копирует в него пространство процесса, области команд, данных и стека. Впоследствии пользователь может тщательно изучить дамп образа процесса с помощью стандартных средств отладки. Опишите алгоритм, которому на Ваш взгляд должно следовать ядро в процессе создания файла „core“. Что нужно предпринять в том случае, если в текущем каталоге файл с таким именем уже существует? Как должно вести себя ядро, когда в одном и том же каталоге дампируют свои образы сразу несколько процессов?

Поделиться с друзьями: