What Is struct task_struct in Linux? – Free Linux Device Driver Training

Linux Kernel Task Structure: Understanding struct task_struct

Free Linux Kernel Development Course — EmbeddedPathashala

Part 1
Task Structure Deep Dive
Free
Linux Kernel Course
Beginner+
Level

Linux Kernel Task Structure (struct task_struct) Explained

In this free Linux kernel development course tutorial, you will learn one of the most fundamental concepts in the Linux kernel — the task structure, formally known as struct task_struct. Every process and every thread that runs on a Linux system is represented internally by one of these structures. Without understanding struct task_struct, you cannot fully understand process management, scheduling, memory management, or Linux device drivers development. This tutorial is designed to give you a clear, practical understanding from the ground up.

Whether you are pursuing a career in embedded systems, Linux kernel programming, or Linux device drivers development, mastering the task structure is a critical milestone. This is part of our completely free Linux kernel development course at EmbeddedPathashala.

📚 What You Will Learn
  • What struct task_struct is and why it exists in the Linux kernel
  • How the Linux kernel represents every process and thread using a single data structure
  • Key members of the task structure — state, PID, memory, credentials, scheduling, and more
  • How all task structures are organized in memory (the task list)
  • How to access the current task structure from inside a kernel module using the current macro
  • The difference between what is inherited and what is reset on fork()
  • How to iterate over all tasks running on the system using kernel macros
  • Practical kernel module code demonstrating task structure access
✅ Prerequisites
  • Basic understanding of C programming (pointers, structs)
  • Familiarity with Linux processes and threads at the user-space level
  • Knowledge of what a kernel module is (refer to our earlier free Linux kernel development course modules)
  • A Linux system with kernel headers installed (Ubuntu/Fedora/Debian recommended)

What is the Linux Kernel Task Structure?

When you run any program on a Linux system — a web browser, a shell script, a background daemon, or even a kernel thread — the kernel needs a place to keep track of everything about that running entity. It needs to know the program’s current state (is it sleeping? running? stopped?), which CPU it is on, which files it has open, what its memory layout looks like, its user credentials, its signal handlers, and dozens of other details.

The Linux kernel solves this by defining a large C structure called struct task_struct. This structure is the complete representation of a task. In Linux kernel terminology, both processes and threads are called tasks. Each task — whether it is a user-space process, a user-space thread, or a kernel thread — gets exactly one task_struct instance allocated in kernel memory.

Think of struct task_struct as the “identity card + passport + medical record + property deed” of a running task, all bundled into one. Everything the kernel needs to know about a task is either directly stored in task_struct or accessible through pointers hanging off it.

Conceptual View: Linux Kernel Task Structure
struct task_struct
Root descriptor of every task
🗓 State & Lifecycle
🆔 PID / TGID
🧠 Memory (mm_struct)
📅 CPU Scheduling
📂 Open Files
🔐 Credentials
📡 Signal Handlers
🔒 Security (LSM)
⏱ Timers / Alarms
🔗 IPC Structures
🧵 Thread TLS
📊 Resource Limits

Every running task (process or thread) on Linux has exactly one task_struct in kernel memory

Where is struct task_struct Defined in the Linux Kernel?

The task structure is defined in the kernel header file include/linux/sched.h. This is one of the most important and most included headers in the entire Linux kernel source tree. Almost every subsystem of the kernel — scheduling, memory management, virtual file system, networking, security — ends up pulling in this header because almost every subsystem needs to work with tasks.

On a modern kernel (6.x series), the task_struct is enormous — well over 700 members. Its size on a 64-bit x86 system can exceed 10,000 bytes. This reflects just how much information the kernel needs to maintain per task to support preemptive multitasking, security isolation, resource accounting, and all the other services a modern OS provides.

You can explore it yourself on your system:

# Find where task_struct is defined
$ grep -n "struct task_struct {" /usr/src/linux-headers-$(uname -r)/include/linux/sched.h

# Count the number of members (approximate)
$ grep -c ";" /usr/src/linux-headers-$(uname -r)/include/linux/sched.h
💡 Note: The exact members and their layout in struct task_struct change between kernel versions. Always check the kernel source for the version you are actually working with using uname -r. This tutorial describes concepts valid for Linux 6.x kernels.

Key Members of the Linux Task Structure

Rather than going through every single field (which would take a book), let’s focus on the members that matter most for anyone studying Linux kernel programming or Linux device drivers development. We group them by category.

3.1 Task State

The __state member (older kernels used state) tracks what the task is currently doing. This is a bitmask field. The most common states are:

Linux Task States
Macro / Constant Value Meaning
TASK_RUNNING 0x00000000 Task is runnable — either currently executing on a CPU or waiting in the run queue
TASK_INTERRUPTIBLE 0x00000001 Sleeping, waiting for an event; can be woken up by a signal
TASK_UNINTERRUPTIBLE 0x00000002 Sleeping; cannot be interrupted by signals (used for critical I/O waits)
__TASK_STOPPED 0x00000004 Execution stopped (e.g. by SIGSTOP or a debugger)
__TASK_TRACED 0x00000008 Being traced by a debugger (e.g. ptrace from gdb)
TASK_DEAD 0x00000080 Task has exited; the task_struct is kept alive until the parent calls wait()

