Страницы

Поиск по вопросам

четверг, 16 мая 2019 г.

Что происходит с памятью при вызове exec() на уровне ядра?

Вопрос теоретический. fork() делает копию процесса. Известно, что родительский процесс имеет собственное адресное пространство. Дочерний процесс, фактически, до какого-то момента может работать с адресным пространством своего родителя. Этот момент - запись в адресное пространство. Таким образом дочерний процесс в режиме "только чтение" может в полной мере руководствоваться родительским адресным пространством. Если производится запись, то адресное пространство копируется (copy-on-write [COW]). Возникает несколько вопросов:
Что будет со скопированным адресным пространством, если будет вызван exec()? Если после fork() вызвать exec(), то новый исполняемый код создает свое собственное адресное пространство? Что будет с таблицей страниц дочернего процесса после вызова exec()?


Ответ

Рассматривать что происходит с памятью при вызове exec() в отрыве от остальной подсистемой управления пямятью — неполноценно и описать этот процесс можно только в общих словах. А по всей данной подсистеме можно написать целую книгу (и несколько уже написаны), но я всё же попытаюсь выделить основные моменты.
Немного о структурах связанных с управлением памятью процесса
Кратко опишу назначение основных структур связанных с управлением памятью процесса.
Структуры связанные с управлением виртуальным АП.
task_struct — дескриптор процесса mm_struct — адресное пространство процесса vm_area_struct — отдельный сегмент памяти процесса со своим набором атрибутов (rwx), можно думать о нём как о неком регионе отображённом в память mmap ()'ом: он может быть и частью файла (исполняемого или обычного) и простым анонимным участком памяти. Список можно посмотреть, например в /proc//maps
Структуры связанные с трансляцией адресов и физической памятью:
pgd_t и pmd_t (а также p4d и pud) — системно-зависимые типы для записей в глобальной директории страниц (Page Global Directory) и промежуточной директории страниц (Page Middle Directory), если таковая есть (на простом x86 таковой нет). p4d и pud — это дополнительные типы в новых ядрах для пятиуровневой и четырёхуровневой адресации. pte_t — системно-зависимый тип Записи в таблице страниц (Page Table Entries). На x86 это, как и pgd_t 32-битный тип с аналогичным названием struct page — системно-независимая структура представляющая страницу физической памяти. Все они постоянно хранятся в памяти в массиве mem_map
Если кратко, то эти структуры взаимосвязаны следующим образом:
task_struct.mm ссылается на mm_struct, связанный с процессом. На один mm_struct может ссылаться несколько task_struct (так в частности реализованы потоки) mm_struct.mmap в свою очередь ссылается на двусвязный список vm_area_struct, связанных с процессом. mm_struct.pgd хранит адрес глобальной директории страниц pgd_t связанной с процессом. На x86 он фактически загружается в CR3 при смене контекста. vm_area_struct.mm — указатель на mm_struct к которому принадлежит данный сегмент. vm_area_struct.next и vm_area_struct.prev — указатели на предыдущий и следующий элемент в двусвязном списке. Для pte_t и pgd_t можно получить соответствующую структуру struct page с помощью макросов и pte_page() и pgd_page() соответственно.
Немного о том, как работает COW
При создании нового процесса fork ()'ом цикл проходится по всем связанным с ним vm_area_struct и помечает все доступные на запись страницы (pte_t), доступными только на чтение. А в соответствующей struct page увеличивается счётчик ссылок на страницу.
Когда процесс пытается записать в такую страницу, происходит прерывание, далее обработчик (спустившись на несколько функций по стеку вызовов) определяет, что хотя прерывание произошло на странице на которой запрещена запись (согласно pte_t), эта страница относится к сегменту, в котором она разрешена (согласно vm_area_struct). Таким образом определяется, что это одна из COW-страниц и происходит копирование. После чего уменьшается счётчик ссылок исходной страницы (в struct page) и, если он достиг нуля, снова разрешается запись в неё.
Что происходит с адресным пространством при execve()
Сначала exec() создаёт новое АП (mm_struct) частично его инициализирует (например создаёт стек), А потом пробует подсунуть файл поочерёдно каждому из модулей поддержки форматов (например ELF) пока один из них не сможет его загрузить. Модуль в свою очередь сначала проводит частичную проверку формата и также частичную инициализацию АП, а затем, когда убедится, что формат выбран правильно и скорей всего удастся загрузить данный файл, происходит подмена старого АП новым и последующая подчистка старого. В частности она включает:
Уменьшение количества пользователей АП (mm_struct.users), если оно достигло нуля, то:
В цикле обходятся все vm_area_struct и для каждой происходит рекурсивная итерация по каталогу страниц в результате счётчики ссылок всех ассоциированных страниц (struct page) уменьшаются аналогично тому как это было описано для COW. После этого в другом цикле обходятся все vm_area_struct и записываются несохранённые данные из грязных страниц mmap-файлов. Уменьшается количество ссылок на АП (mm_struct.count) и если оно достигло нуля (кроме процессов структура может использоваться другими подсистемами ядра), то она полностью удаляется
В случае успешного завершения модуль формата продолжит инициализацию АП, а затем произойдёт передача управления процессу.
Стоит заметить, что операция подмены АП(вызов flush_old_exec()) — это точка невозвращения, т.е. после оной exec () уже не сможет вернуть одну из документированных ошибок; и в случае сбоя весь процесс будет аварийно завершён по сигналу, например, SIGSEGV
Односложные ответы на конкретные вопросы
Что будет со скопированным адресным пространством, если будет вызван exec()?
Оно будет замещено другим, созданным exec ()
Если после fork() вызвать exec(), то новый исполняемый код создает свое собственное адресное пространство?
Да.
Что будет с таблицей страниц дочернего процесса после вызова exec()?
Она также будет замещена новой. Страницы не используемые другими будут добавлены в список свободных.
Дальнейшее чтение:
Understanding The Linux Virtual Memory Manager — Книга описывает довольно старое ядро, но отличия в основном косметические, а основные принципы не изменились.

Комментариев нет:

Отправить комментарий