Теряет ли родительский процесс возможность записи во время копирования при записи?

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


person GamefanA    schedule 13.03.2018    source источник


Ответы (2)


Правильно, если какой-либо процесс записывает страницу COW, он вызывает сбой страницы.

В обработчике ошибок страницы, если страница предполагается доступна для записи, он выделяет новую физическую страницу и выполняет memcpy(newpage, shared_page, pagesize), а затем обновляет таблицу страниц любого процесса, в котором произошел сбой, для сопоставления новой страницы с этим виртуальным адресом. . Затем возвращается в пользовательское пространство для повторного запуска инструкции сохранения.

Это выигрыш для чего-то вроде fork, потому что один процесс обычно делает execve системный вызов сразу после касания обычно одной страницы (памяти стека). execve уничтожает все сопоставления памяти для этого процесса, эффективно заменяя его новым процессом. Родитель снова имеет единственную копию каждой страницы. (За исключением страниц, которые были уже копированием при записи, например, память, выделенная с помощью mmap, обычно COW-отображается на одну физическую страницу нулей, поэтому операции чтения могут попадать в кэш L1d).

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

person Peter Cordes    schedule 13.03.2018
comment
Как это будет работать, если родитель после изменения некоторых данных решит создать второго ребенка? Если родитель теряет право собственности на свои данные, то теперь будет два сегмента памяти только для чтения. Один после создания первого ребенка и второй после создания второго ребенка. И теперь ОС должна будет выделить третий доступный для записи сегмент, чтобы родительский элемент выполнял копирование при записи, если он снова решит изменить свои данные. Это звучит неправильно, поскольку кажется ужасной тратой ресурсов. - person GamefanA; 13.03.2018
comment
@ user3769877: как только execve запускается в дочернем процессе, все сопоставления памяти стираются, поэтому счетчик ссылок на всех родительских страницах возвращается к 1. (если они уже не были совместно использованы). Дочерняя копия любых страниц, которые COW фактически скопировала, освобождается execve, независимо от того, была ли это родительская или дочерняя, вызвавшая копирование. - person Peter Cordes; 13.03.2018
comment
Но что, если exec никогда не вызывается. То есть мы просто вызываем fork один раз, меняем некоторые данные в родительском элементе, затем вызываем fork второй раз, изменяем данные в родительском элементе, вызываем fork в третий раз ... и так далее, и тому подобное. Как будет работать копирование на запись в этом случае? - person GamefanA; 13.03.2018
comment
@ user3769877: Тогда все страницы будут общими, за исключением нескольких страниц, которые были изменены. Каждый процесс, который изменяет страницу, будет иметь свою собственную копию этой страницы, как если бы ничто не было опубликовано. Вы экономите много памяти по сравнению с копированием всего на форк, например если bash создает подоболочку для такой команды, как foo; (echo 1234 > file)& bar, подоболочка, которая запускает echo > file, не изменяет большую часть своих страниц, поэтому стоит несколько дополнительных ошибок страниц, чтобы избежать большого объема работы по копированию. (Это было особенно верно, когда была разработана Unix, когда копирование памяти было медленным по сравнению с pagefault) - person Peter Cordes; 13.03.2018

Некоторые реализации UNIX совместно используют текст программы между ними, поскольку его нельзя изменить. В качестве альтернативы, дочерний элемент может совместно использовать всю родительскую память, но в этом случае память используется совместно copy-on-write, что означает, что всякий раз, когда кто-либо из двух хочет изменить часть памяти, этот фрагмент памяти сначала явно копируется, чтобы убедиться, что изменение происходит в частной области памяти.

Взято из: Современные операционные системы (4-е издание), Таненбаум

введите описание изображения здесь

person snr    schedule 11.03.2019