Як працює розподіл стеків в Linux?


18

Чи ОС залишає фіксовану кількість дійсного віртуального простору для стека чи чогось іншого? Чи можу я створити переповнення стека лише за допомогою великих локальних змінних?

Я написав невелику 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)

Потім я спробував збільшити n = 11240 * 1024

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

Карти proc показує стек: 7fffb6b35000-7fffb7633000. (11256 * 1024B)

ulimit -sвідбитки 10240в моєму ПК.

Як бачите, в обох випадках розмір стека більший, ніж ulimit -sдає. І стек росте з більшою локальною змінною. Верхня частина стека якась на 3-5 кБ більше &a[0](AFAIK червона зона - 128B).

Отже, як ця карта стека виділяється?

Відповіді:


14

Здається, що обмеження пам'яті стека не виділено (все одно, з необмеженим стеком він не міг). https://www.kernel.org/doc/Documentation/vm/overcommit-accounting говорить:

Зростання стека мови C робить неявну мережеву карту. Якщо ви хочете абсолютних гарантій і пробігаєтеся близько до краю, ОБОВ'ЯЗКОВО зігнути ваш стек для найбільшого розміру, який, на вашу думку, знадобиться. Для типового використання стека це не має великого значення, але це дуже важливий випадок, якщо вам дійсно все одно

Однак мета копіювання стека була б метою компілятора (якщо він має для цього варіант).

EDIT: Після деяких тестів на машині Debian x84_64 я виявив, що стек росте без будь-якого системного виклику (відповідно strace). Отже, це означає, що ядро ​​виростає його автоматично (це те, що "неявна" означає вище), тобто без явного mmap/ mremapз процесу.

Було досить важко знайти детальну інформацію, яка підтверджує це. Я рекомендую розуміти Менеджер віртуальної пам’яті Linux Мел Горман. Я припускаю, що відповідь міститься в Розділі 4.6.1 Поводження з помилкою сторінки , за винятком "Регіон недійсний, але знаходиться поруч з розширюваною областю, як стек" та відповідною дією "Розгорніть регіон та розподіліть сторінку". Див. Також D.5.2 Розширення стека .

Інші посилання про управління пам’яттю Linux (але майже нічого про стек):

EDIT 2: Ця реалізація має недолік: у кутових випадках зіткнення стека-купи може бути не виявлено, навіть у випадку, коли стек буде перевищувати межу! Причина полягає в тому, що запис у змінну в стеку може закінчитися виділеною пам’яттю купи, у цьому випадку немає помилок сторінки і ядро ​​не може знати, що стек потрібно розширити. Дивіться мій приклад в обговоренні Silent зіткнення стека-купи під GNU / Linux, який я почав у списку довідки gcc. Щоб уникнути цього, компілятору потрібно додати код під час виклику функції; це можна зробити -fstack-checkза допомогою GCC (детальніше див. відповідь Ian Lance Taylor та сторінку GCC).


Це здається правильною відповіддю на моє запитання. Але це мене більше бентежить. Коли буде запущений дзвінок mremap? Чи буде це вбудована в програму sscall?
Амос

@amos Я припускаю, що виклик mremap буде ініційовано, якщо потрібно, щоб був виклик функції або коли виклик alloca ().
vinc17

Напевно, було б хорошою ідеєю згадати, що таке mmap для людей, які не знають.
Faheem Mitha

@FaheemMitha Я додав деяку інформацію. Тим, хто не знає, що таке mmap, дивіться відповіді на поширені запитання про пам'ять. Тут, для стека, це було б "анонімне відображення", щоб невикористаний простір не зайняв фізичної пам'яті, але, як пояснив Мел Горман, ядро ​​робить відображення (віртуальна пам'ять) і фізичне розподіл одночасно .
vinc17

1
@max Я спробував програму ОП з ulimit -sнаданням 10240, як, наприклад, за умовами ОП, і я отримую SIGSEGV, як очікувалося (саме це вимагає POSIX: "Якщо цей ліміт буде перевищено, для потоку генерується SIGSEGV. "). Я підозрюю помилку в ядрі ОП.
vinc17

6

Ядро Linux 4.2

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

Потім ми можемо протестувати його за допомогою мінімальної 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, що спочатку ставляться на стек, крім середовища: Параметри командного рядка Linux 64 у асамблеї | Переповнення стеку

Якщо ви серйозно ставитесь до цього, TODO зробить мінімальний initrd-образ, який починає писати з верху стека і знижується, а потім запускає його за допомогою QEMU + GDB . Покладіть dprintfна цикл, що друкує адресу стека, і точку перелому в acct_stack_growth. Це буде славно.

Пов'язані:


2

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

Показано за замовчуванням у кБ:

$ ulimit -s
8192

Встановити необмежений:

ulimit -s unlimited

що впливає на поточну оболонку та підшарки та їх дочірні процеси.
( ulimitце команда вбудованої оболонки)

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


Отже, коли програма завантажена поточною оболонкою, ОС зробить сегмент пам'яті ulimit -sKB, дійсний для програми. У моєму випадку це 10240 Кб. Але коли я оголошу локальний масив char a[10240*1024]і встановлюю a[0]=1, програма закінчується правильно. Чому?
Амос

Спробуйте також встановити останній елемент. І переконайтесь, що вони не оптимізовані.
vinc17

@amos Я думаю, що означає vinc17 - це те, що ти назвав область пам’яті, яка не помістилася б на стеці у вашій програмі , але оскільки ви фактично не отримуєте до неї доступ до тієї частини, яка не підходить , машина ніколи цього не помічає - це не навіть отримати цю інформацію .
Volker Siegel

@amos Спробуйте int n = 10240*1024; char a[n]; memset(a,'x',n);... seg вина.
goldilocks

2
@amos Отже, як ви бачите, a[]у вашому стеку 10 МБ не виділено. Компілятор, можливо, побачив, що не може бути рекурсивного виклику і здійснив спеціальне виділення, або щось інше, як переривчастий стек або деяке непряме.
vinc17
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.