Процесс загрузки Unix / Linux

Может ли кто-нибудь сказать мне, какой процесс операционной системы загружает файл ELF (Исполняемый и связанный формат) в ОЗУ?

Обычно пользователь сталкивается с тремя типами файлов ELF: .o-файлов, обычных исполняемых файлов и разделяемых библиотек. Хотя все эти файлы служат для разных целей, их файлы внутренней структуры очень похожи.

Одним из универсальных понятий среди всех типов файлов ELF (а также a.out и многих других исполняемых форматов файлов) является понятие раздела. Раздел представляет собой набор информации подобного типа. Каждая секция представляет часть файла. Например, исполняемый код всегда помещается в раздел, известный как .text ; все переменные данных, инициализированные пользователем, помещаются в раздел, известный как .data ; и неинициализированные данные помещаются в раздел, известный как .bss .

На самом деле, можно разработать исполняемый формат файла, где все смешалось (например, MS DOS ). Но разделение исполняемых файлов на разделы имеет важные преимущества. Например, как только вы загрузили исполняемые части исполняемого файла в память, эти ячейки памяти не должны меняться. На современных машинных архитектурах диспетчер памяти может отмечать части памяти только для чтения, так что любая попытка изменить местоположение памяти только для чтения приводит к тому, что программа умирает и сбрасывает ядро. Таким образом, вместо того, чтобы просто сказать, что мы не ожидаем изменения определенной ячейки памяти, мы можем указать, что любая попытка изменить место хранения только для чтения является фатальной ошибкой, указывающей на ошибку в приложении. При этом обычно вы не можете индивидуально настраивать статус только для чтения для каждого байта памяти, вместо этого вы можете индивидуально устанавливать защиту областей памяти, известных как страницы. В архитектуре i386 размер страницы составляет 4096 байт, поэтому вы можете указать, что адреса 0-4095 доступны только для чтения, а байты 4096 и выше, например, доступны для записи.

Учитывая, что мы хотим, чтобы все исполняемые части исполняемого файла в постоянной памяти и все изменяемые места памяти (такие как переменные) в записываемой памяти, оказалось наиболее эффективным сгруппировать все исполняемые части исполняемого файла в один раздел ( раздел .text ) и все изменяемые области данных вместе в другую область памяти (в дальнейшем называемую разделом .data ).

Еще одно различие делается между переменными данных, которые пользователь инициализировал, и переменными данных, которые пользователь не инициализировал. Если пользователь не указал начальное значение переменной, нет смысла тратить пространство в исполняемом файле для хранения значения. Таким образом, инициализированные переменные сгруппированы в раздел .data, а неинициализированные переменные сгруппированы в секцию .bss, что является особенным, потому что оно не занимает места в файле, а только указывает, сколько места требуется для неинициализированных переменных.

Когда вы запрашиваете ядро ​​для загрузки и запуска исполняемого файла, он начинает с просмотра заголовка изображения, чтобы узнать, как загрузить изображение. Он находит секцию .text в исполняемом файле, загружает его в соответствующие части памяти и маркирует эти страницы как доступные только для чтения. Затем он находит раздел .data в исполняемом файле и загружает его в адресное пространство пользователя, на этот раз в памяти чтения-записи. Наконец, он находит местоположение и размер секции .bss из заголовка изображения и добавляет соответствующие страницы памяти в адресное пространство пользователя. Несмотря на то, что пользователь не указал начальные значения переменных, помещенных в .bss, по соглашению ядро ​​инициализирует всю эту память до нуля.

Таким образом, вы видите, на самом деле ядро ​​выдает заказы для загрузки исполняемого файла в память. Текстовый раздел в результате таких вызовов загружается в память только для чтения, а раздел данных загружается в память чтения-записи.

Это зависит от ОС.

Например, в GNU Hurd исполняемый файл загружается сервером exec .

На более типичной монолитной ОС это делается путем:

  1. ядро отображает исполняемый файл и динамический компоновщик в памяти;

  2. динамический компоновщик разбивает общие объекты в памяти.

Ядро Linux хранится как файл ELF: этот загружается загрузчиком (например, GRUB).