Выполнение клонированного потомка начинается с функции childFunc(), которая получает файловый дескриптор (посредством аргумента arg), открытый главной программой (на шаге ). Потомок закрывает этот дескриптор и завершается, выполняя операцию return .

Листинг 28.3. Использование clone() для создания дочерних процессов

procexec/t_clone.c

#define _GNU_SOURCE

#include

#include

#include

#include

#include "tlpi_hdr.h"

#ifndef CHILD_SIG

#define CHILD_SIG SIGUSR1 /* При завершении клонированного потомка

будет сгенерирован сигнал */

#endif

static int /* Начальная функция для клонированного потомка */

childFunc(void *arg)

{

if (close(*((int *) arg)) == –1)

errExit("close");

return 0; /* Здесь потомок завершается */

}

int

main(int argc, char *argv[])

{

const int STACK_SIZE = 65536; /* Размер стека для клонированного потомка */

char *stack; /* Начало буфера стека */

char *stackTop; /* Конец буфера стека */

int s, fd, flags;

fd = open("/dev/null", O_RDWR); /* Потомок закроет дескриптор fd */

if (fd == –1)

errExit("open");

/* Если argc > 1, потомок будет разделять таблицу файловых дескрипторов с родителем */

flags = (argc > 1)? CLONE_FILES: 0;

/* Выделяем стек для потомка */

stack = malloc(STACK_SIZE);

if (stack == NULL)

errExit("malloc");

stackTop = stack + STACK_SIZE; /* Предполагается, что стек растет сверху вниз */

/* Игнорируем CHILD_SIG, если это сигнал, который по умолчанию завершает процесс;

при этом не игнорируем SIGCHLD (который игнорируется по умолчанию), иначе это

могло бы помешать созданию процесса-"зомби". */

if (CHILD_SIG!= 0 && CHILD_SIG!= SIGCHLD)

if (signal(CHILD_SIG, SIG_IGN) == SIG_ERR)

errExit("signal");

/* Создаем потомка; потомок начинает выполнение с функции childFunc() */

if (clone(childFunc, stackTop, flags | CHILD_SIG, (void *) &fd) == –1)

errExit("clone");

/* Родитель переходит сюда и ждет потомка; потомку, который применяет

для уведомления любой сигнал, кроме SIGCHLD, нужно значение _WCLONE. */

if (waitpid(–1, NULL, (CHILD_SIG!= SIGCHLD)? __WCLONE: 0) == –1)

errExit("waitpid");

printf("child has terminated\n");

/* Повлияло ли на родителя закрытие дескриптора в потомке? */

s = write(fd, "x", 1);

if (s == –1 && errno == EBADF)

printf("file descriptor %d has been closed\n", fd);

else if (s == –1)

printf("write() on file descriptor %d failed "

"unexpectedly (%s)\n", fd, strerror(errno));

else

printf("write() on file descriptor %d succeeded\n", fd);

exit(EXIT_SUCCESS);

}

procexec/t_clone.c

Запустив программу из листинга 28.3 без аргумента командной строки, мы получим следующий результат:

$ ./t_clone Не использует CLONE_FILES

child has terminated

write() on file descriptor 3 succeeded Вызов close() в потомке не влияет на родителя

Если запустить программу с аргументом командной строки, можно увидеть, что оба процесса используют одну и ту же таблицу файловых дескрипторов:

$ ./t_clone x Использует CLONE_FILES

child has terminated

file descriptor 3 has been closed Вызов close() в потомке затрагивает родителя

Более сложный пример использования вызова clone() представлен в файле procexec/demo_clone.c, входящем в архив с исходным кодом для этой книги.

28.2.1. Аргумент flags вызова clone()

Аргумент flags вызова clone() представляет собой сочетание (побитовое ИЛИ) значений битовой маски, описанных на следующих страницах. Мы рассмотрим их в порядке, который облегчает объяснение; начнем с тех флагов, что применяются в реализации потоков выполнения POSIX. С этой точки зрения термин «процесс», многократно упоминающийся ниже, часто можно заменить словом «поток».

На данном этапе стоит отметить, что наши попытки провести грань между терминами «процесс» и «поток» в некоторой степени являются жонглированием словами. Это немного помогает при знакомстве с понятием единицы планирования ядра (Kernel Scheduling Entity, KSE); с его помощью в технической литературе иногда описывают объекты, с которыми работает планировщик ядра. На самом деле потоки и процессы являются всего лишь экземплярами KSE, поддерживающими разную степень совместного использования атрибутов (виртуальной памяти, дескрипторов открытых файлов, действий сигналов, идентификатора процесса и т. д.). Спецификация POSIX-потоков описывает лишь один из множества возможных способов разделения атрибутов между потоками.

По мере описания далее мы иногда будем упоминать две главные реализации POSIX-потоков, доступные в Linux: LinuxThreads (старую) и NTPL (более современную). Детальную информацию о них можно найти в разделе 33.5.

Перейти на страницу:

Похожие книги