Как копирование при записи в fork () обрабатывает несколько форков?

23

Согласно Википедии (что может быть не так)

Когда выполняется системный вызов fork (), создается копия всех страниц, соответствующих родительскому процессу, загружаемая ОС в отдельную область памяти для дочернего процесса. Но это не нужно в определенных случаях. Рассмотрим случай, когда дочерний процесс выполняет execсистемный вызов "" (который используется для выполнения любого исполняемого файла из программы на C) или завершается очень скоро после fork(). Когда дочерний элемент требуется просто для выполнения команды для родительского процесса, нет необходимости копировать страницы родительского процесса, поскольку он execзаменяет адресное пространство процесса, который его вызвал, командой, которая должна быть выполнена.

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

Кажется, что когда один из процессов пытается записать на страницу, новая копия страницы выделяется и присваивается процессу, который вызвал ошибку страницы. Исходная страница впоследствии помечается как доступная для записи.

Мой вопрос: что произойдет, если fork()get вызывается несколько раз, прежде чем какой-либо процесс попытается записать на общую страницу?

ssgao
источник
Википедия в этом случае права, просто более высокий уровень.
Диди Кохен
1
Да, копирование при записи - это ленивое копирование, дочерний процесс копирует страницу при попытке ее записи. Так что, в основном, после разветвления почти детская память делится с родительской. Однако перед выполнением любого из процессов каждый дочерний процесс все еще имеет некоторую личную память, измененную из родительского или нового выделения. Это означает, что даже без каких-либо действий разветвленный дочерний процесс имеет некоторую личную память. Мы можем проверить это с помощью pmap -XX PIDили cat /proc/PID/smap.
где23
Относительно - «Первоначальная страница помечается как пригодная для записи впоследствии.», Кому она будет принадлежать? Вот другой процесс, который не пытался написать это?
Адил
Это прекрасно. Давайте начнем преподавать это в детских садах
ed22

Ответы:

18

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

jlliagre
источник
Правильно. Дело в том, что это особый дочерний процесс, который выполняет копирование, если он пытается записать на общую страницу. Ни родитель, ни другие дети не должны знать об изменении, если оно сделано правильно.
Чарльз Стюарт
9
Дочерний процесс не такой уж особенный. И дочерний, и родительский процессы имеют один и тот же набор страниц только для чтения после разветвления. Что касается этих страниц, обработка страниц является симметричной.
Jlliagre
3

Поведение fork () зависит от того, имеет ли система * nix MMU или нет. В системе без MMU (например, в ранних PDP-11) системный вызов fork () копировал всю память родителя для каждого дочернего элемента. В * nix-системе на основе MMU ядро ​​помечает все не-стековые страницы как R / O и разделяет их между родителем и потомком. Затем, когда любой процесс выполняет запись на любую страницу, MMU прерывает попытку, ядро ​​затем выделяет доступную для записи страницу и обновляет таблицы страниц MMU, чтобы указывать на страницу, доступную для записи. Такое поведение копирования при записи обеспечивает ускорение, поскольку первоначально для каждого дочернего процесса необходимо выделять и клонировать только частный стек.

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

CyberFonic
источник
0

Когда система предварительно формирует форк, обычно (это может зависеть от реализации) она также помечает страницы как доступные только для чтения и отмечает родительский процесс как мастер этих страниц.
При попытке записи на эти страницы происходит сбой страницы, и ОС берет верх, копируя весь список страниц или только измененные (опять же, в зависимости от реализации), поэтому процесс записи будет иметь доступную для записи копию.
Когда есть несколько процессов, разветвленных от одного и того же, когда «главный» процесс записывает в свою память, другие процессы копируют свои эквивалентные страницы.

Диди Коэн
источник
какая система это делает? Linux использует реализацию копирования при записи
brauliobo 10.10.14
Вот как работает копирование при записи ...
Диди Кохен
3
@DavidKohen это не то, как копирование при записи работает в любой версии, о которой я когда-либо слышал. Там нет "мастер" процесса. Если какой-либо отдельный процесс записывает общие страницы, его копия превращается в частную, а все остальные процессы продолжают делиться ею.
Celada
1
Я думаю, что Дэвид Коэн прав в какой-то момент. Это один из способов реализовать копирование при записи. Суть в том, что при этой маркировке запись на эту страницу вызовет обработчик ошибок страницы, который затем предпримет соответствующее действие, то есть копирование при записи. К сожалению, эта деталь (которая будет зависеть от системы) в основном не имеет значения для вопроса. Имейте в виду, что CoW имеет два измерения: одно, видимое для процесса, и то, как ядро ​​может его реализовать.
0xC0000022L