Thread Stacks

 

← Chapter Index Section 33.1 · Thread Stacks Next: Threads & Signals →

Chapter 33 · Section 33.1

Thread Stacks

Default sizes, limits, resizing, and the virtual address space calculation

2 MBDefault stack (x86-32)

~1500Max threads on x86-32

16 KBMinimum stack (NPTL)

Key Terms

Thread Stack pthread_attr_setstacksize pthread_attr_getstacksize pthread_attr_setstack RLIMIT_STACK sysconf(_SC_THREAD_STACK_MIN) Virtual Address Space ulimit -s

What Is a Thread Stack?

Every thread needs its own private stack. The stack holds:

  • Local variables declared inside functions
  • Function call return addresses
  • Function arguments (depending on architecture calling convention)
  • Saved registers during context switches

Because each thread runs independently, they must each have their own stack. If two threads shared a stack, their local variables would collide and corrupt each other.

Virtual Address Space Layout — x86-32 Process with 3 Threads

0xC000 0000 — Kernel Space
Main Thread Stack (grows ↓, large)
Thread 2 Stack — 2 MB
Thread 3 Stack — 2 MB
Shared Libraries (mmap region)
Heap (malloc / grows ↑)
BSS + Data + Text Segments
0x0000 0000 — NULL / unmapped

On x86-32, user space = 3 GB. Each non-main thread stack = 2 MB → max ~1500 threads.

Default Stack Sizes

The default per-thread stack size (for threads other than the main thread) depends on the architecture:

Architecture Default Stack Size Note
x86-32 (i386) 2 MB Most common embedded reference
x86-64 8 MB (glibc default) Larger due to 64-bit address space
IA-64 (Itanium) 32 MB Very large due to register stack engine
ARM (32-bit) 2 MB (typical) Common in embedded systems
Why does the main thread have a much larger stack? The main thread’s stack is placed at the top of the user address space and can grow downward into unmapped memory (the OS grows it on demand up to RLIMIT_STACK). For non-main threads, the threading library must pre-allocate the entire stack up front using mmap(), because it must pick a fixed location in the virtual address space.

On x86-32 with a 3 GB user virtual address space:

3 GB usable ÷ 2 MB per thread ≈ ~1500 threads maximum
(actual limit is lower due to text/data/heap/shared libs consuming some space)

Querying the Minimum Stack Size

You can query the minimum stack size supported by the current threading implementation using sysconf():

#include <unistd.h>   /* sysconf */
#include <stdio.h>

int main(void)
{
    long min_stack = sysconf(_SC_THREAD_STACK_MIN);
    printf("Minimum thread stack size: %ld bytes (%ld KB)\n",
           min_stack, min_stack / 1024);
    return 0;
}
/* On NPTL / x86-32: prints 16384 bytes = 16 KB */
Tip: Never set a stack smaller than sysconf(_SC_THREAD_STACK_MIN). The threading library itself needs some stack space for internal bookkeeping.

Resizing Thread Stacks

Use pthread_attr_setstacksize() to set the stack size in a thread attributes object before creating the thread.

Function Purpose
pthread_attr_setstacksize(attr, size) Set stack size (bytes)
pthread_attr_getstacksize(attr, &size) Get current stack size setting
pthread_attr_setstack(attr, stackaddr, size) Set both address AND size (reduces portability)

Code Example 1 — Set Custom Stack Size

This example creates a thread with a reduced 512 KB stack. Useful for embedded systems or when you need many threads in a constrained address space.

/*
 * ep_stack_size.c — Create a thread with a custom stack size
 * Compile: gcc -o ep_stack_size ep_stack_size.c -lpthread
 */
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define STACK_SIZE  (512 * 1024)   /* 512 KB custom stack */

void *thread_func(void *arg)
{
    /* Local array to demonstrate stack usage */
    char local_buf[1024];
    local_buf[0] = 'A';  /* touch the memory */
    printf("[Thread %lu] Running with custom 512 KB stack\n",
           (unsigned long)pthread_self());
    printf("[Thread %lu] local_buf is at %p\n",
           (unsigned long)pthread_self(), (void *)local_buf);
    return NULL;
}

