Системные вызовы для работы с процессами

Рассмотрим системные вызовы, используемые при работе с процессами в ОС UNIX и описанные в библиотеке <fcntl.h>.

Системный вызов fork создает новый процесс, копируя вызывающий, вызов exit завершает выполнение процесса, wait дает возможность родительскому процессу синхронизировать свое продолжение с завершением порожденного процесса, а sleep приостанавливает на определенное время выполнение процесса. Системный вызов exec дает процессу возможность запускать на выполнение другую программу.

FORK Создание нового процесса:

int fork(void)

pid = fork();

В ходе выполнения функции ядро производит следующую последовательность действий:

1. Отводит место в таблице процессов под новый процесс.

2. Присваивает порождаемому процессу уникальный код идентификации.

3. Создает логическую копию контекста родительского процесса. Поскольку те или иные составляющие процесса, такие как область команд, могут разделяться другими процессами, ядро может иногда вместо копирования области в новый физический участок памяти просто увеличить значение счетчика ссылок на область.

4. Увеличивает значения счетчиков числа файлов, связанных с процессом как в таблице файлов, так и в таблице индексов.

5. Возвращает родительскому процессу код идентификации порожденного процесса, а порожденному процессу – 0.

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

Процесс, вызывающий функцию fork, называется родительским (процесс-предок), вновь создаваемый процесс называется порожденным (процесс-потомок). Процесс-потомок всегда имеет более высокий приоритет, чем процесс-предок, так как приоритет процесса является самым высоким в момент порождения и уменьшается по мере нахождения в состоянии выполнения. Нулевой процесс, возникающий внутри ядра при загрузке системы, является единственным процессом, не создаваемым с помощью функции fork.

EXIT Завершение выполнения процесса:

void exit(int status)

где status – значение, возвращаемое функцией родительскому процессу. Процессы могут вызывать функцию exit как в явном, так и в неявном виде (по окончании выполнения программы функция exit вызывается автоматически с параметром 0). Также ядро может вызывать функцию exit по своей инициативе, если процесс завершается по сигналу. В этом случае значение параметра status равно номеру сигнала.

Выполнение вызова exit приводит к "прекращению существования" процесса, освобождению ресурсов и ликвидации контекста. Система не накладывает никакого ограничения на продолжительность выполнения процесса.

WAIT Ожидание завершения выполнения процесса-потомка:

int wait(int *stat)

pid = wait(stat_addr);

где pid – значение кода идентификации (PID) завершившегося потомка, stat_addr – адрес переменной целого типа, в которую будет помещено возвращаемое функцией exit значение.

С помощью этой функции процесс синхронизирует продолжение своего выполнения с моментом завершения потомка. Ядро ведет поиск потомков процесса, прекративших существование, и в случае их отсутствия возвращает ошибку. Если потомок, прекративший существование, обнаружен, ядро передает его код идентификации и значение, возвращаемое через параметр функции exit, процессу, вызвавшему функцию wait. Таким образом, через параметр функции exit (status) завершающийся процесс может передавать различные значения, кодирующие информацию о причине завершения процесса, однако на практике этот параметр используется по назначению довольно редко. Если процесс, выполняющий функцию wait, имеет потомков, продолжающих существование, он приостанавливается до получения ожидаемого сигнала. Ядро не возобновляет по своей инициативе процесс, приостановившийся с помощью функции wait: такой процесс может возобновиться только в случае получения сигнала о "гибели потомка".

SLEEP Приостанов работы процесса на определенное время:

void sleep(unsigned seconds)

где seconds – количество секунд, на которое требуется приостановить работу процесса.

Сначала ядро повышает приоритет работы процесса так, чтобы заблокировать все прерывания, которые могли бы (путем создания конкуренции) помешать работе с очередями приостановленных процессов, и запоминает старый приоритет, чтобы восстановить его, когда выполнение процесса будет возобновлено. Процесс получает пометку “приостановленного”, адрес приостанова и приоритет запоминаются в таблице процессов, а процесс помещается в хеш-очередь приостановленных процессов. При этом в простейшем случае (когда приостанов не допускает прерываний) процесс выполняет переключение контекста и благополучно "засыпает". На время приостанова процесс помещается в хеш-очередь приостановленных процессов. По истечение указанного периода времени приостановленный процесс "пробуждается" и ядро осуществляет его запуск. Нельзя гарантировать, что приостановленный процесс сразу возобновит свою работу: он может быть выгружен на время приостанова и тогда требуется его подкачка в память; в это время на выполнении может находится процесс с более высоким приоритетом или процесс, не допускающий прерываний (например, находящийся в критическом интервале) и т.д.

Параметр seconds устанавливает минимальный интервал, в течение которого процесс будет приостановлен, а реальное время приостанова в любом случае будет несколько больше, хотя бы за счет времени, необходимого для переключения процессов.

EXEC Запуск программы.

Системный вызов exec осуществляется несколькими библиотечными функциями – execl, execv, execle и др.. Приведем формат одной из них:

int execv(char *path, char *argv[])

res = execv(path, argv);

где path – имя исполняемого файла, argv – указатель на массив параметров, которые передаются вызываемой программе. Этот массив аналогичен параметру argv командной строки функции main.

Список argv должен содержать минимум два параметра: первый – имя программы, подлежащей выполнению (отображается в argv[0] функции main новой программы), второй – NULL (завершающий список аргументов). Системный вызов exec дает возможность процессу запускать другую программу, при этом соответствующий этой программе исполняемый файл будет располагаться в пространстве памяти процесса. Содержимое пользовательского контекста после вызова функции становится недоступным, за исключением передаваемых функции параметров, которые переписываются ядром из старого адресного пространства в новое.

Вызов exec возвращает 0 при успешном завершении и -1 при аварийном. В последнем случае управление возвращается в вызывающую программу.

В качестве примера использования этого вызова можно привести работу командного интерпретатора shell: при выполнении команды он сначала порождает свою копию (fork), а затем запускает соответствующую указанной команде программу с помощью exec.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: