Когда выполняется вызов fork(), потомок получает копии всех файловых дескрипторов родителя. Эти копии создаются в той же манере, как работает функция dup(); это означает, что соответствующие дескрипторы в родительском и дочернем процессах указывают на один и тот же открытый файл. Как мы уже видели в разделе 5.4, дескриптор открытого файла содержит его текущее смещение (образовавшееся во время редактирования с помощью функций read(), write() и lseek()) и флаги состояния (установленные вызовом open() и измененные операцией fcntl() F_SETFL). Как следствие, эти атрибуты открытого файла являются общими для родителя и потомка. Например, если дочерний процесс обновляет смещение, это изменение доступно его родителю посредством соответствующего дескриптора.
Тот факт, что после вызова fork() эти атрибуты являются общими для родителя и потомка, продемонстрирован в программе из листинга 24.2. Эта программа открывает временный файл, используя функцию mkstemp(), и затем вызывает fork() для создания дочернего процесса. Потомок изменяет смещение открытого файла и флаги его состояния, после чего завершает работу. Затем родитель извлекает смещение и флаги файла, чтобы удостовериться в том, что он может видеть изменения, внесенные его потомком. Запустив программу, мы получим следующий вывод:
$ ./fork_file_sharing
File offset before fork(): 0
O_APPEND flag before fork() is: off
Child has exited
File offset in parent: 1000
O_APPEND flag in parent is: on
Объяснение того, зачем мы приводим значение, возвращаемое функцией lseek(), к типу long long, ищите в разделе 5.10.
Листинг 24.2. Совместный доступ к смещению файла и флагам его состояния из родителя и потомка
procexec/fork_file_sharing.c
#include
#include
#include
#include "tlpi_hdr.h"
int
main(int argc, char *argv[])
{
int fd, flags;
char template[] = "/tmp/testXXXXXX";
setbuf(stdout, NULL); /* Отключаем буферизацию стандартного вывода */
fd = mkstemp(template);
if (fd == –1)
errExit("mkstemp");
printf("File offset before fork(): %lld\n", (long long) lseek(fd, 0, SEEK_CUR));
flags = fcntl(fd, F_GETFL);
if (flags == –1)
errExit("fcntl — F_GETFL");
printf("O_APPEND flag before fork() is: %s\n", (flags & O_APPEND)? "on": "off");
switch (fork()) {
case –1:
errExit("fork");
case 0: /* Потомок изменяет сдвиг файла и флаги его состояния */
if (lseek(fd, 1000, SEEK_SET) == –1)
errExit("lseek");
flags = fcntl(fd, F_GETFL); /* Извлекаем текущие флаги */
if (flags == –1)
errExit("fcntl — F_GETFL");
flags |= O_APPEND; /* Устанавливаем флаг O_APPEND */
if (fcntl(fd, F_SETFL, flags) == –1)
errExit("fcntl — F_SETFL");
_exit(EXIT_SUCCESS);
default: /* Родитель может видеть изменения, внесенные потомком */
if (wait(NULL) == –1)
errExit("wait"); /* Ждем завершения работы потомка */
printf("Child has exited\n");
printf("File offset in parent: %lld\n", (long long) lseek(fd, 0, SEEK_CUR));
flags = fcntl(fd, F_GETFL);
if (flags == –1)
errExit("fcntl — F_GETFL");
printf("O_APPEND flag in parent is: %s\n", (flags & O_APPEND)? "on": "off");
exit(EXIT_SUCCESS);
}
}
procexec/fork_file_sharing.c
Совместный доступ к атрибутам файла из родительского и дочернего процессов часто бывает полезен. Например, если родитель и потомок одновременно осуществляют запись в файл, доступ к его смещению позволяет избежать перезаписи процессами вывода друг друга. Однако это не предотвращает случайное смешивание записываемых ими данных. Если такое поведение вас не устраивает, вам придется реализовать некую форму синхронизации между процессами. Например, родитель может использовать системный вызов wait(), чтобы дождаться, пока его потомок не завершит работу. Такой подход применяется в командной оболочке, чтобы приглашение командной строки выводилось только после завершения работы команды в дочернем процессе (за исключением тех случаев, когда пользователь намеренно запускает команду в фоновом режиме, указывая знак амперсанда после ее имени).
Если не требуется такое совместное использование атрибутов файлов, приложение должно быть спроектировано так, чтобы родитель и потомок после вызова fork() использовали разные дескрипторы, сразу же закрывая те из них, которые были получены из другого процесса (если один из процессов выполняет вызов exec(), вам также может пригодиться флаг FD_CLOEXEC, описанный в разделе 27.4). Эти шаги показаны на рис. 24.2.
Рис. 24.2.
24.2.2. Семантика памяти вызова fork()