Разница между fork (), vfork (), exec () и clone ()


197

Я пытался найти разницу между этими четырьмя в Google, и я ожидал, что по этому поводу будет огромное количество информации, но между четырьмя звонками действительно не было четкого сравнения.

Я попытался скомпилировать некий базовый взгляд на различия между этими системными вызовами и вот что я получил. Вся эта информация верна / я что-то упускаю?

Fork Вызов fork в основном создает дубликаты текущего процесса, практически идентичные (не все копируются, например, из-за ограничений ресурсов в некоторых реализациях, но идея состоит в том, чтобы создать максимально близкую копию).

Новый процесс (дочерний) получает другой идентификатор процесса (PID) и имеет PID старого процесса (родителя) в качестве родительского PID (PPID). Поскольку два процесса теперь работают точно в одном и том же коде, они могут определить, какой именно, по коду возврата fork - дочерний элемент получает 0, родительский получает PID дочернего элемента. Конечно, это все, если предположить, что вызов fork работает - если нет, дочерний элемент не создается, а родительский код получает код ошибки.

Vfork: Основное различие между vfork и fork заключается в том, что при создании нового процесса с помощью vfork () родительский процесс временно приостанавливается, и дочерний процесс может занимать адресное пространство родительского процесса. Это странное положение вещей продолжается до тех пор, пока дочерний процесс не завершится или не вызовет execve (), после чего родительский процесс продолжится.

Это означает, что дочерний процесс vfork () должен быть осторожен, чтобы избежать неожиданного изменения переменных родительского процесса. В частности, дочерний процесс не должен возвращаться из функции, содержащей вызов vfork (), и он не должен вызывать exit () (если ему нужно выйти, он должен использовать _exit (); на самом деле, это также верно для дочернего процесса нормальной вилки ()).

Exec :Вызов exec - это способ в основном заменить весь текущий процесс новой программой. Он загружает программу в текущее пространство процесса и запускает ее из точки входа. exec () заменяет текущий процесс исполняемым файлом, указанным функцией. Элемент управления никогда не возвращается к исходной программе, если не существует ошибки exec ().

Clone :Клон, как форк, создает новый процесс. В отличие от fork эти вызовы позволяют дочернему процессу совместно использовать часть своего контекста выполнения с вызывающим процессом, например пространство памяти, таблицу дескрипторов файлов и таблицу обработчиков сигналов.

Когда дочерний процесс создается с помощью clone, он выполняет функцию приложения fn (arg). (Это отличается от fork, где выполнение продолжается в дочернем элементе с точки исходного вызова fork.) Аргумент fn является указателем на функцию, которая вызывается дочерним процессом в начале его выполнения. Аргумент arg передается в функцию fn.

Когда возвращается функция функции fn (arg), дочерний процесс завершается. Целое число, возвращаемое fn, является кодом выхода для дочернего процесса. Дочерний процесс также может явно завершиться с помощью вызова exit (2) или после получения фатального сигнала.

Информация полученная форма:

Спасибо, что нашли время, чтобы прочитать это! :)


2
Почему vfork не должен вызывать exit ()? Или не вернуть? Разве exit () не использует _exit ()? Я тоже пытаюсь понять :)
LazerSharks

2
@Gnuey: потому что он потенциально (если он реализован не так fork(), как в Linux и, вероятно, во всех BSD), занимает адресное пространство своего родителя. Все, что он делает, кроме вызова execve()или _exit(), имеет большой потенциал, чтобы испортить родителя. В частности, обработчики exit()вызовов atexit()и другие «финализаторы», например: сбрасывает потоки stdio. Возвращение от vfork()ребенка потенциально (то же самое предостережение, что и раньше) испортит стек родителя.
ниндзя

Мне было интересно, что происходит с потоками родительского процесса; Все они клонированы или только поток, который вызывает forkсистемный вызов?
Мухаммед Джафар Машхади

@LazerSharks vfork создает потокоподобный процесс, в котором память распределяется без защиты от копирования при записи, поэтому работа со стеком может привести к сбою родительского процесса.
Jasen

Ответы:


159
  • vfork()это устаревшая оптимизация До хорошего управления памятью fork()делали полную копию памяти родителя, поэтому это было довольно дорого. поскольку во многих случаях за fork()ним следовал a exec(), который отбрасывает текущую карту памяти и создает новую, это было ненужным расходом. В наше время fork()не копирует память; он просто устанавливается как «копировать при записи», поэтому fork()+ exec()так же эффективен, как и vfork()+ exec().

  • clone()это системный вызов используется fork(). с некоторыми параметрами он создает новый процесс, с другими он создает поток. разница между ними заключается в том, какие структуры данных (пространство памяти, состояние процессора, стек, PID, открытые файлы и т. д.) используются совместно или нет.



22
vforkизбавляет от необходимости временно выделять гораздо больше памяти только для того, чтобы ее можно было выполнять exec, и это все же более эффективно, чем fork, даже если и не в такой степени. Таким образом, можно избежать необходимости перегружать память, просто чтобы огромная программа могла порождать дочерний процесс. Таким образом, не только повышение производительности, но может сделать его вообще возможным.
Дедупликатор

