Linux Process and Thread Virtual Address Space (VAS): A Complete Guide for Kernel Developers – Best Linux Device Drivers Course

 

 

Linux Process & Thread Virtual Address Space
How Linux 6.x lays out memory for every running process and thread — Free Linux Kernel Development Course
📚 Level
Intermediate
⏱ Read Time
~18 min
💻 Kernel
Linux 6.x

Linux Process and Thread Virtual Address Space (VAS): A Complete Guide for Kernel Developers

Every time you run a program on Linux, the kernel creates a private virtual address space (VAS) for that process. Understanding how this virtual address space is organized — what lives in it, how threads share it, and how the kernel manages it — is one of the most important foundations of Linux kernel programming and Linux device driver development. In this lecture, part of our free Linux kernel development course, we will walk through the Linux process virtual address space from first principles, updated for Linux 6.x behavior.

🎓 What You Will Learn

  • What a Virtual Address Space (VAS) is and why Linux uses one per process
  • How a Linux process VAS is divided into user space and kernel space
  • What segments (mappings) make up a process in user space: text, data, heap, stack, library mappings
  • How threads share the VAS and why each thread gets its own stack
  • How fork() and pthread_create() create stacks and how they differ
  • What task_struct is and why every thread has one in kernel space
  • How kernel stacks work and why they are separate from user stacks
  • How to inspect VAS information on a running Linux system
  • Key changes and improvements from Linux 5.x to Linux 6.x

✅ Prerequisites

  • Basic C programming knowledge
  • Familiarity with what a process is in Linux
  • Basic understanding of what virtual memory means (we will also explain it here)
  • A Linux system (Ubuntu 22.04 / Debian 12 or later recommended for Linux 6.x)

What is a Virtual Address Space in Linux?

Modern operating systems, including Linux, do not give a process direct access to physical RAM. Instead, the kernel gives each process its own virtual address space — a large, private range of memory addresses that the process can use as if it owned all of it. Behind the scenes, the CPU’s Memory Management Unit (MMU) translates these virtual addresses into real physical addresses using page tables maintained by the kernel.

This design gives Linux several important benefits:

  • Isolation: One process cannot accidentally (or maliciously) read or write another process’s memory.
  • Flexibility: The kernel can place data anywhere in physical RAM and map it to a convenient virtual address for the process.
  • Overcommit: The total virtual memory used by all processes can exceed the physical RAM available; the kernel uses demand paging to bring pages in only when actually accessed.

On a 64-bit x86 Linux system running Linux 6.x, the virtual address space is enormous — theoretically 264 bytes. In practice, Linux splits the usable 48-bit (or 57-bit with 5-level paging) address range into two halves:

Linux 64-bit Virtual Address Space Split (x86-64, Linux 6.x)
Kernel Space
High virtual addresses
Process cannot access directly
Shared across all processes
— address space boundary (canonical hole on x86-64) —
User Space
Low virtual addresses
Process runs here
Private per process

The user space half belongs exclusively to the process. The kernel space half is shared across all processes — but user-mode code cannot access it directly. Attempting to do so results in a segmentation fault (SIGSEGV).

💡 Note: On x86-64 with 4-level paging (the most common configuration in Linux 6.x), user space spans 0x0000000000000000 to 0x00007FFFFFFFFFFF and kernel space spans the upper half. With 5-level paging (enabled with CONFIG_X86_5LEVEL), user space is even larger. You can check which is active with cat /proc/cpuinfo | grep la57.

User Space Segments: What Lives in a Process VAS

The user space portion of a process VAS is not a single flat block. The kernel divides it into distinct segments (technically called memory mappings or VMAs — Virtual Memory Areas). Each VMA has its own permissions, purpose, and behavior.

Let’s look at the main segments that make up a typical Linux process in user space:

📄
Text Segment
r-x
Your compiled program code. Read-only and executable. Shared between processes running the same binary.
📉
Data Segment
rw-
Global and static variables. Split into initialized (.data) and uninitialized/BSS (.bss) sections.
📌
Heap
rw-
Dynamic memory (malloc/calloc/new). Grows upward. Managed by the C runtime.
📚
Library Mappings
r-x / rw-
Shared libraries (.so files) mapped in. Text is shared, data is private per process.
▼
Stack(s)
rw-
Grows downward. One per thread. Holds local variables, function call frames, return addresses.