In a kernel module, you can read the state of any task whose task_struct pointer you have. For example:

#include <linux/sched.h>

/* Assume 'task' is a valid struct task_struct pointer */
if (task_is_running(task))
    pr_info("Task %s is currently runnable\n", task->comm);
✅ Modern Kernel Tip: On kernels 5.14+, the state field was renamed from state to __state and direct assignment was replaced by helper functions like set_current_state(). Always use the provided helper macros rather than directly touching __state.

3.2 PID, TGID, and How Linux Identifies Tasks

Every task in Linux has two important numeric identifiers stored in task_struct:

  • pid (Process ID): Uniquely identifies this specific task (thread) in the kernel. Each thread gets its own unique PID at the kernel level.
  • tgid (Thread Group ID): All threads belonging to the same process share the same TGID. This is what getpid() returns in user space — the TGID of the thread group leader.
PID vs TGID: How Linux Tracks Processes and Threads
Process (main thread)
pid = 1000
tgid = 1000
comm = “myapp”
getpid() returns 1000
⟶
Thread 1 (spawned)
pid = 1001
tgid = 1000
comm = “myapp”
getpid() returns 1000 (same TGID)
⟶
Thread 2 (spawned)
pid = 1002
tgid = 1000
comm = “myapp”
getpid() returns 1000 (same TGID)

Each thread has a unique kernel-level PID, but all share the same TGID (visible as PID in user space via getpid())

This distinction is why the output of ps -eLf shows more entries than ps -ef: the former shows kernel-level PIDs (one per thread), while the latter shows user-visible PIDs (which are actually TGIDs).

# Show all threads with their kernel-level PID (LWP column)
$ ps -eLf | head -20

# See all threads of a specific process
$ ps -eLf | grep myapp

3.3 Task Name (comm)

The comm member stores the name of the task as a short character array. On modern kernels this is 16 bytes (including the null terminator), so task names are limited to 15 characters. This is the name you see in the output of tools like top, htop, and /proc/<pid>/comm.

/* Print the name of the current task from a kernel module */
#include <linux/sched.h>
#include <linux/kernel.h>

pr_info("Current task name: %s\n", current->comm);
pr_info("Current task PID:  %d\n", current->pid);
pr_info("Current task TGID: %d\n", current->tgid);

3.4 Memory Management: mm_struct

Each task has a pointer called mm that points to a struct mm_struct. This structure describes the virtual address space of the process — where the code segment is, where the stack is, the heap, memory-mapped regions, and the page table root. Threads within the same process share the same mm_struct, which is how they share virtual memory.

There is also an active_mm pointer. For regular user processes, mm and active_mm point to the same structure. For kernel threads, mm is NULL (kernel threads have no user-space virtual address space), but active_mm temporarily borrows the mm of the most recently scheduled user process. This “lazy TLB” trick avoids expensive TLB flushes when the kernel thread does not actually access user memory.

mm vs active_mm in task_struct
User Process
mm ──────────┐
active_mm ──┘
mm_struct
(process VMA)
Both point to same mm_struct
|
Kernel Thread
mm ──── NULL
active_mm ──┐
Borrowed mm_struct
(from last user task)
mm is NULL; active_mm is borrowed
⚠️ Device Driver Tip: In a kernel module (which runs in process context), you can check current->mm to determine if the current task is a kernel thread (mm == NULL) or a user process (mm != NULL). This check is often used when accessing user-space memory from a driver.

3.5 Credentials and Capabilities

Security in Linux is built around the concept of task credentials. The cred member in task_struct points to a struct cred that stores:

  • UID, GID (real and effective)
  • Supplementary group list
  • Linux capability sets (permitted, effective, inheritable, bounding, ambient)
  • Security labels (used by SELinux, AppArmor)

The credential structure uses Copy-On-Write (COW) semantics. A task’s credentials can be replaced atomically using commit_creds(), which is how su, sudo, and setuid programs elevate privileges safely.

3.6 Open Files (files_struct)

The files member points to a struct files_struct, which maintains the table of open file descriptors for the task. File descriptors 0, 1, and 2 (stdin, stdout, stderr) are entries in this table. When a process opens a file, a new file descriptor entry is added here. When threads are created, they typically share the same files_struct, which is why threads within a process see the same file descriptors.

3.7 CPU Scheduling Information

The task structure contains several scheduling-related fields. The most important ones for a kernel programming student to know are:

Key Scheduling Members in task_struct
Member Type Purpose
prio int Dynamic priority used by the scheduler
static_prio int Nice value converted to kernel priority (not changed by scheduler)
rt_priority unsigned int Real-time priority (1–99 for SCHED_FIFO / SCHED_RR)
sched_class struct sched_class * Pointer to the scheduler class (CFS, RT, deadline, idle)
se struct sched_entity CFS scheduling entity (tracks virtual runtime for fairness)

The Task List: How the Kernel Organizes All task_struct Objects

At any given moment, a busy Linux system may have thousands of tasks active simultaneously. The kernel needs to organize all these task_struct objects so they can be found and iterated efficiently. It achieves this using an intrusive circular doubly linked list called the task list.

The task_struct contains a member of type struct list_head named tasks. This embeds the linked list node directly inside the task structure — this is the “intrusive” linked list pattern that is extremely common throughout the Linux kernel. All task structures are chained together through their tasks member, forming a ring.

The Linux Kernel Task List (Circular Doubly Linked List)
init_task
(PID 0)
swapper
next
⟶
prev
⟵
task_struct
(PID 1)
systemd
next
⟶
prev
⟵
task_struct
(PID 2)
kthreadd
next
⟶
prev
⟵
… more tasks …
wraps back to init_task

All task_struct objects are linked via their embedded tasks list_head member. The list is circular — the last entry links back to init_task.

The kernel provides a convenient macro for_each_process(p) to iterate over all processes in this list. Under the hood it uses list_for_each_entry() starting from the global init_task (the idle process, PID 0).

#include <linux/sched/signal.h>
#include <linux/init_task.h>

struct task_struct *task;

/* Iterate over all processes (thread group leaders only) */
for_each_process(task) {
    pr_info("Process: %-16s  PID: %d  State: %ld\n",
            task->comm, task->pid, task_state_index(task));
}

/* To iterate over ALL tasks including threads, use: */
/* for_each_process_thread(p, t) { ... } */
⚠️ Warning: You must hold the tasklist_lock (read lock) or use RCU locking (rcu_read_lock() / rcu_read_unlock()) when iterating over the task list. Doing this without proper locking can lead to race conditions and kernel crashes. In modern kernels, RCU is preferred over tasklist_lock for read-side access.

What Gets Inherited Across fork() — and What Does Not

When a process calls fork() (or when a thread is created via pthread_create(), which internally calls clone()), a new task_struct is allocated for the child. The kernel copies most fields from the parent’s task structure to the child’s, but a number of fields are reset or treated differently. Understanding this is important for anyone studying Linux kernel development or device drivers.

What Happens to task_struct Members on fork()
✅ Inherited by Child
  • 📂 Open file table (files_struct)
  • 🧠 Virtual address space (mm_struct — COW)
  • 🔐 Credentials (cred)
  • 📡 Signal handlers (sighand_struct)
  • 🌐 Namespaces
  • 🔗 Paging tables (COW)
  • 📦 VFS data (filesystem context)
  • 🔒 Security attributes (LSM labels)
❌ Not Inherited (Reset)
  • 🆔 PID (child gets a new unique PID)
  • ⏱ Timers and alarms (reset)
  • 🔔 Pending signals (cleared)
  • 🔑 Locks held (not inherited)
  • 📊 AIO contexts (reset)
  • 🧾 Audit info
  • 📈 CPU usage counters (reset)
  • 🧵 Kernel-mode stack (new stack allocated)

Common Mistakes When Working with task_struct in Kernel Modules

⚠️ Mistake 1: Accessing task_struct without proper locking

Task structures can be freed when a task exits. If you hold a pointer to a task_struct without getting a reference via get_task_struct(), the structure can be freed under you, causing a use-after-free kernel bug. Always call get_task_struct(task) to increment the reference count, and put_task_struct(task) when done.

⚠️ Mistake 2: Iterating the task list without RCU

Never use for_each_process() outside of an RCU read-side critical section. The task list can be modified concurrently by other CPUs. Use rcu_read_lock() before iteration and rcu_read_unlock() after.

⚠️ Mistake 3: Directly modifying task_struct fields

Many fields in task_struct have specific helper functions for modification. Directly writing to __state, priority fields, or credentials without using the proper kernel APIs can corrupt kernel state and cause panics. Always use the provided helper functions.

⚠️ Mistake 4: Assuming task_struct layout is stable across kernel versions

The layout and members of task_struct change frequently between kernel versions. Code that accesses specific offsets directly will break. Always compile your kernel module against the exact kernel headers for the running kernel.

🎯 Key Takeaways — Part 1
  • struct task_struct is the central data structure representing every process and thread in the Linux kernel
  • Every task — user process, user thread, or kernel thread — has exactly one task_struct instance
  • It stores state, PID/TGID, memory info, credentials, open files, scheduling parameters, signal handlers, and much more
  • All task structures are linked together in a circular doubly linked list called the task list
  • Processes and threads share many task_struct fields (credentials, mm_struct, files) but each gets unique values for PID, kernel stack, and more
  • Always use proper RCU locking and reference counting when accessing task_struct from kernel code

1 Comment

Leave a Reply

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