int main(void)
{
    pthread_t tid;
    pthread_attr_t attr;
    size_t stack_size;
    int ret;

    /* Step 1: Initialise attributes object */
    ret = pthread_attr_init(&attr);
    if (ret != 0) { perror("pthread_attr_init"); exit(EXIT_FAILURE); }

    /* Step 2: Set the stack size */
    ret = pthread_attr_setstacksize(&attr, STACK_SIZE);
    if (ret != 0) { perror("pthread_attr_setstacksize"); exit(EXIT_FAILURE); }

    /* Step 3: Verify what was set */
    pthread_attr_getstacksize(&attr, &stack_size);
    printf("[Main] Thread stack size will be: %zu KB\n", stack_size / 1024);

    /* Step 4: Create thread with the attribute */
    ret = pthread_create(&tid, &attr, thread_func, NULL);
    if (ret != 0) { perror("pthread_create"); exit(EXIT_FAILURE); }

    /* Step 5: Destroy attributes (no longer needed after create) */
    pthread_attr_destroy(&attr);

    pthread_join(tid, NULL);
    printf("[Main] Thread finished. Program exiting.\n");
    return 0;
}

Expected output:

[Main] Thread stack size will be: 512 KB
[Thread 140234567890] Running with custom 512 KB stack
[Thread 140234567890] local_buf is at 0x7f3a40007a00
[Main] Thread finished. Program exiting.

Code Example 2 — RLIMIT_STACK and Many Threads

This example demonstrates querying stack limits, checking RLIMIT_STACK, and estimating the theoretical maximum thread count based on virtual address space.

/*
 * ep_stack_limits.c — Explore stack size limits
 * Compile: gcc -o ep_stack_limits ep_stack_limits.c -lpthread
 */
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/resource.h>

int main(void)
{
    struct rlimit rl;
    long min_stack;
    size_t default_stack;
    pthread_attr_t attr;

    /* Query minimum stack size for this implementation */
    min_stack = sysconf(_SC_THREAD_STACK_MIN);
    printf("sysconf(_SC_THREAD_STACK_MIN) = %ld bytes (%ld KB)\n",
           min_stack, min_stack / 1024);

    /* Query RLIMIT_STACK — affects NPTL default stack size */
    if (getrlimit(RLIMIT_STACK, &rl) == 0) {
        if (rl.rlim_cur == RLIM_INFINITY)
            printf("RLIMIT_STACK = unlimited\n");
        else
            printf("RLIMIT_STACK = %lu KB\n",
                   (unsigned long)rl.rlim_cur / 1024);
    }

    /* Get the default stack size from a default attr object */
    pthread_attr_init(&attr);
    pthread_attr_getstacksize(&attr, &default_stack);
    printf("Default thread stack size (attr) = %zu KB\n",
           default_stack / 1024);
    pthread_attr_destroy(&attr);

    /* Calculate theoretical max threads on x86-32 (3 GB user space) */
    unsigned long user_space = 3UL * 1024 * 1024 * 1024; /* 3 GB */
    unsigned long max_threads = user_space / default_stack;
    printf("\nTheoretical max threads (x86-32, 3GB user space): ~%lu\n",
           max_threads);
    printf("(Real limit lower due to text/data/heap/libs overhead)\n");

    /* Show how reducing stack size increases max thread count */
    printf("\nWith 256 KB stack:  ~%lu threads\n",
           user_space / (256 * 1024));
    printf("With 512 KB stack:  ~%lu threads\n",
           user_space / (512 * 1024));
    printf("With 2048 KB stack: ~%lu threads\n",
           user_space / (2048 * 1024));

    return 0;
}
/* NOTE: Under NPTL, if RLIMIT_STACK != unlimited, it becomes
 * the default stack size for new threads. Set it BEFORE exec()
 * using:  ulimit -s 512   (in shell, before running the program)
 * Calling setrlimit() inside main() is too late for NPTL. */
Important (NPTL behaviour): Under NPTL, RLIMIT_STACK becomes the default thread stack size. But NPTL reads it during run-time initialisation — before main() is even called. You must set it using ulimit -s in the shell before running the program. Calling setrlimit() inside main() has no effect on the default stack size.

