Листинг 10.3. Пример race1.c #include #include #include #include #define WORKTIME 3
int main (void)
{
unsigned long parents = 0;
unsigned long children = 0;
pid_t result;
time_t nowtime = time (NULL);
result = fork ();
if (!result) {
while (time (NULL) < nowtime+WORKTIME) children++;
printf ("children: %ld\n", children);
} else {
while (time (NULL) < nowtime+WORKTIME) parents++;
printf ("parents: %ld\n", parents);
}
return 0;
}
Приведенная в листинге 10.3 программа в каждом из процессов запускает практически одинаковые циклы, которые в течение трех секунд "накручивают" счетчики children и parents. По завершении каждого цикла выводится отчет о состоянии счетчика. Итак, проверяем:
$ gcc -o race1 race1.c $ ./race1 children: 470136
parents: 830649
$ ./race1 children: 586457
parents: 979719
$ ./race1 children: 447814
parents: 810994
Обратите внимание, что при выполнении приведенного примера возможен вариант, при котором "родитель" завершится чуть раньше и оболочка "выкинет" приглашение командной строки перед отчетом процесса-потомка. Если такое произойдет, то это будет лишь очередным доказательством того, что процессы работают независимо.
Передача управления: execve() Системный вызов execve() загружает в процесс другую программу и передает ей безвозвратное управление. Этот системный вызов объявлен в заголовочном файле unistd.h следующим образом:
int execve (const char * PATH, const char ** ARGV,
const char ** ENVP);
Итак, системный вызов execve() принимает три аргумента:
PATH — это путь к исполняемому файлу программы, которая будет запускаться внутри процесса. Здесь следует учитывать, что одноименная переменная окружения в системном вызове execve() не используется. В результате ответственность за поиск программы возложена только на программиста.
ARGV — это уже знакомый нам массив аргументов программы. Здесь важно помнить, что первый аргумент (ARGV[0]) этого массива является именем программы или чем-то другим (на ваше усмотрение), но не фактическим аргументом. Последним элементом ARGV должен быть NULL.
ENVP — это тоже уже знакомый нам массив, содержащий окружение запускаемой программы. Этот массив также должен заканчиваться элементом NULL.
Выполняющийся внутри процесса код называется образом процесса (process image). Важно понимать, что системный вызов execve() заменяет текущий образ процесса на новый. Следовательно, возврата в исходную программу не происходит.
В случае ошибки execve() возвращает –1, но если новая программа начала выполняться, то execve() уже ничего не вернет, поскольку работа исходной программы в текущем процессе на этом заканчивается.
Исходя из сказанного, можно сделать вывод, что возврат из системного вызова execve() происходит только в том случае, если произошла ошибка. Учитывая также то, что execve() не может возвращать ничего кроме –1, следующая проверка будет явно избыточной:
if (execve (binary_file, argv, environ) == -1) {
/* обработка ошибки */
}
Целесообразнее более простая форма обнаружения ошибки:
execve (binary_file, argv, environ);
/* обработка ошибки */
Рассмотрим теперь программу (листинг 10.4), демонстрирующую работу системного вызова execve().