Ответ 1
Теоретическая модель для программиста на уровне приложений показывает, что это так. Фактически, обычный процесс запуска (по крайней мере, в Linux 1.x, я считаю, что 2.x и 3.x оптимизированы, но похожи):
- Ядро создает контекст процесса (более или менее виртуальную машину)
- В контексте этого процесса он определяет сопоставление виртуальной памяти, которое отображает из адресов RAM в начало исполняемого файла
- Предполагая, что вы динамически связаны (по умолчанию/обычный), программа
ld.so
(например,/lib/ld-linux.so.2
), определенная в ваших заголовках программ, устанавливает сопоставление памяти для разделяемых библиотек - Ядро делает
jmp
в подпрограмме запуска вашей программы (для программы на C, это что-то вродеcrtprec80
, которая вызываетmain
). Так как он только установил сопоставление и не загрузил ни одной страницы (*), это приведет к сбою страницы с блока управления памятью процессора, который представляет собой прерывание (исключение, сигнал) для ядра. - Обработчик ошибок страницы ядра загружает в ОЗУ часть раздела вашей программы, включая часть, вызвавшую ошибку страницы.
- Как только ваша программа запускается, если она обращается к виртуальному адресу, на котором сейчас нет ОЗУ, возникают ошибки страницы и приводят к тому, что ядро временно приостанавливает выполнение программы, загружает страницу с диска и затем возвращает управление программе, Все это происходит "между инструкциями" и обычно не поддается определению.
- Когда вы используете
malloc
/new
, ядро создает страницы для чтения и записи ОЗУ (без файлов резервного копирования) и добавляет их в виртуальное адресное пространство. - Если вы сбросите Ошибка страницы, пытаясь получить доступ к ячейке памяти, которая не настроена в сопоставлениях виртуальной памяти, вы получаете сигнал нарушения сегментации (SIGSEGV), который обычно является фатальным.
- Когда в системе заканчивается физическое ОЗУ, страницы ОЗУ удаляются; если они являются копиями только что прочитанных на диске (например, исполняемый файл или файл общих объектов), они просто де-распределяются и перезагружаются из своего источника; если они читаются и записываются (например, память, которую вы создали "с помощью
malloc
), они записываются в файл (файл <файл подкачки = свопинг = виртуальная память на диске). Доступ к этим "освобожденным" страницам приводит к еще одной ошибке страницы, и они перезагружены.
В общем, хотя до тех пор, пока ваш процесс не станет больше, чем доступная оперативная память, и данные почти всегда значительно превышают исполняемый файл - вы можете смело притворяться, что вы одни в мире, и ни один из этих требований не вызывает пейджинга.
Итак: фактически, ядро уже запускает вашу программу во время ее загрузки (и, возможно, даже не загрузит некоторые страницы, если вы никогда не прыгаете в этот код/не ссылаетесь на эти данные).
Если ваш запуск особенно вялый, вы можете посмотреть на систему prelink
для оптимизации загрузки разделяемой библиотеки. Это уменьшает объем работы, который ld.so
должен сделать при запуске (между exec
вашей программы и main
вызывался, а также при первой библиотеке вызова подпрограмма).
Иногда связывание статически может повысить производительность программы, но при значительном расходе ОЗУ - поскольку ваши библиотеки не используются совместно, вы дублируете "ваш libc
" в дополнение к общему libc
который использует каждая другая программа, например, Это обычно полезно только во встроенных системах, где ваша программа работает более или менее на машине.
(*) На самом деле, ядро немного умнее и, как правило, предварительно загружает несколько страниц, чтобы уменьшить количество ошибок страницы, но теория такая же, независимо от оптимизации