When Should You Change the Stack Size?

✅ Increase Stack When…

  • Thread uses large local arrays (e.g., char buf[1MB])
  • Deep recursive function calls
  • C++ exception handling overhead
  • Threads calling alloca() with large sizes

✅ Decrease Stack When…

  • You need many threads (hundreds/thousands)
  • Threads are simple worker threads with few locals
  • Running on a constrained virtual address space (32-bit)
  • Embedded targets with limited RAM
Stack overflow danger: If a thread exceeds its stack size, the result is usually a segmentation fault (SIGSEGV). There is no automatic stack growth for non-main threads. The OS will NOT extend the stack. Design your threads to stay well within their allocated stack.

Interview Questions

Q1. What is the default thread stack size on Linux x86-32, and why does the main thread have a larger effective stack?
The default stack size for non-main threads on x86-32 Linux is 2 MB. The main thread’s stack is different: it is placed at the top of the user address space and grows downward on demand (the OS provides additional pages via a page fault mechanism) up to RLIMIT_STACK. Non-main threads cannot grow dynamically because the threading library must pre-allocate the entire stack at a fixed virtual address using mmap().
Q2. How do you calculate the maximum number of threads a 32-bit Linux process can create?
On x86-32, user-accessible virtual address space is approximately 3 GB. With a default 2 MB stack per thread, the theoretical maximum is 3 GB ÷ 2 MB ≈ 1500 threads. The real limit is lower because text, data, BSS, heap, and shared libraries also consume virtual address space. To increase the limit, reduce the per-thread stack size using pthread_attr_setstacksize().
Q3. What is the role of RLIMIT_STACK in NPTL thread stack sizing?
Under NPTL, if RLIMIT_STACK is set to anything other than RLIM_INFINITY, it is used as the default stack size for all newly created threads. This is read during the C library’s run-time initialisation, which happens before main() is called. Therefore, calling setrlimit(RLIMIT_STACK, ...) inside main() has no effect on default stack size. The correct way to set it is via ulimit -s <kbytes> in the shell before executing the program.
Q4. What is the difference between pthread_attr_setstacksize() and pthread_attr_setstack()?
pthread_attr_setstacksize() sets only the size of the stack — the threading library chooses the location automatically. pthread_attr_setstack() sets both the address and the size of the stack, giving you full control. Setting the address manually reduces portability (the address must be valid, aligned, and sized correctly for the target architecture) and is rarely needed outside embedded or real-time systems where you manage memory explicitly.
Q5. What happens if a thread exceeds its stack size?
A non-main thread that overflows its stack will typically receive a SIGSEGV (segmentation fault). Unlike the main thread, non-main threads cannot grow their stack dynamically — the OS does not extend their stack on a page fault because the threading library allocated it as a fixed mmap() region. Depending on whether a guard page is present (NPTL enables a guard page by default via pthread_attr_setguardsize()), the overflow may be detected cleanly or corrupt adjacent memory.
Q6. How do you query the minimum allowed thread stack size programmatically?
Use sysconf(_SC_THREAD_STACK_MIN). On NPTL/x86-32, this returns 16,384 bytes (16 KB). You should never set a thread’s stack smaller than this value. The minimum exists because the Pthreads library itself needs some stack space for its internal book-keeping when the thread starts up.

Section Summary

  • Each thread (except main) gets a fixed-size private stack, default 2 MB on x86-32.
  • Use pthread_attr_setstacksize() before pthread_create() to resize.
  • Minimum stack size: query with sysconf(_SC_THREAD_STACK_MIN) → 16 KB on NPTL.
  • On 32-bit systems, 2 MB stacks limit you to ~1500 threads; reduce stack to increase count.
  • NPTL reads RLIMIT_STACK before main(); set it via shell, not setrlimit() inside program.

Keep Learning — It’s Free

EmbeddedPathashala covers Linux, BLE, RTOS, and more for students and freshers.

Visit EmbeddedPathashala

Leave a Reply

Your email address will not be published. Required fields are marked *