Как указывал Мэдс, для того, чтобы отлавливать большинство обращений через нулевые указатели, Unix-подобные системы стремятся сделать страницу с нулевым адресом «не отображенной». Таким образом, доступ немедленно вызывает исключение CPU, другими словами, сегфоут. Это гораздо лучше, чем позволить приложению стать мошенником. Однако таблица векторов исключений может находиться по любому адресу, по крайней мере, на процессорах x86 (для этого есть специальный регистр, загруженный с кодом операции lidt
).
Адрес отправной точки является частью набора соглашений, которые описывают, как распределяется память. Компоновщик, когда он создает исполняемый двоичный файл, должен знать эти соглашения, поэтому они вряд ли изменятся. По сути, для Linux соглашения о расположении памяти унаследованы от самых первых версий Linux, в начале 90-х годов. Процесс должен иметь доступ к нескольким областям:
- Код должен находиться в диапазоне, который включает начальную точку.
- Должен быть стек.
- Должна быть куча, предел которой увеличивается при системных вызовах
brk()
и sbrk()
.
- Должно быть место для системных вызовов
mmap()
, включая загрузку общей библиотеки.
В настоящее время куча, куда идет malloc()
, поддерживается mmap()
вызовами, которые получают куски памяти по любому адресу, который ядро считает подходящим. Но в прежние времена Linux был похож на предыдущие Unix-подобные системы, и его куча требовала большой площади в одном непрерывном блоке, которая могла увеличиваться в сторону увеличения адресов. Таким образом, каким бы ни было соглашение, оно должно было заполнять код и укладываться в младшие адреса и передавать каждый кусок адресного пространства после заданной точки в кучу.
Но есть также стек, который обычно довольно мал, но в некоторых случаях может значительно увеличиться. Стек растет, и когда стек заполнен, мы действительно хотим, чтобы процесс предсказуемо завершился сбоем, а не перезаписывал некоторые данные. Таким образом, для стека должна была быть широкая область с нижней границей этой области, не отображенной страницей. И вот! На нулевом адресе есть несопоставленная страница, чтобы перехватить нулевой указатель. Следовательно, было определено, что стек получит первые 128 МБ адресного пространства, за исключением первой страницы. Это означает, что код должен идти после этих 128 МБ по адресу, аналогичному 0x080xxxxx.
Как указывает Майкл, "потеря" 128 МБ адресного пространства не была большой проблемой, потому что адресное пространство было очень большим в отношении того, что можно было фактически использовать. В то время ядро Linux ограничивало адресное пространство для одного процесса до 1 ГБ, что превышало максимально 4 ГБ, разрешенное оборудованием, и это не считалось большой проблемой.