The Text Segment (Code)

The text segment contains the machine code of your compiled program. It is mapped with read and execute permissions (r-x) but is not writable — this prevents accidental or malicious self-modification of code. On Linux, when multiple processes run the same program (for example, two terminals running bash), they share the same physical pages for the text segment — only one copy lives in RAM. This sharing is handled transparently by the kernel using the MMU.

The Data Segment (Initialized, Uninitialized, Heap)

The data segment covers all global and static variables. It is split into three distinct mappings:

  • Initialized data (.data): Global/static variables that have an explicit initial value in your source code. These values are stored in the binary on disk and loaded into memory.
  • Uninitialized data / BSS (.bss): Global/static variables that are declared but not initialized. The kernel zero-fills these at process start — it does not need to store them in the binary, saving disk space.
  • Heap: The dynamically growing region managed at runtime by malloc() / free() and similar calls. It starts right above BSS and grows upward toward higher addresses as you allocate memory.
💡 Tip: You can verify the segments of any process by reading /proc/<PID>/maps. For your own process you can use /proc/self/maps. Try: cat /proc/self/maps in a terminal.
## Example: Reading /proc/self/maps on Linux 6.x
## Each line: start-end  permissions  offset  dev  inode  pathname

$ cat /proc/self/maps
55a3f0000000-55a3f0001000 r--p 00000000 08:01 1234567 /bin/cat   ← text (read)
55a3f0001000-55a3f0002000 r-xp 00001000 08:01 1234567 /bin/cat   ← text (exec)
55a3f0002000-55a3f0003000 r--p 00002000 08:01 1234567 /bin/cat   ← rodata
55a3f0004000-55a3f0005000 rw-p 00003000 08:01 1234567 /bin/cat   ← data
55a3f1000000-55a3f1021000 rw-p 00000000 00:00 0        [heap]
7f3b00000000-7f3b001ff000 r--p 00000000 08:01 9876543 /lib/libc.so.6
...
7fff00000000-7fff00021000 rw-p 00000000 00:00 0        [stack]

Library Mappings

When your program uses shared libraries (like libc.so.6, libpthread.so.0, or any custom .so), the dynamic linker (ld.so) maps these into the process’s VAS at runtime. Each shared library gets its own VMA entries — typically one for the code (read + execute), one for read-only data, and one for writable data. The code pages are shared in physical memory across all processes using the same library; only the per-process data pages are private.

Threads and Stacks in the Virtual Address Space

Here is where things get interesting for Linux kernel programming. The relationship between threads and the VAS is fundamental to understanding how the Linux scheduler works and how task_struct is organized.

All Threads in a Process Share the Same VAS

In Linux, a thread is not fundamentally different from a process at the kernel level. Both are represented by a task_struct. The difference is that threads within the same process all share the same virtual address space — they use the same page tables, see the same text segment, the same heap, the same global variables. A process is essentially a group of threads sharing one VAS.

One Process = Shared VAS + Multiple Threads, Each with Its Own Stack
Process P (PID 1042)
📄 Text Segment (shared by all threads)
📉 Data / BSS / Heap (shared)
📚 Library Mappings (shared)
▼ Per-Thread Stacks (each thread has its own)
Stack
main()
Stack
thread-1
Stack
thread-2

One User Space Stack Per Thread

Even though threads share the text segment, heap, and data segments, every thread needs its own private stack. This is because each thread is executing its own call chain — it may be deep inside some function while another thread is doing something completely different. If threads shared a stack, their function calls would corrupt each other.

So on a system with 1,000 user-mode threads alive, there are 1,000 user space stacks in memory — one per thread. Each stack:

  • Grows downward (toward lower addresses) as functions are called.
  • Has a default size limit of 8 MB (controlled by the RLIMIT_STACK resource limit).
  • Can be inspected using the prlimit or ulimit -s commands.
## Check the stack size limit on your Linux system
$ ulimit -s
8192        ← 8192 KB = 8 MB (default on most distributions)

## Or with prlimit (more detailed):
$ prlimit --stack --pid $$
RESOURCE  DESCRIPTION               SOFT      HARD UNITS
STACK     max stack size            8192 unlimited kbytes

Where Does the main() Thread Stack Live?

The main() thread is special. Its stack is placed by the kernel near the very top (high end) of the user VAS — just below where environment variables and command-line arguments are stored. If a process is single-threaded (only main()), it has exactly one user space stack, located at the high end of the address space.

