Что такое «автоматическое расширение стека»?

13

getrlimit (2) имеет следующее определение на страницах руководства:

RLIMIT_AS Максимальный размер виртуальной памяти процесса (адресного пространства) в байтах. Это ограничение влияет на вызовы brk (2), mmap (2) и mremap (2), которые завершаются ошибкой ENOMEM при превышении этого предела. Также автоматическое расширение стека завершится неудачно (и сгенерирует SIGSEGV, который убивает процесс, если через sigaltstack (2) не было сделано доступного альтернативного стека). Поскольку значение является длинным, на машинах с 32-битной длиной это ограничение не должно превышать 2 ГиБ, или этот ресурс не ограничен.

Что подразумевается под «автоматическим расширением стека» здесь? Стек в среде Linux / UNIX растет по мере необходимости? Если да, каков точный механизм?

громко и ясно
источник

Ответы:

1

Да, стеки растут динамически. Стек находится в верхней части памяти, растущей вниз к куче.

--------------
| Stack      |
--------------
| Free memory|
--------------
| Heap       |
--------------
     .
     .

Куча растет вверх (всякий раз, когда вы делаете malloc), а стек увеличивается вниз, когда и когда вызываются новые функции. Куча присутствует чуть выше раздела BSS программы. Это означает, что размер вашей программы и то, как она распределяет память в куче, также влияют на максимальный размер стека для этого процесса. Обычно размер стека не ограничен (до тех пор, пока области кучи и стека не встретятся и / или не будут перезаписаны, что приведет к переполнению стека и SIGSEGV :-)

Это только для пользовательских процессов. Стек ядра всегда фиксирован (обычно 8 КБ).

Сантош
источник
«Обычно размер стека не ограничен», нет, обычно он ограничен 8Mb ( ulimit -s).
Eddy_Em
да, вы правы в большинстве систем. Вы проверяете команду оболочки ulimit, в этом случае существует жесткое ограничение на размер стека, которое не ограничено (ulimit -Hs). Во всяком случае, эта точка должна была подчеркнуть, что стек и куча растут в противоположных направлениях.
Сантош
Тогда чем «автоматическое расширение» отличается от «выталкивания элементов в стек»? Из твоего объяснения у меня возникает ощущение, что они одинаковые. Кроме того, я чувствовал, что начальные точки стека и кучи превышают 8 МБ, поэтому стек может расти столько, сколько ему нужно (или он попадает в кучу). Это правда? Если да, то как операционная система решила, где разместить кучу и стек?
громко и ясно
Они одинаковы, но стеки не имеют фиксированного размера, если только вы жестко не ограничите размер с помощью rlimit. Стек помещается в конец области виртуальной памяти, и куча сразу же после сегмента данных исполняемого файла.
Сантош
Я понимаю, спасибо. Тем не менее, я не думаю, что получаю часть «стеки не имеют фиксированного размера». Если это так, то для чего нужен мягкий предел 8 Мб?
громко и ясно
8

Точный механизм приведен здесь, в Linux: при обработке ошибки страницы в анонимных сопоставлениях вы проверяете, является ли это «распределением, выполняемым с помощью функции« взрослый »», которое вы должны расширять как стек. Если запись области VM говорит, что вы должны, тогда вы настраиваете начальный адрес, чтобы расширить стек.

Когда происходит сбой страницы, в зависимости от адреса, он может быть обслужен (и сброшен сбой) посредством расширения стека. Такое поведение «возрастания количества ошибок» для виртуальной памяти может запрашиваться произвольными пользовательскими программами, MAP_GROWSDOWNфлаг которых передается mmapсистемному вызову.

Вы также можете возиться с этим механизмом в пользовательской программе:

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>

int main() {
        long page_size = sysconf(_SC_PAGE_SIZE);
        void *mem = mmap(NULL, page_size, PROT_READ|PROT_WRITE, MAP_GROWSDOWN|MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
        if (MAP_FAILED == mem) {
                perror("failed to create growsdown mapping");
                return EXIT_FAILURE;
        }

        volatile char *tos = (char *) mem + page_size;

        int i;
        for (i = 1; i < 10 * page_size; ++i)
                tos[-i] = 42;

        fprintf(stderr, "inspect mappping for originally page-sized %p in /proc... press any key to continue...\n", mem);
        (void) getchar();

        if (munmap(mem, page_size))
                perror("failed munmap");

        return EXIT_SUCCESS;
}

Когда он предложит, вы найдете pid программы (через ps) и посмотрите, /proc/$THAT_PID/mapsкак выросла исходная область.

cdleary
источник
Можно ли вызывать munmap для оригинального mem и page_size, даже если область памяти увеличилась с помощью MAP_GROWSDOWN? Я предполагаю, что да, потому что в противном случае это был бы очень сложный API для использования, но в документации ничего не сказано явно по этому вопросу
i.petruk
2
MAP_GROWSDOWN не должен использоваться, и он был удален из glibc (см. Lwn.net/Articles/294001, почему).
Коллин