5
На самом деле, я воочию видел, как fork () далеко не дешев, когда у вас большой RSS. Я предполагаю, что это потому, что ядру все еще нужно скопировать все таблицы страниц.
Мартина Феррари

4
Он должен скопировать все таблицы страниц, установить копирование-запись-запись всей доступной памяти в обоих процессах , очистить TLB, а затем вернуть все изменения в родительский (и снова очистить TLB) exec.
Звол

3
vfork все еще полезен в cygwin (ядре, эмулирующем dll, которое работает поверх Windows от Microsoft). Cygwin не может реализовать эффективный форк, так как в базовой ОС его нет.
Ctrl-Alt-Delor

80
  • execve() заменяет текущий исполняемый образ другим, загруженным из исполняемого файла.
  • fork() создает дочерний процесс.
  • vfork()является исторически оптимизированной версией fork(), предназначенной для использования при execve()непосредственном вызове fork(). Оказалось, что он хорошо работает в не-MMU системах (где fork()не может работать эффективно) и при использовании fork()процессов с огромным объемом памяти для запуска небольшой программы (например, Java Runtime.exec()). POSIX стандартизировал, posix_spawn()чтобы заменить эти последние два более современных использования vfork().
  • posix_spawn()делает эквивалент a fork()/execve(), а также позволяет некоторую жонглирование между ними. Он должен заменить fork()/execve(), в основном для не-MMU платформ.
  • pthread_create() создает новую тему
  • clone()это специфичный для Linux вызов, который можно использовать для реализации чего угодно, от fork()до pthread_create(). Это дает много контроля. Вдохновленный на rfork().
  • rfork()это конкретный вызов Plan-9. Предполагается, что это будет общий вызов, обеспечивающий несколько степеней разделения между полными процессами и потоками.

2
Спасибо, что добавили больше информации, чем было запрошено, это помогло мне сэкономить время
Neeraj

5
План 9 такой дразнить.
JJ

1
Для тех, кто не может вспомнить, что означает MMU: «Блок управления памятью» - дальнейшее чтение в Википедии
mgarey

43
  1. fork()- создает новый дочерний процесс, который является полной копией родительского процесса. Дочерний и родительский процессы используют разные виртуальные адресные пространства, которые изначально заполнены одними и теми же страницами памяти. Затем, когда оба процесса выполняются, виртуальные адресные пространства начинают все больше и больше различаться, поскольку операционная система выполняет ленивое копирование страниц памяти, которые записываются любым из этих двух процессов, и назначает независимые копии измененных страниц память для каждого процесса. Этот метод называется копирование при записи (COW).
  2. vfork()- создает новый дочерний процесс, который является «быстрой» копией родительского процесса. В отличие от системного вызова fork(), дочерний и родительский процессы совместно используют одно и то же виртуальное адресное пространство. НОТА! Используя одно и то же виртуальное адресное пространство, и родитель, и потомок используют один и тот же стек, указатель стека и указатель инструкций, как в случае классического fork()! Чтобы предотвратить нежелательное вмешательство между родительским и дочерним процессами, которые используют один и тот же стек, выполнение родительского процесса приостанавливается до тех пор, пока дочерний процесс не вызовет exec()(создаст новое виртуальное адресное пространство и перейдет в другой стек) или_exit() (завершение выполнения процесса). ). vfork()является оптимизацией fork()для модели "fork-and-exec". Это может быть выполнено в 4-5 раз быстрее, чем fork(), потому что в отличие отfork()(даже с учетом COW), реализация vfork()системного вызова не включает создание нового адресного пространства (выделение и настройка новых каталогов страниц).
  3. clone()- создает новый дочерний процесс. Различные параметры этого системного вызова указывают, какие части родительского процесса должны быть скопированы в дочерний процесс, а какие будут разделены между ними. В результате этот системный вызов может использоваться для создания всех видов исполняемых объектов, начиная с потоков и заканчивая совершенно независимыми процессами. Фактически, clone()системный вызов является базой, которая используется для реализации pthread_create()и всех семейств fork()системных вызовов.
  4. exec()- сбрасывает всю память процесса, загружает и анализирует указанный исполняемый файл, устанавливает новый стек и передает управление точке входа загруженного исполняемого файла. Этот системный вызов никогда не возвращает управление вызывающей стороне и служит для загрузки новой программы в уже существующий процесс. Этот системный вызов вместе с fork()системным вызовом образуют классическую модель управления процессами UNIX, которая называется «fork-and-exec».

2
Обратите внимание, что требования BSD и POSIX для vforkнастолько слабы, что было бы законно создать vforkсиноним fork(а POSIX.1-2008 полностью исключает vforkиз спецификации). Если вам случится протестировать ваш код в системе, которая их синонимизирует (например, большинство BSD после 4.4, кроме NetBSD, ядра Linux до 2.2.0-pre6 и т. Д.), Он может работать, даже если вы нарушаете vforkконтракт, а затем взорвитесь если вы запустите его в другом месте. Некоторые из тех, кто имитирует это с помощью fork(например, OpenBSD), все еще гарантируют, что родитель не возобновит работу до тех пор, пока ребенок execили дети не будут _exit. Это смехотворно непереносимо.
ShadowRanger

