Если при загрузке программы память по этому адресу недоступна, загрузчик должен переместить загруженную программу, чтобы отразить фактический адрес загрузки.
Не весь файл форматы поддерживают это:
G CC для 32-битных Windows добавит информацию, необходимую для загрузчика в случае динамических c библиотек (.dll
). Однако информация не добавляется в исполняемые файлы (.exe
), поэтому такой исполняемый файл должен быть загружен по фиксированному адресу.
Под Linux это немного сложнее; однако также невозможно загрузить многие (обычно более старые 32-разрядные) исполняемые файлы по разным адресам, в то время как библиотеки Dynami c (.so
) могут быть загружены по разным адресам.
Предположим У меня есть еще один исполняемый файл, который в настоящее время работает на моем компьютере, уже использующий пространство памяти между 400540
и 601040
...
Современные компьютеры (все 32-разрядные компьютеры x86) имеют MMU подкачки который используется в большинстве современных операционных систем. Это некая схема (обычно в ЦП), которая переводит адреса, видимые программным обеспечением, в адреса, видимые ОЗУ. В вашем примере 400540
может быть преобразовано в 1234000
, поэтому доступ к адресу 400540
фактически приведет к доступу к адресу 1234000
в ОЗУ.
Дело в том, что современные ОС используют разные конфигурации MMU для разные задачи. Таким образом, если вы снова запустите свою программу, используется другая конфигурация MMU, которая преобразует адрес 400540
, видимый программным обеспечением, в адрес 2345000
в ОЗУ. Обе программы, использующие адрес 400540
, могут работать одновременно, поскольку одна программа будет обращаться к адресу 1234000
, а другая - к адресу 2345000
в ОЗУ, когда программы обращаются к адресу 400540
.
Это означает, что некоторый адрес (например, 400540
) никогда не будет «уже занят», когда исполняемый файл загружен.
Адрес может уже использоваться, когда динамическая c библиотека (.so
/ .dll
) загружается, потому что эти библиотеки совместно используют память с исполняемым файлом.
... как решается, где запустить мой новый исполняемый файл linux?
Под Linux исполняемый файл будет загружен по фиксированному адресу, если он был связан таким образом, что его нельзя переместить на другой адрес. (Как уже говорилось: это типично для старых 32-битных файлов.) В вашем примере строка «Hello world» будет расположена по адресу 0x601040
, если ваш компилятор и компоновщик создали исполняемый файл таким образом.
Однако большинство 64-битных исполняемых файлов можно загрузить по другому адресу. Linux загрузит их на некоторый случайный адрес из соображений безопасности, что затрудняет атаку вирусов или других вредоносных программ на программу.
... чтобы стек мог выросла ниже текстового сегмента ...
Я никогда не видел такой схемы памяти ни в одной операционной системе:
И под Linux, и под Solaris стек располагался в конец адресного пространства (где-то около 0xBFFFFF00
), в то время как текстовый сегмент был загружен довольно близко к началу памяти (возможно, адрес 0x401000
).
.. . и куча может расти с конца данных, ...
предположим, что куча предыдущего приложения ползет вверх ...
Многие реализации с тех пор конец 1990-х больше не использует кучу. Вместо этого они используют mmap()
для резервирования новой памяти.
Согласно странице руководства brk()
, куча была объявлена как «устаревшая функция» в 2001 году, поэтому она не должна использоваться новыми
(Однако, по словам Питера Кордеса, malloc()
по-прежнему в некоторых случаях использует кучу.)
В отличие от «простых» операционных систем, таких как MS-DOS, Linux не позволяет вам «просто» использовать кучу, но вы должны вызвать функцию brk()
, чтобы сообщить Linux, сколько кучи вы хотите использовать.
Если программа использует кучу, и она использует кучу больше, чем доступно, функция brk()
возвращает код ошибки, а функция malloc()
просто возвращает NULL
.
Однако такая ситуация обычно возникает из-за того, что ОЗУ больше нет, и не потому, что куча перекрывается с какой-то другой областью памяти.
... в то время как стек только что запущенного linux вырос вниз до ...
Скоро будет конфликт / перекрытие адресов памяти. Что происходит, когда в конечном итоге происходит cla sh?
Действительно, размер стека ограничен.
Если вы используете слишком много стека, вы получаете «переполнение стека» .
Эта программа намеренно использует слишком много стека - просто чтобы увидеть, что произойдет:
.globl _start
_start:
sub $0x100000, %rsp
push %rax
push %rax
jmp _start
В случае операционной системы с MMU (например, Linux) ваш программа выдаст sh с сообщением об ошибке:
~$ ./example_program
Segmentation fault (core dumped)
~$
ИЗМЕНИТЬ / ДОБАВИТЬ
Находится ли стек для всех запущенных программ в «конце»?
В старых версиях Linux стек был расположен рядом (но не совсем в нем) с концом виртуальной памяти, доступной для программы: программы могли обращаться к диапазону адресов от 0
до 0xBFFFFFFF
в этих Linux версиях. Первоначальный указатель стека располагался около 0xBFFFFE00
. (Аргументы командной строки и переменные среды идут после стека.)
И это конец реальной физической памяти? Не перепутается ли тогда стек разных запущенных программ? У меня создалось впечатление, что весь стек и память программы остаются непрерывными в реальной физической памяти, ...
На компьютере, использующем MMU, программа никогда не видит физическую память:
Когда программа загружена, ОС будет искать в свободной области ОЗУ - возможно, она найдет что-то по физическому адресу 0xABC000
. Затем он настраивает MMU таким образом, чтобы виртуальные адреса 0xBFFFF000-0xBFFFFFFF
преобразовывались в физические адреса 0xABC000-0xABCFFF
.
Это означает: всякий раз, когда программа обращается к адресу 0xBFFFFE20
(например, используя push
операция), физический адрес 0xABCE20
в ОЗУ фактически осуществляется.
Программа вообще не может получить доступ к определенному физическому адресу.
Если у вас работает другая программа , MMU настроен таким образом, что адреса 0xBFFFF000-0xBFFFFFFF
преобразуются в адреса 0x345000-0x345FFF
при работе другой программы.
Итак, если одна из двух программ выполнит операцию push
и указатель стека 0xBFFFFE20
, будет доступен адрес 0xABCE20
в ОЗУ; если другая программа выполняет операцию push
(с тем же значением указателя стека), будет доступен адрес 0x345E20
.
Следовательно, стеки не будут смешиваться.
ОС без использования MMU, но с поддержкой многозадачности (например, Amiga 500 или ранние версии Apple Macintosh), конечно, не будет работать таким образом. Такие ОС используют специальные форматы файлов (а не ELF), которые оптимизированы для запуска нескольких программ без MMU. Компиляция программ для таких ОС намного сложнее, чем компиляция программ для Linux или Windows. И есть даже ограничения для разработчика программного обеспечения (пример: функции и массивы не должны быть слишком длинными).
Кроме того, каждая программа имеет свой собственный указатель стека, базовый указатель, регистры и т. Д. c.? Или в ОС есть только один набор этих регистров, который будет использоваться всеми программами?
(Предполагая одноядерный ЦП), ЦП имеет один набор регистров; и только одна программа может работать одновременно.
Когда вы запускаете несколько программ, ОС будет переключаться между программами. Это означает, что программа A выполняется (например) 1/50 секунды, затем программа B выполняется в течение 1/50 секунды, затем программа A выполняется в течение 1/50 секунды и так далее. Вам кажется, что программы выполняются одновременно.
Когда ОС переключается с программы A на программу B, она должна сначала сохранить значения регистров (программы A). Затем он должен изменить конфигурацию MMU. Наконец, он должен восстановить значения регистров программы B.