Скажем, у нас есть некий родительский процесс с произвольным объемом данных, хранящихся в памяти, и мы используем fork
для порождения дочернего процесса. Я понимаю, что для того, чтобы ОС выполняла копирование при записи, на определенной странице в памяти, содержащей изменяемые нами данные, будет установлен бит только для чтения, и ОС будет использовать исключение, которое возникнет, когда дочерний элемент попытается для изменения данных, чтобы скопировать всю страницу в другую область памяти, чтобы ребенок получил свою копию. Я не понимаю, что если этот конкретный раздел в памяти помечен как доступный только для чтения, то родительский процесс, которому изначально принадлежали данные, не сможет их изменить. Так как же может работать вся эта схема? Теряет ли родительский элемент права собственности на свои данные, и нужно ли будет выполнять копирование при записи, даже если родитель сам пытается изменить данные?
Теряет ли родительский процесс возможность записи во время копирования при записи?
Ответы (2)
Правильно, если какой-либо процесс записывает страницу COW, он вызывает сбой страницы.
В обработчике ошибок страницы, если страница предполагается доступна для записи, он выделяет новую физическую страницу и выполняет memcpy(newpage, shared_page, pagesize)
, а затем обновляет таблицу страниц любого процесса, в котором произошел сбой, для сопоставления новой страницы с этим виртуальным адресом. . Затем возвращается в пользовательское пространство для повторного запуска инструкции сохранения.
Это выигрыш для чего-то вроде fork
, потому что один процесс обычно делает execve
системный вызов сразу после касания обычно одной страницы (памяти стека). execve
уничтожает все сопоставления памяти для этого процесса, эффективно заменяя его новым процессом. Родитель снова имеет единственную копию каждой страницы. (За исключением страниц, которые были уже копированием при записи, например, память, выделенная с помощью mmap
, обычно COW-отображается на одну физическую страницу нулей, поэтому операции чтения могут попадать в кэш L1d).
Умной оптимизацией будет fork
фактическое копирование страницы, содержащей вершину стека, но при этом ленивое выполнение COW для всех других страниц при условии, что дочерний процесс обычно сразу execve
сразу же и, таким образом, отбрасывает свои ссылки на все другие страницы. Тем не менее, временное переворачивание всех страниц в режим только для чтения и обратно требует недействительности TLB в родительском элементе.
execve
запускается в дочернем процессе, все сопоставления памяти стираются, поэтому счетчик ссылок на всех родительских страницах возвращается к 1. (если они уже не были совместно использованы). Дочерняя копия любых страниц, которые COW фактически скопировала, освобождается execve
, независимо от того, была ли это родительская или дочерняя, вызвавшая копирование.
- person Peter Cordes; 13.03.2018
bash
создает подоболочку для такой команды, как foo; (echo 1234 > file)& bar
, подоболочка, которая запускает echo > file
, не изменяет большую часть своих страниц, поэтому стоит несколько дополнительных ошибок страниц, чтобы избежать большого объема работы по копированию. (Это было особенно верно, когда была разработана Unix, когда копирование памяти было медленным по сравнению с pagefault)
- person Peter Cordes; 13.03.2018
Некоторые реализации UNIX совместно используют текст программы между ними, поскольку его нельзя изменить. В качестве альтернативы, дочерний элемент может совместно использовать всю родительскую память, но в этом случае память используется совместно
copy-on-write
, что означает, что всякий раз, когда кто-либо из двух хочет изменить часть памяти, этот фрагмент памяти сначала явно копируется, чтобы убедиться, что изменение происходит в частной области памяти.Взято из: Современные операционные системы (4-е издание), Таненбаум а>