111
Пример 2:
•создает дочерний процесс посредством вызова fork(…) и, в случае ошибки, - завершает работу;
•родительский процесс: ожидает завершения дочернего процесса, а затем, с помощью вызова execvp(…), меняет тело на программу Примера 1;
•дочерний процесс сразу, с помощью вызова execvp(…), меняет тело на программу Примера 1;
Исходный текст, реализующий данный пример, представлен на листинге 3.2.
Листинг 3.2 — Реализация программы Примера 2
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h>
int main(int argc, char *argv[]) { puts("Проект lab9.2 ..."); int ret, fid, pret;
fid = fork(); if (fid < 0) {
perror("Не могу сделать fork():"); exit(EXIT_FAILURE);
}
if (fid > 0){ wait(NULL);
puts("Родительский процесс ...");
if (execvp("lab9.1", &argv[0]) < 0){
perror("Родительский процесс: не могу запустить execvp():");
}
puts("Завершение родительской части проекта lab9.2 ..."); exit(EXIT_SUCCESS);
}
puts("Дочерний процесс ...");
if (execvp("lab9.1", &argv[0]) < 0){
perror("Дочерний процесс: не могу запустить execvp():");
}
puts("Завершение дочерней части проекта lab9.2 ..."); exit(EXIT_SUCCESS);
}
На рисунке 3.2 показан вывод этой программы, из которого видно, что:
•запускается дочерний процесс, который определяет, что он не является ни лидером группы и ни лидером сессии;
•дочерний процесс создает новую сессию посредством обращения к системному вызову setsid();
•дочерний процесс проверяет свои атрибуты и устанавливает, что он является как лидером группы, так и лидером собственной сессии;
•дочерний процесс завершает свою работу;
•просыпается родительский процесс;
•родительский процесс проверяет свои атрибуты и устанавливает, что он яв-
112
ляется как лидером группы, но не является лидером сессии;
• родительский процесс завершает свою работу.
Рисунок 3.2 — Установка новой сессии дочернего процесса
Замечание
Обратите внимание, что программы, запускаемые в командной строке терминала, создают процессы, которые являются лидерами группы, и поэтому они не могут сразу стать лидерами сессии; необходимо дополнительно изменить их групповую принадлежность.
Вданном подразделе, мы рассмотрим группу системных вызовов, связанных с
порождением и завершением процессов.
Прежде всего, взаимодействие процессов на этом уровне определяется функциями:
•fork(...) - порождает новый дочерний процесс;
•wait(...) - ожидает завершение любого дочернего процесса, регистрируя их в системе и завершая сам процесс удаления из системы;
113
• waitpid(...) - ожидает завершение процесса с конкретным номером PID.
Синтаксис перечисленных функций имеет следующий вид:
#include <unistd.h> pid_t fork (void);
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait (int *status);
pid_t waitpid (pid_t pid, int *status, int options);
где status — указатель на целочисленное значение, возвращаемое системным вызовом exit(...) или оператором return из функции main(...); если не требуется проводить анализ кода завершения дочернего процесса, то следует использовать значение
NULL;
pid — идентификатор ожидаемого дочернего процесса;
options — дополнительные опции, которые мы рассмотрим ниже.
Новый (порожденный, дочерний) процесс является точной копией (родительского) процесса, вызвавшего fork(...), за исключением следующих моментов:
•у порожденного процесса свой идентификатор, отличный от идентификатора родительского процесса;
•у порожденного процесса собственная копия файловых дескрипторов, ссыла-
ющихся на те же описания открытых файлов, что и соответствующие дескрипторы родительского процесса;
•порожденный процесс не наследует блокировки файлов, установленные родительским процессом.
•порожденный процесс создается с одним потоком управления – копией того, что вызвал fork(...);
•имеются также некоторые тонкости, связанные с обработкой сигналов, на которых мы здесь останавливаться не будем.
Вслучае успешного завершения функция fork(...) возвращает порожденному процессу 0, а родительскому процессу – идентификатор порожденного процесса. После этого оба процесса начинают независимо выполнять инструкции, расположенные за обращением к функции fork(...).
При неудаче, родительскому процессу возвращается -1, новый процесс не создается, а значение errno устанавливается:
•EAGAIN - не возможно выделить достаточно памяти для копирования
таблиц страниц родителя и для выделения структуры описания процессапотомка;
•ENOMEM - не возможно выделить необходимые ресурсы ядра, потому что памяти слишком мало.
114
Замечание
В ОС Linux родительский и дочерний процессы разделяют сегмент кода, а сегменты данных и стека у них — различны.
Родительский процесс реализует ожидание завершения процессов-потомков и получает информацию о их статусе завершения с помощью функций семейства wait(...).
Функция wait(...) приостанавливает выполнение текущего процесса до тех пор, пока дочерний процесс не завершится, или до появления сигнала, который либо завершает текущий процесс, либо требует вызвать функцию-обработчик. Если дочерний процесс к моменту вызова функции уже завершился (так называемый процесс "зомби" ("zombie")), то функция немедленно возвращается. Системные ресурсы, связанные с дочерним процессом, освобождаются.
Функция wait(...) возвращает идентификатор дочернего процесса, который завершил выполнение, или -1 в случае ошибки и переменной errno присваивается номер ошибки.
В частности, -1 возвращается, когда дочерних процессов еще небыло или они все уже обработаны.
Замечание
Целочисленная переменная status содержит в себе статус завершения дочернего процесса, которое установленно им с помощью системного вызова exit(...) или оператора return, в функции main(...). Чтобы правильно извлечь это значение, следует воспользоваться двумя макросами:
•WIFEXITED(status) - не равно нулю, если дочерний процесс успешно завершился;
•WEXITSTATUS(status) - возвращает восемь младших битов значения, которое вер-
нул завершившийся дочерний процесс; этот макрос можно использовать только, если макрос WIFEXITED вернул ненулевое значение.
Чтобы лучше продемострировать возможности функций fork(...) и wait(...), рассмотрим текст программы, представленный на листинге 133.
Листинг 3.3 — Изучение системных вызовов fork() и wait()
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h>
int main(void) {
puts("Проект lab9.3 ..."); pid_t pid, cpid;
int i, status=0xAAAA;
pid = wait(&status); if (pid < 0) {
perror("wait():");
puts("Использование wait(), когда дочерних процессов нет:"); printf(" pid = %i status = 0x%X\n", pid, status);
115
}
puts("Запуск пяти дочерних процессов:"); for (i=0; i<5; i++) {
pid = fork(); if (pid > 0) {
printf(" |
Родителский |
процесс: Запустил ДП%i |
с pid = %i\n", |
i, |
pid); |
}else{ |
|
|
|
|
|
printf(" |
Дочерний |
процесс %i: Завершаюсь |
со статусом = %i\n", i, |
i); |
|
exit(i);
}
}
sleep(1);
puts("Ожидание завершения дочерних процессов:"); while((cpid = wait(&status)) > 0) {
if (WIFEXITED(status)) {
printf(" Процесс pid = %i завершился со статусом = %i\n", cpid, WEXITSTATUS(status));
}
}
puts("Завершение проекта lab9.3 ..."); return EXIT_SUCCESS;
}
Замечание
Системный вызов wait(...) может возвращать значение -1 и в других случаях, например, при обработке сигналов, что будет рассмотрено далее.
На рисунке 3.3 представлен результат работы этой программы.
Рисунок 3.3 — Результат примера взаимодейстаия fork() и wait()