Understanding Hardware and Linux Stacks: From Function Calls to Multitasking
This article explains the concept of a stack as a LIFO data structure, its hardware implementation in CPUs, how it supports function calls and multitasking, and details the four types of stacks used in Linux—including process, thread, kernel, and interrupt stacks—along with code examples and diagrams.
What is a stack and how is it used?
A stack is a linear LIFO (last‑in‑first‑out) data structure. In hardware it is implemented with a dedicated stack‑pointer register and specific instructions such as PUSH and POP (e.g., ARM uses sp (R13) as the stack pointer and lr (R14) for the return address).
Extended reading: ARM registers include sp (R13) and lr (R14).
Function calls
Each call performs three basic actions:
Pass arguments (often via registers, but saved on the stack when registers are insufficient).
Allocate space for local variables by moving the stack pointer down.
Save the return address so the CPU can resume the caller after the callee returns.
The caller saves any registers that the callee will overwrite, pushes arguments and the return address, then adjusts the stack pointer. When the callee finishes, the saved registers are popped and the stack pointer is restored, yielding a distinct stack frame for each nested call.
func B():
return;
func A():
B();
func main():
while (1)
A();Typical stack‑frame layout (high address → low address): actual arguments → return address → saved base pointer (EBP) → callee locals.
Extended reading: A stack frame stores a function’s arguments, locals, and the saved base pointer.
Multitasking support
An operating system can pre‑empt a task by saving three pieces of state:
The code pointer (program counter) of the task’s main function.
The current stack pointer.
The CPU register set.
Restoring these values later resumes the task exactly where it left off, which is the basis of context switching in pre‑emptive multitasking.
How Linux organizes different stacks
Linux distinguishes four kinds of stacks:
Process stack (user‑space)
Thread stack
Kernel stack (per‑process)
Interrupt stack
Process stack
The process stack lives in user virtual address space. On a 32‑bit system the lower 3 GB (0x00000000‑0xBFFFFFFF) is user space; the upper 1 GB (0xC0000000‑0xFFFFFFFF) is kernel space. The stack grows downward and its maximum size is limited by RLIMIT_STACK (default 8 MiB). The kernel expands the stack on demand via expand_stack() and acct_stack_growth(). Exceeding the limit causes a segmentation fault.
/* file: 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
Program received signal SIGSEGV, Segmentation fault.
(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 8MThread stack
Linux implements threads as processes that share the same task_struct. When a thread is created with the CLONE_VM flag, it shares the parent’s mm_struct. The thread’s stack is allocated via mmap with MAP_STACK and has a fixed size determined at creation.
if (clone_flags & CLONE_VM) {
atomic_inc(¤t->mm->mm_users);
tsk->mm = current->mm;
}glibc’s nptl/allocatestack.c creates the stack with:
mem = mmap(NULL, size, prot,
MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0);Kernel stack (per process)
When a process enters kernel mode (e.g., via a system call), it switches to a separate kernel stack allocated from the thread_info_cache slab. The default size is one page (4 KB) defined by THREAD_SIZE. The bottom of this stack holds a thread_info structure, allowing fast retrieval of the current task_struct from the stack pointer.
union thread_union {
struct thread_info thread_info;
unsigned long stack[THREAD_SIZE/sizeof(long)];
}; 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()Interrupt stack
Interrupt handling also requires a stack. On x86 each CPU has a dedicated interrupt stack (typically two 4 KB pages) allocated by __alloc_pages. Soft‑IRQs receive their own separate stack. On ARM the interrupt stack is shared with the kernel stack, which can lead to overflow on nested interrupts.
Process address‑space layout (Linux)
In a 32‑bit Linux system the virtual address space is divided into user space (0x00000000‑0xBFFFFFFF) and kernel space (0xC0000000‑0xFFFFFFFF). The user space contains the following segments:
Text segment – executable code.
Data segment – initialized global variables.
BSS segment – zero‑initialized globals.
Heap – dynamic allocations.
Stack – function arguments, locals, return addresses.
Memory‑mapped segment – files and shared libraries.
The kernel tracks the layout with struct mm_struct. Relevant fields for the stack are start_stack (base address) and stack_vm (number of pages used by the stack).
struct mm_struct {
...
unsigned long start_stack; /* stack base address */
unsigned long stack_vm; /* stack pages */
...
};Dynamic stack growth
When a process pushes data beyond the current stack region, a page‑fault occurs. The fault handler calls expand_stack(), which invokes acct_stack_growth() to allocate additional pages as long as the total size stays below RLIMIT_STACK. If the limit is reached, the kernel delivers SIGSEGV.
Summary
Hardware stacks provide a fast LIFO storage mechanism used for function calls, register saving, and return‑address handling. Linux builds on this primitive to implement distinct stacks for user‑space processes, fixed‑size thread stacks, per‑process kernel stacks, and architecture‑specific interrupt stacks, each with its own allocation strategy and size limits.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Liangxu Linux
Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)
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.
