Как выполняется распределение стека в Linux?

Охраняет ли ОС фиксированное количество действительного виртуального пространства для стека или что-то еще? Могу ли я создать переполнение стека только с помощью больших локальных переменных?

Я написал небольшую программу на C чтобы проверить мое предположение. Он работает на X86-64 CentOS 6.5.

 #include <string.h> #include <stdio.h> int main() { int n = 10240 * 1024; char a[n]; memset(a, 'x', n); printf("%x\n%x\n", &a[0], &a[n-1]); getchar(); return 0; } 

Запуск программы дает &a[0] = f0ceabe0 и &a[n-1] = f16eabdf

На картах proc отображается стек: 7ffff0cea000-7ffff16ec000. (10248 * 1024B) 7ffff0cea000-7ffff16ec000. (10248 * 1024B)

Затем я попытался увеличить n = 11240 * 1024

Запуск программы дает &a[0] = b6b36690 и &a[n-1] = b763068f

На картах proc отображается стек: 7fffb6b35000-7fffb7633000. (11256 * 1024B) 7fffb6b35000-7fffb7633000. (11256 * 1024B)

ulimit -s печатает 10240 на моем ПК.

Как вы можете видеть, в обоих случаях размер стека больше, чем дает ulimit -s . И стек растет с большей локальной переменной. Верх стека как-то 3-5kB больше off &a[0] (AFAIK – красная зона – 128B).

Итак, как распределяется эта карта стека?

3 Solutions collect form web for “Как выполняется распределение стека в Linux?”

Похоже, что предел памяти стека не выделен (во всяком случае, он не может с неограниченным стеком). https://www.kernel.org/doc/Documentation/vm/overcommit-accounting говорит:

Рост стека языка C подразумевает неявное mremap. Если вы хотите получить абсолютные гарантии и приблизиться к краю, вы ДОЛЖНЫ вырезать свой стек на самый большой размер, который, по вашему мнению, вам понадобится. Для обычного использования стека это не имеет большого значения, но это угловой случай, если вы действительно очень заботитесь

Однако mmapping стека будет целью компилятора (если у него есть опция для этого).

EDIT: после некоторых тестов на машине x84_64 Debian я обнаружил, что стек растет без системного вызова (согласно strace ). Таким образом, это означает, что ядро ​​растет автоматически (это означает, что «неявное» означает выше), т. mremap Без явного mmap / mremap из процесса.

Было довольно сложно найти подробную информацию, подтверждающую это. Я рекомендую Понимание Linux Virtual Memory Manager Мел Горман. Я полагаю, что ответ приведен в разделе 4.6.1 « Обработка ошибки страницы» , за исключением «Регион недействителен, но находится рядом с расширяемым регионом, таким как стек», и соответствующее действие «Развернуть регион и выделить страницу». См. Также D.5.2. Расширение стека .

Другие справки об управлении памятью Linux (но почти ничего о стеке):

  • Вопросы по памяти
  • Что каждый программист должен знать о памяти Ульриха Дреппера

EDIT 2: эта реализация имеет недостаток: в угловых случаях столкновение с кучей стека не может быть обнаружено даже в том случае, когда стек будет больше предела! Причина в том, что запись в переменной в стеке может закончиться в выделенной памяти кучи, и в этом случае нет ошибки страницы, и ядро ​​не может знать, что стек необходимо расширить. См. Мой пример в обсуждении Бесшумное столкновение стоп-кучек под GNU / Linux, которое я начал в списке gcc-help. Чтобы этого избежать, компилятору необходимо добавить код при вызове функции; это можно сделать с помощью -fstack-check для GCC (подробности см. в -fstack-check Ян Лэнса Тейлора и справочной странице GCC).

Ядро Linux 4.2

  • mm / mmap.c # acct_stack_growth решает, будет ли он segfault или нет. Он использует rlim[RLIMIT_STACK] который соответствует POSIX gerlimit(RLIMIT_STACK)
  • arch / x86 / mm / fault.c # do_page_fault – это обработчик прерываний, который запускает цепочку, которая заканчивается вызовом acct_stack_growth
  • arch / x86 / entry / entry_64.S устанавливает обработчик ошибок страницы. Вы должны знать немного о подкачке, чтобы понять эту часть: https://stackoverflow.com/questions/18431261/how-does-x86-paging-work

Минимальная тестовая программа

Затем мы можем протестировать его с минимальной 64-разрядной программой NASM:

 global _start _start: sub rsp, 0x7FF000 mov [rsp], rax mov rax, 60 mov rdi, 0 syscall 

Убедитесь, что вы отключили ASLR и удалили переменные окружения, так как они будут идти в стек и занимать место:

 echo 0 | sudo tee /proc/sys/kernel/randomize_va_space env -i ./main.out 

Предел где-то чуть ниже моего ulimit -s (8MiB для меня). Похоже, это связано с тем, что дополнительные данные, указанные в System V, изначально помещаются в стек в дополнение к среде: https://stackoverflow.com/questions/3683144/linux-64-command-line-parameters-in-assembly

Если вы серьезно относитесь к этому, подготовьте образ initrd, который сразу начнет нашу небольшую тестовую программу как / init, а затем запустит ее с помощью QEMU + GDB .

Связанный:

По умолчанию максимальный размер стека составляет 8 МБ на процесс,
но его можно изменить с помощью ulimit :

Отображение значения по умолчанию в kB:

 $ ulimit -s 8192 

Установите неограниченное количество:

ulimit -s unlimited

влияя на текущую оболочку и подоболочки и их дочерние процессы.
( ulimit – встроенная команда оболочки)

Вы можете показать фактический диапазон адресов стека, который используется с:
cat /proc/$PID/maps | grep -F '[stack]'
на Linux.

Linux и Unix - лучшая ОС в мире.