Ответ
Оба вызова используются для создания нового процесса, но с фундаментально разной семантикой и оптимизациями.
fork() — создает почти полную копию родительского процесса (копирует адресное пространство, таблицу файловых дескрипторов и т.д.). Для оптимизации используется механизм Copy-On-Write (COW): физические страницы памяти дублируются только при попытке записи в них. Это стандартный и безопасный способ создания процессов.
vfork() — создает новый процесс, но не копирует адресное пространство родителя. Дочерний процесс выполняется в том же адресном пространстве, что и родитель, и родительский процесс приостанавливается, пока дочерний не вызовет exec() или _exit(). Это историческая оптимизация для систем, где fork() с полным копированием был очень дорогим.
Ключевые различия:
| Аспект | fork() |
vfork() |
|---|---|---|
| Адресное пространство | Копируется (с COW). | Разделяется с родителем. |
| Выполнение родителя | Продолжается параллельно с дочерним. | Блокируется до завершения дочернего. |
| Безопасность | Безопасен. Изменения в памяти одного процесса не влияют на другой. | Опасен. Запись в память дочерним процессом может повредить состояние родителя. |
| Производительность | Быстрее, чем полное копирование (благодаря COW), но все равно имеет накладные расходы. | Чрезвычайно быстр, так как не требует копирования таблиц страниц. |
| Типичное применение | Общий случай создания процессов (например, в веб-серверах, демонах). | Строго для сценария vfork() → exec(), когда нужно максимально быстро запустить новую программу. |
Пример кода, демонстрирующий правильное использование vfork():
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
int main() {
pid_t pid = vfork(); // Используем vfork
if (pid == 0) {
// Дочерний процесс: Родитель заблокирован.
// НИКОГДА не модифицируйте глобальные/локальные переменные здесь!
// Сразу выполняем новую программу.
execlp("/bin/ls", "ls", "-l", NULL);
// Если execlp fails, нужно использовать _exit(), а не exit()!
// exit() сбрасывает буферы stdio и может сломать состояние родителя.
_exit(EXIT_FAILURE);
} else if (pid > 0) {
// Родительский процесс: продолжает работу только после exec/_exit в дочернем.
printf("Parent process resumed. Child PID: %dn", pid);
} else {
perror("vfork failed");
}
return 0;
}
Современный контекст: В современных ядрах Linux fork() с COW настолько оптимизирован, что vfork() практически утратил свое преимущество в скорости для большинства задач. Фактически, во многих реализациях vfork() — это просто fork() с гарантией, что родитель будет заблокирован. Я использую fork() для всех стандартных случаев, а vfork() рассматриваю как устаревшую, потенциально опасную конструкцию, которую стоит применять только в очень специфичных embedded-системах или при написании низкоуровневого системного ПО, где каждый цикл процессора на счету.