2
Что касается последнего предложения вашего 3-го пункта: я заметил, что в Linux используется strace: хотя обертка glibc для fork () вызывает системный вызов clone, обертка для vfork () вызывает системный
вызов

7

Все fork (), vfork () и clone () вызывают do_fork () для реальной работы, но с разными параметрами.

asmlinkage int sys_fork(struct pt_regs regs)
{
    return do_fork(SIGCHLD, regs.esp, &regs, 0);
}

asmlinkage int sys_clone(struct pt_regs regs)
{
    unsigned long clone_flags;
    unsigned long newsp;

    clone_flags = regs.ebx;
    newsp = regs.ecx;
    if (!newsp)
        newsp = regs.esp;
    return do_fork(clone_flags, newsp, &regs, 0);
}
asmlinkage int sys_vfork(struct pt_regs regs)
{
    return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, &regs, 0);
}
#define CLONE_VFORK 0x00004000  /* set if the parent wants the child to wake it up on mm_release */
#define CLONE_VM    0x00000100  /* set if VM shared between processes */

SIGCHLD means the child should send this signal to its father when exit.

Для fork дочерний и родительский узел имеет независимую таблицу страниц VM, но, поскольку эффективность fork не будет на самом деле копировать какие-либо страницы, он просто установит все доступные для записи страницы только для чтения для дочернего процесса. Поэтому, когда дочерний процесс хочет что-то записать на этой странице, возникает исключение страницы, и ядро ​​выделяет новую страницу, клонированную со старой страницы с разрешением на запись. Это называется «копировать при записи».

Для vfork виртуальная память принадлежит ребенку и отцу - именно поэтому отец и ребенок не могут бодрствовать одновременно, поскольку они будут влиять друг на друга. Таким образом, отец будет спать в конце do_fork () и просыпаться, когда дочерний вызов вызывает exit () или execve (), с тех пор ему будет принадлежать новая таблица страниц. Вот код (в do_fork ()), который спит отец.

if ((clone_flags & CLONE_VFORK) && (retval > 0))
down(&sem);
return retval;

Вот код (в mm_release (), вызываемый exit () и execve ()), который пробуждает отца.

up(tsk->p_opptr->vfork_sem);

Для sys_clone () это более гибко, так как вы можете ввести в него любые clone_flags. Поэтому pthread_create () вызывает этот системный вызов со многими clone_flags:

int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGNAL | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | CLONE_SYSVSEM);

Резюме: fork (), vfork () и clone () создадут дочерние процессы с различным монтированием ресурса совместного использования с родительским процессом. Мы также можем сказать, что vfork () и clone () могут создавать потоки (на самом деле они являются процессами, поскольку имеют независимую task_struct), поскольку они совместно используют таблицу страниц VM с родительским процессом.


-4

в fork () дочерний или родительский процесс будет выполняться на основе выбора процессора. Но в vfork (), конечно, дочерний процесс будет выполняться первым. после того, как дочерний процесс прекращен, родитель выполнит.


3
Неправильно. vfork()можно просто реализовать как fork().
ниндзял

после AnyFork () не определено, кто запускает первого родителя / потомка.
AjayKumarBasuthkar

5
@Raj: У вас есть некоторые концептуальные недоразумения, если вы думаете, что после разветвления существует неявное понятие серийного порядка. Форкинг создает новый процесс, а затем возвращает управление обоим процессам (каждый из которых возвращает разные pid) - операционная система может запланировать параллельный запуск нового процесса, если такая вещь имеет смысл (например, несколько процессоров). Если по какой-то причине вам нужно, чтобы эти процессы выполнялись в определенном последовательном порядке, вам нужна дополнительная синхронизация, которую не обеспечивает разветвление; честно говоря, вы, вероятно, даже не хотели бы развилки в первую очередь.
Андон М. Коулман,

На самом деле @AjayKumarBasuthkar и @ninjalj, вы оба не правы. с vfork(), ребенок бежит первым. Это на страницах руководства; казнь родителей приостанавливается до тех пор, пока ребенок не умрет или не умрет exec. И ниндзя посмотрите исходный код ядра. Нет способа реализовать vfork()это, fork()потому что они передают разные аргументы do_fork()внутри ядра. Тем не менее, вы можете реализовать vforkс помощью cloneсистемного вызова
Зак

@ZacWimer: см. Комментарий ShadowRanger к другому ответу stackoverflow.com/questions/4856255/… Старый Linux действительно их синонимизировал, как, очевидно, BSD, отличные от NetBSD (который, как правило, переносится на многие системы, отличные от MMU). С man-страницы Linux: В 4.4BSD это стало синонимом для fork (2), но NetBSD представил его снова; см. ⟨netbsd.org/Documentation/kernel/vfork.html⟩ . В Linux он был эквивалентен fork (2) до версии 2.2.0-pre6 или около того.
ниндзя
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.