Когда запускается двоичный файл, он копирует все свои двоичные данные в память сразу? Могу ли я это изменить?

Копирует ли весь файл в память до его выполнения? Меня интересует этот вопрос и хочу изменить его каким-то другим способом. Я имею в виду, если двоичный файл имеет размер 100M (кажется невозможным), я могу запустить его, пока я копирую его в память. Это возможно?

Или вы могли бы рассказать мне, как увидеть, как он работает? Какие инструменты мне нужны?

Ответ 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 который использует каждая другая программа, например, Это обычно полезно только во встроенных системах, где ваша программа работает более или менее на машине.

(*) На самом деле, ядро немного умнее и, как правило, предварительно загружает несколько страниц, чтобы уменьшить количество ошибок страницы, но теория такая же, независимо от оптимизации

Ответ 2

Нет, он загружает только нужные страницы в память. Это запрос пейджинга.

Я не знаю инструмента, который действительно может показать это в режиме реального времени, но вы можете посмотреть на /proc/xxx/maps, где xxx - это PID вашего процесса.

Ответ 3

Пока вы задаете правильный вопрос, я не думаю, что вам нужно беспокоиться. Во-первых, двоичный код 100M не является невозможным. Во-вторых, системный загрузчик будет загружать страницы, которые ему нужны, из ELF (Исполняемый и Связываемый формат) в память, а также выполнять различные перемещения и т.д., Которые заставят его работать, если это необходимо. Таким же образом он будет загружать все свои необходимые общие библиотечные зависимости. Однако это не невероятно трудоемкий процесс и тот, который действительно не нуждается в оптимизации. Возможно, любая "оптимизация" будет иметь значительные накладные расходы, чтобы убедиться, что она не пытается использовать что-то, что не было загружено должным образом, и, возможно, будет менее эффективным.

Если вам интересно, что отображается, как говорит fge, вы можете проверить /proc/pid/maps. Если вы хотите посмотреть, как загружается программа, вы можете попробовать запустить программу с помощью strace, например:

strace ls

Это довольно многословие, но оно должно дать вам некоторое представление о вызовах mmap() и т.д.