Пожалуйста, укажите источник: kyang.cc/
Что такое стек? Что делает стек?
Во-первых, стек представляет собой списокструктура данных. Характеристики этой структуры данных:последний пришел первый вышел(LIFO, LAST IN First Out), данные могут быть выполнены только на одном конце последовательности (называемой: вершина стека)Толкать(нажать) инеожиданно возникнуть(поп) операция. По характеристикам стека легко предположить, что массив можно использовать для реализации этой структуры данных. Но в этой статье будет обсуждаться не стек на программном уровне, а стек на аппаратном уровне.
В большинстве процессорных архитектур реализованы аппаратные стеки. Существуют специальные регистры указателей стека и специальные аппаратные инструкции для выполнения операций push/pop. Например, в архитектуре ARM указатель R13 (SP) — это регистр указателя стека, PUSH — ассемблерная инструкция для заполнения стека, а POP — ассемблерная инструкция для извлечения из стека.
[Расширенное чтение]:Введение в регистры ARM
Процессоры ARM имеют 37 регистров. Регистры расположены частично перекрывающимися группами. Каждый режим процессора имеет другой набор регистров. Сгруппированные регистры обеспечивают быстрое переключение контекста для обработки исключений процессора и привилегированных операций.
Предусмотрены следующие регистры:
- Тридцать 32-битных регистров общего назначения:
- Регистров общего назначения пятнадцать, это r0-r12, sp, lr
- sp (r13) - указатель стека. Компиляторы C/C++ всегда используют sp в качестве указателя стека.
- lr (r14) используется для хранения адреса возврата при вызове подпрограммы. lr можно использовать как регистр общего назначения, если адрес возврата хранится в стеке.
- счетчик программ (ПК): регистр команд
- Регистр состояния приложения (APSR): содержит копию флага состояния арифметико-логического устройства (ALU).
- Регистр состояния текущей программы (CPSR): содержит флаги APSR, текущий режим процессора, флаги отключения прерываний и т. д.
- Регистр состояния сохраненной программы (SPSR): используйте SPSR для хранения CPSR при возникновении исключения.
Вышеизложенный принцип и реализация стека Давайте посмотрим, что делает стек. Роль стека можно отразить в двух аспектах:вызов функциииМногозадачность поддержки.
1. Вызов функции
Мы знаем, что вызов функции имеет следующие три основных процесса:
- Параметры входящего вызова
- Управление пространством локальных переменных
- возврат функции
Функция вызова должна быть эффективной, и данные хранятся вОбщий регистр процессораилиоперативная памятьНесомненно лучший выбор. Взяв в качестве примера параметры вызова, мы можем использовать общие регистры ЦП для хранения параметров. Однако количество регистров общего назначения ограничено, при вложенном вызове функции подфункция снова использует исходный регистр общего назначения, что неизбежно приведет к конфликтам. Поэтому, если вы хотите использовать ее для передачи параметров, перед вызовом подфункции нужно сначалаСохраните значение исходного регистра, а затем, когда подфункция выходитВосстановить исходное значение регистра.
Количество параметров вызова функции, как правило, относительно невелико, поэтому регистры общего назначения могут удовлетворять определенным требованиям. Однако количество и занимаемое пространство локальных переменных относительно велико, и трудно полагаться на ограниченные регистры общего назначения, поэтому мы можем использовать некоторые области оперативной памяти для хранения локальных переменных. Но где подходящее хранилище? Не допускается конфликтов при вложенности и вызове функций, но также обратите внимание на эффективность.
В этом случае стек, несомненно, является хорошим решением. 1. Для конфликта передачи параметров в общие регистры мы можем временно запихнуть общие регистры в стек перед вызовом подфункции, после вызова подфункции вытолкнуть сохраненные регистры и восстановить их. 2. Для применения пространства локальных переменных вам нужно всего лишь переместить указатель на вершине стека вниз, переместив указатель на вершине стека назад, вы можете завершить освобождение пространства локальных переменных 3. Для возврат функции, вам нужно только Перед вызовом подфункции поместить адрес возврата в стек, а после завершения вызова подфункции вставить адрес возврата функции в указатель ПК, что завершает возврат функции вызов;
Таким образом, три основных процесса вышеуказанного вызова функции превращаются в процесс записи указателя стека. Каждый вызов функции сопровождается указателем стека. Даже если функции вызываются в цикле, пока соответствующие указатели стека функций различны, конфликта не будет.
[Расширенное чтение]:Фрейм стека функций
Вызовы функций часто вложены, и в то же время будет много функциональной информации о стеке. Каждая незаконченная функция занимает отдельную смежную область, называемую кадром стека. Рамка стека хранит параметры функции, локальные переменные и данные, необходимые для восстановления предыдущего кадра стека и т. Д. Порядок укладки, когда вызывается функция:
Фактический параметр N~1 → адрес возврата вызывающей функции → базовый указатель кадра вызывающей функции EBP → локальная переменная вызываемой функции 1~N
Границы кадра стека задаются выражениемуказатель базового адреса кадра стека EBPиуказатель стека ESPОпределение, EBP указывает на нижнюю часть текущего кадра стека (высокий адрес), и позиция фиксируется в текущем кадре стека; ESP указывает на вершину текущего кадра стека (младший адрес), когда программа выполняется, ESP будет двигаться с толчком и поп данных. Поэтому доступ к большинству данных в функции основан на EBP. Типичная структура памяти стека вызовов функций показана на следующем рисунке:
Во-вторых, поддержка многозадачности.
Тем не менее, значение стека - это не просто функциональный вызов, с его существованием, чтобы построить многоцелевой режим операционной системы. Мы называем основную функцию в качестве примера, основная функция содержит бесконечное циклическое тело, функция называется первой в циклическом корпусе, а затем вызывается функция B.
func B():
return;
func A():
B();
func main():
while (1)
A();
Только представьте, что в случае с однопроцессорным процессором программа навсегда останется в этой основной функции. Даже если в состоянии ожидания находится другая задача, программа не может перейти к другой задаче из этой основной функции. Потому что, если это отношение вызова функции, это, по сути, задача, принадлежащая основной функции, и ее нельзя рассматривать как переключатель многозадачности.В этот момент сама главная функция-задача фактически привязана к своему стеку, независимо от того, как функция вложена, указатель стека перемещается внутри области действия этого стека.
Видно, что задачу можно охарактеризовать следующей информацией:
1. Основной код функции
2. Указатель стека основной функции
3. Информация о текущем регистре ЦП
Если мы сможем сохранить эту информацию, вы можете заставить CPU справиться с другими задачами. Пока в будущем хотелось бы продолжить с этой главной задачей, чтобы восстановить обратно к вышеуказанной информации. С этим предварительным условием существует основание многозадачности, вы также можете увидеть, что есть еще одно чувство стека.В многозадачном режиме, когда планировщик считает необходимым переключить задачи, ему нужно только сохранить информацию о задаче (то есть три упомянутых выше содержимого). Восстановление состояния другой задачи, а затем переход к позиции, в которой она выполнялась в последний раз, может возобновить выполнение.
Видно, что у каждой задачи есть свое пространство стека, именно из-за независимого пространства стека.Для повторного использования кода разные задачи могут даже смешивать тела функций самих задач.Например, основная функция может иметь две задачи экземпляры. С тех пор также сформировалась структура операционной системы.Например, когда задача вызывает sleep() для ожидания, она может активно уступать ЦП другим задачам, или задача операционной системы с разделением времени будет вынуждена Закончились временные интервалы. Откажитесь от процессора. Независимо от того, какой метод используется, если вы найдете способ переключить контекстное пространство задачи и переключить стек.
[Расширенное чтение]: Связь между задачами, потоками и процессами
Задача — это абстрактное понятие, которое относится к деятельности, выполняемой программным обеспечением; поток — это действие, необходимое для выполнения задачи; процесс — это собирательное название ресурсов, необходимых для выполнения этого действия; существует образ отношения между тремя.
- задача = доставка
- Тема = грузовик доставки привода
- Планирование системы = Решите, какой грузовик доставки подходит для вождения
- Процесс = Дорога + Заправочная станция + Автомобиль доставки + Ремонтная мастерская
Сколько стеков в Linux? Расположение памяти различных стеков?
Познакомившись с принципом работы и назначением стека, вернемся к ядру Linux. Ядро делит стек на четыре типа:
- стек процессов
- стек потоков
- стек ядра
- стек прерываний
Во-первых, стек процессов
Стек процесса принадлежит стеку пользовательского режима, и процессВиртуальное адресное пространствотесно связаны. Тогда давайте сначала разберемся, что такое виртуальное адресное пространство: под 32-битной машиной размер виртуального адресного пространства равен 4G. Эти виртуальные адреса сопоставляются с физической памятью через таблицы страниц, которые поддерживаются операционной системой и на которые ссылается аппаратное обеспечение блока управления памятью (MMU) процессора.Каждый процесс имеет набор собственных таблиц страниц, поэтому для каждого процесса как бы монопольно все виртуальное адресное пространство.
Ядро Linux делит пространство байтов 4G на две части, и самый старший байт 1G (0xC0000000-0xFFFFFFFF) используется ядром, называемымпространство ядра. А младшие байты 3G (0x00000000-0xBFFFFFFFF) используются каждым процессом, называемымпользовательское пространство. Каждый процесс может попасть в режим ядра через системные вызовы, поэтому пространство ядра совместно используется всеми процессами. Хотя процессы режима ядра и пользовательского режима занимают такое большое адресное пространство, это не означает, что они используют так много физической памяти, это означает только то, что они могут доминировать в таком большом адресном пространстве. Они используются для отображения физической памяти в виртуальное адресное пространство по мере необходимости.
Linux имеет стандартную схему адресного пространства процесса.Адресное пространство состоит из различных сегментов памяти (Memory Segment).Основные сегменты памяти следующие:
- Блок (текстовый сегмент): исполняемый код карты памяти
- Сегмент данных: карта памяти инициализированных глобальных переменных исполняемого файла.
- Сегмент BSS: неинициализированные глобальные переменные или статические переменные (инициализированные с нулевыми страницами)
- Куча: хранение динамического распределения памяти, отображение анонимной памяти
- Площадь стека (Stack)
: Стек пользовательского пространства процесса, который автоматически выделяется и освобождается компилятором и хранит значения параметров функций, значения локальных переменных и т.д.
- Сегмент отображения памяти: любой файл отображения памяти
Область стека в указанном выше виртуальном адресном пространстве процесса относится к тому, что мы называем стеком процесса.. Размер стека процесса инициализации рассчитывается компилятором и компоновщиком, но размер стека в реальном времени не фиксирован, ядро Linux будет основано на динамическом росте ситуации стека области стека (фактически, это добавление новой таблицы страниц). . Но нельзя сказать, что площадь стека может расти бесконечно, она тоже имеет максимальный пределRLIMIT_STACK
(обычно 8 м), мы можем пройтиulimit
просмотреть или изменитьRLIMIT_STACK
значение .
[Расширенное чтение]: Как подтвердить размер стека процесса
Чтобы узнать размер стека, мы должны знать начальный и конечный адреса стека.начальный адрес стекаПриобретение очень простое, просто нужно встроить инструкцию по сборке, чтобы получить адрес esp указателя стека.Адрес конца стекаЭто немного проблематично получить, нам нужно использовать рекурсивную функцию для переполнения стека, а затем печатать указатель стека esp в GDB, когда стек переполняется. код показывает, как показано ниже:
/* file name: stacksize.c */
void *orig_stack_pointer;
void blow_stack() {
blow_stack();
}
int main() {
__asm__("movl %esp, orig_stack_pointer");
blow_stack();
return 0;
}
$ g++ -g stacksize.c -o ./stacksize
$ gdb ./stacksize
(gdb) r
Starting program: /home/home/misc-code/setrlimit
Program received signal SIGSEGV, Segmentation fault.
blow_stack () at setrlimit.c:4
4 blow_stack();
(gdb) print (void *)$esp
$1 = (void *) 0xffffffffff7ff000
(gdb) print (void *)orig_stack_pointer
$2 = (void *) 0xffffc800
(gdb) print 0xffffc800-0xff7ff000
$3 = 8378368 // Current Process Stack Size is 8M
Вышеизложенное представляет собой относительно глобальное введение в адресное пространство процесса, поэтому давайте посмотрим, как представленная выше структура памяти отражается в ядре Linux. Ядро использует дескрипторы памяти для представления адресного пространства процесса, а дескриптор представляет информацию обо всех адресных пространствах процесса. Дескриптор памяти представлен структурой mm_struct. Описание каждого поля в структуре дескриптора памяти приведено ниже. Пожалуйста, взгляните на предыдущую схему расположения сегментов памяти процесса:
struct mm_struct {
struct vm_area_struct *mmap; /* 内存区域链表 */
struct rb_root mm_rb; /* VMA 形成的红黑树 */
...
struct list_head mmlist; /* 所有 mm_struct 形成的链表 */
...
unsigned long total_vm; /* 全部页面数目 */
unsigned long locked_vm; /* 上锁的页面数据 */
unsigned long pinned_vm; /* Refcount permanently increased */
unsigned long shared_vm; /* 共享页面数目 Shared pages (files) */
unsigned long exec_vm; /* 可执行页面数目 VM_EXEC & ~VM_WRITE */
unsigned long stack_vm; /* 栈区页面数目 VM_GROWSUP/DOWN */
unsigned long def_flags;
unsigned long start_code, end_code, start_data, end_data; /* 代码段、数据段 起始地址和结束地址 */
unsigned long start_brk, brk, start_stack; /* 栈区 的起始地址,堆区 起始地址和结束地址 */
unsigned long arg_start, arg_end, env_start, env_end; /* 命令行参数 和 环境变量的 起始地址和结束地址 */
...
/* Architecture-specific MM context */
mm_context_t context; /* 体系结构特殊数据 */
/* Must use atomic bitops to access the bits */
unsigned long flags; /* 状态标志位 */
...
/* Coredumping and NUMA and HugePage 相关结构体 */
};
[Расширенное чтение]: Реализация динамического роста стека процессов
Когда процесс запущен, постоянно помещая данные в область стека, когда емкость области стека превышена, область памяти, соответствующая стеку, будет исчерпана, что вызоветошибка страницы. После падения в состояние ядра через исключение исключение будет обнаружено ядром
expand_stack()
Обработка функции, затем вызовacct_stack_growth()
чтобы проверить, есть ли еще подходящее место для роста стека.Если размер стека меньше
RLIMIT_STACK
(обычно 8МБ), то вообще стек удлинится и программа продолжит выполнение не почувствовав что произошло, что является нормальным механизмом расширения стека до нужного размера. Однако, если достигнут максимальный размер пространства стека, это произойдетпереполнение стека, процесс получитошибка сегментацииСигнал.Динамический рост стека — единственный случай, когда разрешен доступ к неотображенной области памяти, любой другой доступ к неотображенной области памяти вызовет ошибку страницы, что приведет к ошибке сегментации. Некоторые из отображаемых областей доступны только для чтения, поэтому попытка записи в эти области также приведет к ошибке сегментации.
Во-вторых, стек потоков
С точки зрения ядра Linux в нем фактически нет концепции потоков. Linux реализует все потоки как процессы и объединяет потоки и процессы в task_struct без каких-либо различий. Поток рассматривается только как процесс, который разделяет некоторые ресурсы с другими процессами, и является ли адресное пространство общим или нет, это почти единственное различие между процессом и так называемым потоком в Linux. При создании потока добавляется флаг CLONE_VM, чтобыДескриптор памяти потока будет напрямую указывать на дескриптор памяти родителя..
if (clone_flags & CLONE_VM) {
/*
* current 是父进程而 tsk 在 fork() 执行期间是共享子进程
*/
atomic_inc(¤t->mm->mm_users);
tsk->mm = current->mm;
}
Хотя адресное пространство потока такое же, как и у процесса, стек, обрабатывающий его адресное пространство, несколько отличается. Для процесса или основного потока Linux его стек создается во время разветвления, которое на самом деле является копией адреса пространства стека родителя, затем копированием при записи (корова) и динамическим ростом. Однако для дочернего потока, сгенерированного основным потоком, его стек будет уже не таким, а фиксированным заранее, с помощью системного вызова mmap, у него нет флага VM_STACK_FLAGS. Это можно скачать с glibcnptl/allocatestack.c
серединаallocate_stack()
Смотрите в функции:
mem = mmap (NULL, size, prot,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
из-за нитиmm->start_stack
Адрес стека совпадает с адресом процесса-владельца, поэтому начальный адрес стека потока не хранится вtask_struct
, должен быть использованpthread_attr_t
серединаstackaddr
инициализироватьtask_struct->thread->sp
(SP указывает наstruct pt_regs
объект, эта структура используется для сохранения поля регистра пользовательского процесса или потока). Все это не имеет значения, главное,Стек потока не может расти динамически. Как только он будет исчерпан, он исчезнет. Это отличается от разветвления порожденного процесса.. Так как стек потока берется из адресного пространства процесса
Выделенная область памяти, в принципе, является приватной для потока. Однако, когда генерируются все потоки одного и того же процесса, многие поля генератора task_struct копируются поверхностно, включая всеvma
, если вы хотите, другие потоки все еще могут получить к нему доступ, поэтому вы должны быть внимательны.
В-третьих, стек ядра процесса
В жизненном цикле каждого процесса он обязательно попадает в ядро через системные вызовы. После выполнения системного вызова и перехвата в ядре стек, используемый этими кодами ядра, является не стеком в исходном пользовательском пространстве процесса, а отдельным стеком пространства ядра, который называется стеком ядра процесса. Когда процесс создается, стек ядра процесса сохраняется из распределителя slab изthread_info_cache
Выделяется в пуле буферов, его размерTHREAD_SIZE
, как правило, размер страницы 4K;
union thread_union {
struct thread_info thread_info;
unsigned long stack[THREAD_SIZE/sizeof(long)];
};
thread_union
стек ядра обработки иtask_struct
Дескрипторы процессов тесно связаны между собой. Так как ядру часто приходится обращатьсяtask_struct
, очень важно эффективно получить дескриптор текущего процесса. Поэтому ядро использует пространство в начале стека ядра процесса для храненияthread_info
структура, и эта структура записывает дескриптор соответствующего процесса, отношения между ними следующие (соответствующая функция ядраdup_task_struct()
):
С помощью связанной выше структуры ядро может сначала получить указатель вершины стека esp, а затем получить его через esp.thread_info
. Вот небольшой трюк, чтобы напрямую сравнить адрес esp с указанным выше.~(THREAD_SIZE - 1)
сразу послеthread_info
адрес г. так какthread_union
Структура изthread_info_cache
Буферный пул Slab применяется иthread_info_cache
существуетkmem_cache_create
При создании адрес гарантированно будетTHREAD_SIZE
выровнены. Поэтому вам нужно только THREAD_SIZE выравнивание указателя стека, вы можете получитьthread_union
адрес, также полученныйthread_union
адрес г. успешно полученthread_info
После этого сразу вытащите его член задачи и успешно получите его.task_struct
. На самом деле приведенное выше описаниеcurrentКак реализовать макрос:
register unsigned long current_stack_pointer asm ("sp");
static inline struct thread_info *current_thread_info(void)
{
return (struct thread_info *)
(current_stack_pointer & ~(THREAD_SIZE - 1));
}
#define get_current() (current_thread_info()->task)
#define current get_current()
В-четвертых, стек прерываний
Когда процесс переходит в режим ядра, ему нужен стек ядра для поддержки вызовов функций ядра. То же самое относится и к прерываниям: когда система получает событие прерывания и выполняет обработку прерывания, ей также требуется стек прерываний для поддержки вызовов функций. Поскольку система находится в режиме ядра, когда система прерывается, стек прерываний может использоваться совместно со стеком ядра. Но является ли он общим или нет, тесно связано с конкретной архитектурой обработки.
Стек прерываний на X86 не зависит от стека ядра, выделение пространства памяти, где находится независимый стек прерываний, происходит вarch/x86/kernel/irq_32.c
изirq_ctx_init()
В функции (если это многопроцессорная система, то у каждого процессора будет независимый стек прерываний) функция использует__alloc_pages
Выделить в младшей области памяти2 физические страницы, что составляет 8 КБ пространства. Интересно, что эта функция также даетsoftirq
Выделите отдельный стек того же размера. так,softirq
не будетhardirq
выполняется в стеке прерываний, но в своем собственном контексте.
На ARM стек прерываний и стек ядра являются общими, совместное использование стека прерываний и стека ядра имеет негативный фактор.Если прерывания вложены друг в друга, это может привести к переполнению стека, что может привести к уничтожению некоторых важных данных стека ядра. , поэтому иногда трудно избежать пространства стека.
Почему Linux нужно различать эти стеки?
Почему необходимо различать эти стеки, на самом деле вопрос дизайна. Вот краткое изложение некоторых мнений, которые я видел для вашего обсуждения:
-
Зачем вам нужен отдельный стек ядра процесса?
- Когда все процессы запущены, они могут перейти в режим ядра через системные вызовы для продолжения выполнения. Предположим, что когда первый процесс A пойман в режиме ядра, ему нужно подождать, чтобы прочитать данные сетевой карты и активно вызвать
schedule()
Отдайте процессор, в это время планировщик будит другой процесс B, и бывает, что процессу B также нужен системный вызов для входа в состояние ядра. Тогда возникает проблема: если есть только один стек ядра, операция проталкивания стека, сгенерированная, когда процесс B входит в состояние ядра, неизбежно уничтожит существующие данные стека ядра процесса A; очень вероятно, что состояние ядра процесса А не может быть корректно возвращено в соответствующее пользовательское состояние;
- Когда все процессы запущены, они могут перейти в режим ядра через системные вызовы для продолжения выполнения. Предположим, что когда первый процесс A пойман в режиме ядра, ему нужно подождать, чтобы прочитать данные сетевой карты и активно вызвать
-
Зачем вам нужен отдельный стек потоков?
- В планировщике Linux нет различия между потоками и процессами.Когда планировщику нужно разбудить «процесс», он должен восстановить контекст процесса, то есть стек процесса, но поток и родительский процесс полностью разделяют адресное пространство, если стек также Если вы используете тот же, вы столкнетесь со следующими проблемами. Если начальное значение указателя стека процесса 0x7ffc80000000; сначала выполняется родительский процесс A, а после вызова некоторых функций указатель стека esp равен 0x7ffc8000FF00, и родительский процесс активно спит, то планировщик будит дочерний поток A1 :
- В это время, если указатель стека esp для A1 имеет начальное значение 0x7ffc80000000, как только в потоке A1 произойдет вызов функции, данные, которые были помещены в стек родительским процессом A, будут уничтожены.
- Если указатель стека потока A1 согласуется с последним обновленным значением родительского процесса в это время, esp равен 0x7ffc8000FF00, то после того, как поток A1 выполнит несколько вызовов функций, указатель стека esp увеличится до 0x7ffc8000FFFF, а затем поток A1 приостановится; планировщик снова заменяется родительским процессом A. Выполнить, должен ли в это время указатель стека родительского процесса быть 0x7ffc8000FF00 или 0x7ffc8000FFFF? Независимо от того, какое значение установлено в указателе стека, проблема будет, верно?
- В планировщике Linux нет различия между потоками и процессами.Когда планировщику нужно разбудить «процесс», он должен восстановить контекст процесса, то есть стек процесса, но поток и родительский процесс полностью разделяют адресное пространство, если стек также Если вы используете тот же, вы столкнетесь со следующими проблемами. Если начальное значение указателя стека процесса 0x7ffc80000000; сначала выполняется родительский процесс A, а после вызова некоторых функций указатель стека esp равен 0x7ffc8000FF00, и родительский процесс активно спит, то планировщик будит дочерний поток A1 :
-
Используют ли процессы и потоки общий стек ядра?
- Нет, он вызывается при создании потоков и процессов.
dup_task_struct
Чтобы создать структуру, связанную с задачей, и стек ядра также находится в этой функции.alloc_thread_info_node
от. Таким образом, несмотря на то, что потоки и процессы совместно используют адресное пространствоmm_struct
, но не разделяйте стек ядра.
- Нет, он вызывается при создании потоков и процессов.
-
Зачем нужен отдельный стек прерываний?
- Эта проблема на самом деле неправильная, архитектура ARM не имеет независимого стека прерываний.
Если у вас есть другие мнения, вы можете оставить сообщение в комментариях :-D
Это конец статьи.Если вы все еще это видите, то я должен выразить вам искреннюю благодарность.Статья слишком длинная.Я писал ее почти неделю. Ну наконец-то его можно выпустить,hexo deploy
, до свидания!
- K.Yang
Справочная статья
Стек вызовов функций языка C (1)
Анализ стека вызовов функций + диаграмма
Распределение адресного пространства процесса
Разница между стеком процессов и стеком потоков в Linux
Основы Linux: управление процессами
stackoverflow - How Process Size is determined?
stackoverflow - Relation between stack limit and threads
Понимание стека ядра процесса Linux
Стек в Linux: стек пользовательского режима/стек ядра/стек прерываний
What Every Programmer Should Know About Memory? - Ulrich Drepper (Red Hat, Inc.)