Первые два из них эмулируют поведение вызова vfork(). Оставшиеся флаги указывают на то, что родитель и потомок должны иметь общие атрибуты файловой системы (umask, корневой и текущий каталог), таблицу действий сигналов и таблицу дескрипторов открытых файлов. Разница между результатами для вызовов clone() и vfork() представляет небольшой объем дополнительной работы, выполняемой последней для копирования этих данных в дочерний процесс. Затраты на копирование атрибутов файловой системы и таблицы действий сигналов остаются постоянными. Однако время копирования таблицы дескрипторов открытых файлов зависит от количества этих дескрипторов. Например, открытие в родительском процессе 100 файлов увеличит реальное время выполнения вызова vfork() (первый столбец в таблице) с 3,52 до 5,04 секунды, но никак не повлияет на вызов clone().
Здесь приводится время выполнения функции-обертки clone() из библиотеки glibc, а не самого системного вызова sys_clone(). Другие тесты (которые здесь не приводятся) показали пренебрежительно малую разницу между sys_clone() и clone() в случае, когда дочерняя функция немедленно завершает работу.
Различия между вызовами fork() и vfork() довольно значительные. Однако необходимо учитывать следующие обстоятельства.
• Последний столбец таблицы, в котором vfork() показывает более чем 30-кратный прирост производительности по сравнению с fork(), относится к большому процессу. Обычные процессы имели бы показатели, близкие к первым двум столбцам.
• Поскольку время, необходимое для создания процесса, обычно меньше того, что требуется для выполнения exec(), разница между вызовами fork() и vfork() становится гораздо менее заметной, если после них запустить новую программу. Этот факт проиллюстрирован в последних двух столбцах табл. 28.3, в которых каждый потомок вместо немедленного завершения выполняет exec(). В качестве запускаемой программы использовалась команда true (/bin/true, выбранная потому, что она не генерирует вывода). В этом случае видно, что различия между fork() и vfork() получились намного меньшими.
На самом деле данные, показанные в табл. 28.3, не отражают всех затрат, связанных с вызовом exec(), поскольку потомок запускает одну и ту же программу в каждой итерации цикла. В результате ресурсы, затрачиваемые на ввод/вывод для считывания программы с диска в память, практически нивелируются, поскольку программа будет скопирована в кэш буфера ядра при первом выполнении exec() и останется там. Если бы на каждой итерации цикла вызывалась новая программа (например, копия одной и той же команды, но с другим именем), мы бы могли увидеть, что на вызов exec() тратится больше ресурсов.
Процесс обладает множеством атрибутов. Часть из них уже была описана ранее, а остальные мы рассмотрим в последующих главах. В этом контексте встает два вопроса.
• Что происходит с этими атрибутами, когда процесс выполняет exec()?
• Какие атрибуты наследуются потомком при вызове fork()?
Ответы собраны в табл. 28.4. Столбец exec() показывает, какие атрибуты сохраняются во время вызова exec(). В столбце fork() перечисляются атрибуты, наследуемые (или в некоторых случаях разделяемые) потомком после вызова fork(). Все перечисленные ниже атрибуты (кроме тех, что обозначены как уникальные для Linux) входят в стандартную реализацию UNIX, а их обработка во время выполнения exec() и fork() соответствует стандарту SUSv3.
Таблица 28.4. Влияние вызовов exec() и fork() на атрибуты процесса
Атрибут процесса — exec() — fork() — Интерфейсы, затрагивающие атрибуты; дополнительные заметки
Адресное пространство процесса — Текстовый сегмент — Нет — Общий
Дочерний процесс разделяет текстовый сегмент с родителем — Сегмент стека — Нет — Да
Входные/выходные точки функций; alloca(), longjmp(), siglongjmp() — Сегменты данных и кучи — Нет — Да
brk(), sbrk() — Переменные среды — См. заметки — Да
putenv(), setenv(); непосредственное изменение environ. Перезаписываются вызовами execle() и execve(), остаются неизменными после вызовов exec()
Отображение в память
Нет
См. заметки
mmap() и munmap(). Флаг MAP_NORESERVE отображения наследуется во время выполнения fork(). Отображения, помеченные madvise(MADV_DONTFORK), не наследуются во время выполнения fork()
Блокировки памяти
Нет
Нет
mlock(), munlock()
Идентификаторы и привилегии процесса
Идентификатор процесса
Да
Нет
Идентификатор родительского процесса
Да
Нет
Идентификатор группы процесса
Да
Да
setpgid()
Идентификатор сессии
Да
Да
setsid()
Реальные идентификаторы
Да
Да
setuid(), setgid() и связанные с ними вызовы
Действующий и сохраненный идентификаторы
См. заметки
Да
setuid(), setgid() и связанные с ними вызовы. То, как exec() влияет на эти идентификаторы, объясняется в главе 9
Дополнительные групповые идентификаторы
Да
Да
setgroups(), initgroups()
Файлы, файловый ввод/вывод и каталоги
Дескрипторы открытых файлов
См. заметки
Да