For threads created later using pthread_create(), the C library allocates their stacks at arbitrary locations in the VAS (typically in the middle range, carved out of the available virtual address space using mmap()).

How Stacks Are Created: fork() vs pthread_create()

Understanding how thread stacks are created is important for Linux kernel programming because it reveals the unified mechanism the kernel uses for both processes and threads.

The fork() System Call Path

When a new process is created using fork(), the kernel:

  1. Creates a new task_struct for the child process.
  2. Copies (or sets up copy-on-write references to) the parent’s VAS.
  3. The child gets a new VAS — a fresh copy of the parent’s user space, including a copy of the stack.

In modern Linux 6.x, fork() goes through this kernel call path:

/* Linux 6.x fork() call path */
sys_fork()
  └─► kernel_clone()          /* renamed from _do_fork() in Linux 5.10 */
        └─► copy_process()
              └─► dup_task_struct()   /* allocates new task_struct + kernel stack */
              └─► copy_mm()           /* sets up new VAS (COW for fork) */
💡 Linux 6.x Update: In older kernels (before 5.10), the main fork implementation function was named _do_fork(). Since Linux 5.10, it was renamed to kernel_clone() to better reflect its generality. If you read older textbooks or tutorials that mention _do_fork(), they are referring to the same mechanism — just with the old name.

The pthread_create() Path

When you call pthread_create() in your C program, it does not go through fork(). Instead, the POSIX threads library (libpthread, or the newer NPTL implementation built into glibc) calls the Linux-specific clone(2) system call directly.

The key difference is in the clone_flags parameter passed to clone(). For threads, flags like CLONE_VM, CLONE_FS, CLONE_FILES, CLONE_SIGHAND are set, which tell the kernel to share the VAS, filesystem context, file descriptors, and signal handlers with the parent. This is what makes a thread a thread — it reuses the parent’s VAS instead of creating a new one.

/* Simplified: what pthread_create does under the hood */
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | ...,
      child_stack_ptr,   /* pointer to newly allocated thread stack */
      &tid,
      NULL,
      NULL);

/* Inside the kernel, clone() calls kernel_clone() with these flags.
   CLONE_VM means: don't copy the VAS, share it.
   The new task_struct points to the SAME mm_struct as the parent. */
/* Minimal example: creating a thread and checking it in /proc */
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

void *worker(void *arg)
{
    printf("Thread TID = %d, PID = %d\n", gettid(), getpid());
    sleep(30);   /* stay alive so we can inspect /proc */
    return NULL;
}

int main(void)
{
    pthread_t t;
    pthread_create(&t, NULL, worker, NULL);
    printf("Main  TID = %d, PID = %d\n", gettid(), getpid());
    pthread_join(t, NULL);
    return 0;
}
## Compile and run:
$ gcc -o thread_demo thread_demo.c -lpthread
$ ./thread_demo &
Main  TID = 5021, PID = 5021
Thread TID = 5022, PID = 5021   ← same PID, different TID

