Understanding Linux Process Stack Initialization, Physical Page Allocation, and Automatic Growth
This article explains how Linux allocates a process's stack virtual address space at load time, when and how physical pages are actually allocated on demand via page‑fault handling, and how the stack can automatically grow within configurable limits.
1. Process Stack Initialization
When a process is created, the execve system call eventually calls do_execve_common , which invokes bprm_mm_init to allocate a new mm_struct and a single page for the initial stack (typically 4 KB). The stack's virtual address range is stored in a vm_area_struct with vm_start and vm_end fields.
#include
void main()
{
int n = 0;
printf("0x%x\n", &v);
}The kernel creates a vma object, sets vma->vm_end = STACK_TOP_MAX and vma->vm_start = vma->vm_end - PAGE_SIZE , and records the stack pointer in bprm->p .
2. Physical Page Allocation
Physical pages are not allocated during the initial stack setup. When a running process accesses a stack variable whose page is not yet present, a page‑fault interrupt is triggered. The kernel entry __do_page_fault (in arch/x86/mm/fault.c ) locates the relevant vma and, if the address is within the current stack range, calls handle_mm_fault to allocate the missing page.
static void __kprobes __do_page_fault(struct pt_regs *regs, unsigned long error_code)
{
...
vma = find_vma(mm, address);
if (likely(vma->vm_start <= address))
goto good_area;
if (unlikely(expand_stack(vma, address))) {
bad_area(regs, error_code, address);
return;
}
good_area:
fault = handle_mm_fault(mm, vma, address, flags);
}handle_mm_fault walks the four‑level page table (pgd, pud, pmd, pte) and eventually calls handle_pte_fault , which for anonymous mappings (such as the stack) invokes do_anonymous_page . This function allocates a zero‑filled physical page via alloc_zeroed_user_highpage_movable , which in turn uses the buddy allocator ( alloc_pages ).
int handle_mm_fault(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long address, unsigned int flags)
{
pgd = pgd_offset(mm, address);
pud = pud_alloc(mm, pgd, address);
pmd = pmd_alloc(mm, pud, address);
pte = pte_offset_map(pmd, address);
return handle_pte_fault(mm, vma, address, pte, pmd, flags);
}3. Automatic Stack Growth
If the accessed address lies below the current vm_start , the kernel must expand the stack's virtual range. The function expand_stack calls expand_downwards , which computes the new size and the number of pages to add, checks limits via acct_stack_growth , and updates vma->vm_start if allowed.
int expand_stack(struct vm_area_struct *vma, unsigned long address)
{
return expand_downwards(vma, address);
}
int expand_downwards(struct vm_area_struct *vma, unsigned long address)
{
size = vma->vm_end - address;
grow = (vma->vm_start - address) >> PAGE_SHIFT;
acct_stack_growth(vma, size, grow);
vma->vm_start = address;
return 0; // simplified
}The limit check uses the RLIMIT_STACK resource (visible via ulimit -s ). By default many Linux systems set a stack limit of 8 MiB, but it can be changed with ulimit -s <size> .
# ulimit -a
stack size (kbytes, -s) 8192
# ulimit -s 10240
# ulimit -a
stack size (kbytes, -s) 102404. Summary
The Linux kernel allocates a small virtual stack region (4 KB) when a process is loaded, defers physical page allocation until the stack is actually accessed, and automatically expands the virtual stack within configurable limits, raising a segmentation fault if the limit is exceeded.
Refining Core Development Skills
Fei has over 10 years of development experience at Tencent and Sogou. Through this account, he shares his deep insights on performance.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.