Main Thread vs. pthread Stacks on Linux
This post compares the stack used by the primordial thread, also called the main
thread, with the stack used by a thread created through the pthread API. On
Linux, the two types of stacks use different memory mappings.
Any mapping has three related but largely independent metrics:
| Term | What it means | Where to read it |
|---|---|---|
| Reserve (virtual) | Address space the kernel has set aside | Size: in /proc/<pid>/smaps |
| Committed | Memory the kernel has accounted against the overcommit limit | Committed_AS globally, or ac in a mapping’s VmFlags |
| Resident (RSS) | Physical pages currently resident in RAM | Rss: in smaps |
Memory is usually reserved first, then committed, and finally made resident when written to.
§Main thread vs. pthread stacks
The main thread’s stack is the kernel’s [stack] mapping. It is not a fixed-size
mmap. The kernel extends the mapping toward lower virtual addresses on demand
as alloca grows the stack and the program writes to new pages. Its reservation
and commit charge grow only as far as the stack is used, so
Reserve == Committed, and both increase after the alloca call.
A pthread stack is a fixed-size mmap that glibc creates with the thread. In
this experiment, glibc reserves and commits the full 8 MiB immediately, in
contrast to the main thread’s lazy reservation and commit.
For both types of threads, resident memory is usually much smaller than the committed size because threads often do not touch all the memory they request.
§The experiment
The program below reports the main thread’s stack mapping, grows the stack with
alloca, writes to the allocated memory to extend the [stack] mapping, and
then reports the mapping again. It repeats the process in a newly created
thread. To find a thread’s stack in smaps, the program takes the address of a
local variable and selects the region that contains it.
§The program
1 |
|
§Observed output
1 | main stack (fresh) : Reserve 136 kB | Committed 136 kB | Resident 16 kB |