## View the VAS of the main thread (same mm for both):
$ cat /proc/5021/maps | grep stack
7f3b00000000-7f3b00021000 rw-p 00000000 00:00 0   [stack]        ← main stack
7f3affc00000-7f3affd00000 rw-p 00000000 00:00 0                  ← thread-1 stack (mmap'd)

Kernel Space Organization: task_struct and Kernel Stacks

So far we have looked at user space. Now let’s cross into kernel space — the half of the VAS that only the kernel can access. This is where the kernel manages per-process and per-thread state.

What is task_struct?

Every thread in Linux — whether it is a user-mode thread or a kernel thread — is represented internally by a task_struct. This is the central data structure of the Linux process management subsystem. It contains everything the kernel needs to know about a running thread: its state, its scheduling priority, its memory mappings (via a pointer to mm_struct), its file descriptors, its signal handlers, its credentials, and much more.

Key Fields in task_struct (Linux 6.x, simplified)
Field Type What It Tracks
pid pid_t Thread ID (TID). Every thread has a unique PID in the kernel.
tgid pid_t Thread Group ID. Equals the PID of the main() thread — this is what getpid() returns.
mm struct mm_struct * Pointer to memory descriptor — describes the entire VAS. Shared among threads in the same process.
active_mm struct mm_struct * Used when running kernel threads that borrow another process’s VAS during context switch.
stack void * Pointer to this thread’s kernel stack.
state / __state unsigned int Current run state: running, sleeping, stopped, etc.
prio int Scheduling priority (dynamic).
files struct files_struct * Open file descriptors. Shared among threads, private for separate processes.
signal struct signal_struct * Signal handlers. Shared among threads in a process.
💡 Linux 6.x Note: In Linux 6.x, the state field in task_struct was renamed to __state (with helpers like task_is_running()). If you are porting code or reading kernel source, be aware of this rename introduced around Linux 5.14.

The Kernel Stack — Separate from the User Stack

Every thread has two stacks: the user space stack (which we already covered) and a kernel stack. These are completely separate. The kernel stack is allocated in kernel space when the task_struct is created, and it is used whenever the thread enters the kernel — for example, during a system call, an interrupt, or a page fault.

The kernel stack is much smaller than the user stack. On most architectures, the default kernel stack size in Linux 6.x is 16 KB per thread (two physical pages on x86-64 with 4 KB pages). This is intentionally small to reduce memory usage when thousands of threads exist. A key principle of Linux kernel programming is: never allocate large data structures on the kernel stack.

User Stack vs Kernel Stack for a Single Thread
User Space
High address ↑
User Stack
~8 MB limit
Grows ↓ downward
Local vars, call frames
rw- user pages
↔
Kernel Space
Kernel address ↑
Kernel Stack
16 KB (default)
Used during syscalls
& interrupt handling
rw- kernel pages

Kernel Threads — Threads Without User Space

Not all threads on a Linux system come from user programs. The kernel itself creates its own threads — called kernel threads — to perform background work like memory management (kswapd), disk I/O (kworker), soft IRQ processing (ksoftirqd), and RCU callbacks (rcu_preempt).

Kernel threads are special: they have a task_struct and a kernel stack, but they have no user space VAS. Their mm pointer in task_struct is NULL. You can see them in the output of ps — they appear with square brackets around their name:

## List kernel threads on a Linux 6.x system
$ ps aux | awk '$1=="root" && $11~/^\[/ {print $11}' | head -15

[kthreadd]

[rcu_gp]

[rcu_par_gp]

[kworker/0:0H-events_highpri]

[mm_percpu_wq]

[rcu_tasks_rude_]

[rcu_tasks_trace]

[ksoftirqd/0]

[rcu_preempt]

[migration/0]

[kswapd0]

[kcompactd0]

[khugepaged]

[kworker/u8:1-flush-8:0]

Inspecting the Virtual Address Space on a Live Linux System

Theory becomes much more concrete once you can see real numbers. Linux exposes detailed VAS information for every process through the /proc filesystem. Here are the most useful commands for a Linux kernel developer:

## 1. See all VMAs (segments) for a process
$ cat /proc/<PID>/maps

## 2. More detailed VMA info (includes VMA flags, RSS, PSS)
$ cat /proc/<PID>/smaps

## 3. Count processes, threads, kernel threads
## (run on your Linux 6.x system and interpret the output)
$ ps -eLf | wc -l                          # total threads (user + kernel)
$ ps -eLf | grep -v '^\[' | wc -l         # user-mode threads
$ cat /proc/loadavg                         # includes running/total threads

## 4. See virtual memory stats for a process
$ cat /proc/<PID>/status | grep -E 'VmRSS|VmSize|VmStk|Threads'
VmSize:    20480 kB     ← total virtual address space used
VmRSS:      4096 kB     ← resident (actually in RAM) pages
VmStk:       136 kB     ← stack usage
Threads:       3         ← total threads in this process

## 5. Read the kernel stack size configuration
$ grep THREAD_SIZE /boot/config-$(uname -r)
CONFIG_THREAD_SIZE_ORDER=2      ← 2^2 = 4 pages = 16 KB kernel stack on x86-64

Process vs Thread: VAS Comparison Table

Aspect Process (fork) Thread (pthread_create / clone)
Virtual Address Space New VAS (copy-on-write copy) Shared VAS with creator
task_struct New task_struct New task_struct
mm_struct pointer New mm_struct Same mm_struct as parent
User space stack Copied (COW) from parent New stack (mmap’d)
Kernel stack New kernel stack New kernel stack
File descriptors Copied (new struct) Shared (CLONE_FILES)
Signal handlers Copied Shared (CLONE_SIGHAND)
PID visible to kernel New unique PID (= TID) New unique TID; TGID = parent PID
Linux kernel call kernel_clone() / copy_mm() creates new mm kernel_clone() with CLONE_VM

Common Mistakes and Misconceptions

⚠️ Mistake 1: Thinking PID and TID are always the same.
The getpid() system call returns the Thread Group ID (TGID), not the kernel-level PID of the individual thread. All threads in a process return the same value from getpid(). To get the actual per-thread ID, use gettid() (available in glibc 2.30+ and Linux 5.x+ without needing syscall()).
⚠️ Mistake 2: Assuming threads are “lighter” because they skip kernel_clone().
Threads still go through kernel_clone() — the difference is only in the flags passed. Thread creation does allocate a new task_struct and a new kernel stack. The savings come from not needing to create a new mm_struct or page tables.
⚠️ Mistake 3: Allocating large arrays on the kernel stack.
The kernel stack is only 16 KB. Declaring a large array inside a kernel function can overflow the kernel stack, causing silent corruption or a kernel panic. Always use kmalloc() / vmalloc() for anything larger than a few hundred bytes in kernel code.
⚠️ Mistake 4: Confusing the BSS segment with uninitialized garbage.
Variables in the BSS segment are zero-initialized by the kernel at process start — not garbage. This is guaranteed by the C standard and enforced by the kernel. Only stack-local variables are uninitialized (and may contain garbage).

Best Practices for Linux Kernel Developers

  • Use /proc/<PID>/maps and /proc/<PID>/smaps regularly to understand and debug VAS layout during kernel and driver development.
  • Keep kernel stack usage minimal. Avoid large local variables in kernel functions. Prefer heap allocation with kmalloc() for bigger structures.
  • Understand mm_struct before writing memory management code. The mm_struct is the heart of the VAS. Reading include/linux/mm_types.h in the kernel source is essential.
  • Use gettid() instead of getpid() when you need to uniquely identify a specific thread in a multithreaded program.
  • Set stack size limits carefully in embedded and real-time applications using pthread_attr_setstacksize().
  • Use KASAN (Kernel Address Sanitizer) — available in Linux 6.x — to catch stack overflows and out-of-bounds accesses during kernel development.

What Changed in Linux 6.x for Process and VAS Management

Area Old Behavior (pre-5.10) Linux 6.x Behavior
Fork implementation _do_fork() kernel_clone()
task_struct state field state __state with accessor helpers
5-level paging Optional, not default Supported and enableable at boot with la57
Stack size (kernel) 8 KB (some configs) 16 KB default on x86-64
VMA locking Single mmap_sem Per-VMA locking (maple tree, Linux 6.1+)
mm_struct VMA storage Red-black tree + linked list Maple Tree (Linux 6.1)
KASAN stack support Limited Improved stack instrumentation
💡 Maple Tree (Linux 6.1): Linux 6.1 replaced the old red-black tree + doubly-linked list VMA storage with a Maple Tree — a B-tree optimized for storing non-overlapping ranges. This improved VMA lookup performance and reduced lock contention for multithreaded processes. You will see references to maple_tree in mm_struct when reading Linux 6.x kernel source.

📚 Summary & Key Takeaways

  • Every Linux process gets a private Virtual Address Space (VAS) split into user space (process-private) and kernel space (kernel-only, shared across processes).
  • The user space VAS contains several segments: text (code, r-x), data/BSS/heap (rw-), library mappings, and per-thread stacks (rw-, grows downward).
  • All threads within a process share the same VAS but each thread has its own private user space stack (default 8 MB) and its own kernel stack (16 KB on x86-64).
  • Every thread is represented by a task_struct in kernel space. Threads share the same mm_struct; separate processes have different mm_struct instances.
  • fork() creates a new VAS (copy-on-write). pthread_create() → clone(CLONE_VM | ...) shares the existing VAS.
  • In Linux 6.x, fork goes through kernel_clone() (renamed from _do_fork()), VMA storage uses a Maple Tree (since 6.1), and task state uses __state.
  • Use /proc/<PID>/maps, /proc/<PID>/status, and prlimit to inspect VAS on a live system.

Conclusion

The Linux process virtual address space is the foundation on which everything else in the kernel is built — scheduling, memory management, device drivers, and system calls all interact with the VAS. By understanding how user space segments are laid out, how threads share the VAS while maintaining private stacks, and how the kernel represents each thread with a task_struct and a kernel stack, you gain the mental model needed for serious Linux kernel programming and Linux device driver development.

This lecture is part of the free Linux kernel development course at EmbeddedPathashala. In the next lecture, we will dive deeper into mm_struct and VMAs, and look at how the kernel walks page tables to resolve virtual-to-physical address translation.

Authoritative References

Frequently Asked Questions

Q1: What is the difference between a process and a thread in Linux at the kernel level?

At the kernel level, both processes and threads are represented by a task_struct and are scheduled the same way. The difference is that threads created with pthread_create() use clone(CLONE_VM) to share the parent’s virtual address space (mm_struct), while processes created with fork() get a new VAS. From the kernel’s perspective, both are “tasks”.

Q2: Why does each thread need its own stack?

Each thread runs its own independent function call chain. If two threads shared a single stack, their local variables and return addresses would overwrite each other, causing immediate corruption. By giving each thread its own stack, the kernel ensures that one thread’s function calls never interfere with another’s.

Q3: How big is the kernel stack in Linux 6.x?

On x86-64, the default kernel stack size in Linux 6.x is 16 KB (4 pages of 4 KB each, configured by CONFIG_THREAD_SIZE_ORDER=2). This is separate from the user space stack (default 8 MB). The kernel stack is used only while the thread is executing in kernel mode (during system calls, interrupts, etc.).

Q4: What is mm_struct and why is it important?

mm_struct is the kernel data structure that describes the entire virtual address space of a process. It contains the list (or tree, in Linux 6.1+) of VMAs, the page tables, memory statistics, and other VAS metadata. All threads in a process share the same mm_struct. Kernel threads have a NULL mm pointer since they have no user space.

Q5: What changed from _do_fork() to kernel_clone() in Linux 5.10+?

The function that implements the core logic of creating a new task (for both fork(), vfork(), and clone()) was renamed from _do_fork() to kernel_clone() in Linux 5.10. The behavior is the same — the rename was done to make the code more readable and to better represent the function’s generality. If you are reading older books, _do_fork() and kernel_clone() refer to the same thing.

Q6: How can I see the virtual address space of a running process?

Use cat /proc/<PID>/maps to see all VMAs. For more detail including memory usage, use cat /proc/<PID>/smaps. For your own process, substitute self for the PID. The pmap command provides a more readable format: pmap -x <PID>.

Q7: What is RLIMIT_STACK and how do I change it?

RLIMIT_STACK is a resource limit that caps the maximum size of the user space stack for the main() thread (typically 8 MB). You can view it with ulimit -s and change it with ulimit -s <size_in_KB> in your shell, or programmatically with setrlimit(RLIMIT_STACK, ...). For thread stacks created with pthread_create(), you control the size via pthread_attr_setstacksize().

Q8: What is the Maple Tree in Linux 6.1 and how does it affect VAS?

The Maple Tree, introduced in Linux 6.1, replaced the red-black tree + linked list that was previously used to store VMAs inside mm_struct. It is a B-tree optimized for non-overlapping ranges (like VMAs). It offers better cache efficiency and improved performance for per-VMA locking, which matters especially for multithreaded processes that frequently allocate/deallocate memory.

Q9: How does copy-on-write (COW) work when fork() is called?

When fork() is called, the kernel does not immediately copy all the parent’s pages to the child. Instead, it marks all writable pages in both parent and child as read-only and shares them. When either process tries to write to a shared page, the MMU triggers a page fault, and only then does the kernel copy that specific page for the writing process. This is copy-on-write — it makes fork() very fast when followed by exec().

Q10: Why do kernel threads have mm = NULL?

Kernel threads only ever execute in kernel space — they never run user-mode code and never access user space memory. Therefore, they do not need a user space VAS at all. The mm pointer in their task_struct is set to NULL. When a kernel thread needs to access memory-mapped I/O or kernel data, it uses the kernel’s own address space, not a per-process VAS. The active_mm field is used to borrow another process’s page tables during context switches (for TLB efficiency), but the kernel thread never actually uses the user-space portion.

🎓 Free Linux Kernel Development Course

EmbeddedPathashala offers 100% free, in-depth courses on Linux kernel programming, Linux device drivers, embedded systems, and Bluetooth development.

📖 Browse All Courses 💻 Linux Kernel Course Index

Leave a Reply

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