task_struct Deep Dive
Introduction
The task_struct is the most important data structure in the Linux kernel. Every schedulable entity — process, thread, kernel thread — is represented by one instance. It’s a massive structure (over 5 KB on x86-64) that contains everything the kernel needs to manage a task: scheduling state, memory mappings, open files, signal handling, credentials, cgroup membership, and much more.
Understanding task_struct is essential for anyone working with the Linux kernel at a systems level. This chapter dissects its major fields, explains how it’s allocated and organized, and shows how the kernel uses it.
Allocation and Layout
Historical Evolution
The allocation strategy for task_struct has evolved significantly:
| Era | Strategy | Details |
|---|---|---|
| Linux 2.4 and earlier | Embedded in kernel stack | task_struct lived at the bottom of the 8KB kernel stack |
| Linux 2.6+ | Slab-allocated + thread_info | task_struct on slab cache, thread_info on kernel stack |
| Linux 3.10+ | thread_info reduced | Minimal thread_info on stack, most data in task_struct |
| Linux 4.9+ (x86) | Stack-based thread_info removed | thread_info embedded in task_struct |
Modern Linux allocates task_struct via the SLAB/SLUB allocator and keeps only a small thread_info (or nothing, on some architectures) on the kernel stack.
/* include/linux/sched.h - simplified */
struct task_struct {
struct thread_info thread_info; /* must be first */
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
int on_cpu;
int on_rq;
int prio;
int static_prio;
int normal_prio;
unsigned int rt_priority;
const struct sched_class *sched_class;
struct sched_entity se;
struct sched_rt_entity rt;
struct sched_dl_entity dl;
struct sched_info sched_info;
/* ... hundreds more fields ... */
};
Allocation with SLUB
/* kernel/fork.c */
struct task_struct *alloc_task_struct_node(int node)
{
return kmem_cache_alloc(task_struct_cachep, GFP_KERNEL);
}
/* During kernel boot, the cache is created: */
void __init fork_init(void)
{
/* Create a slab cache for task_struct objects */
task_struct_cachep = kmem_cache_create("task_struct",
arch_task_struct_size, align,
SLAB_PANIC|SLAB_ACCOUNT, NULL);
}
The arch_task_struct_size allows architectures to extend task_struct with architecture-specific fields (e.g., struct thread_struct for FPU state, debug registers).
The Kernel Stack
Each task has a kernel stack, typically 8KB (2 pages) on most architectures, or 16KB on some:
┌─────────────────────────┐ High address
│ │
│ Kernel Stack │ ← stack grows downward
│ (8KB / 16KB) │
│ │
├─────────────────────────┤
│ thread_info │ (on architectures that still use it)
│ (embedded at bottom) │
└─────────────────────────┘ Low address
/* For x86-64, THREAD_SIZE is 16KB (4 pages) */
#define THREAD_SIZE (4096 * 4)
/* The stack is allocated in fork */
static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
{
struct task_struct *tsk;
unsigned long *stack;
tsk = alloc_task_struct_node(node);
stack = alloc_thread_stack_node(tsk, node);
tsk->stack = stack;
/* ... */
}
Key Field Groups
1. Identity Fields
struct task_struct {
pid_t pid; /* Process ID (thread ID in kernel terms) */
pid_t tgid; /* Thread group ID (userspace PID) */
/* Parent-child relationships */
struct task_struct __rcu *real_parent;
struct task_struct __rcu *parent;
struct list_head children;
struct list_head sibling;
/* Names */
char comm[TASK_COMM_LEN]; /* Executable name (16 chars max) */
/* Credentials */
const struct cred __rcu *cred; /* Effective credentials */
const struct cred __rcu *real_cred; /* Objective credentials */
/* Session/Process group (for job control) */
pid_t session;
pid_t pgrp;
};
# View identity fields from /proc
$ cat /proc/self/status | grep -E '^(Name|Pid|PPid|Tgid|Uid|Gid|NStgid|NSpid)'
Name: cat
Pid: 12345
PPid: 500
Tgid: 12345
Uid: 1000 1000 1000 1000
Gid: 1000 1000 1000 1000
NStgid: 12345
NSpid: 12345
2. Scheduling Fields
These fields control how the task is scheduled:
struct task_struct {
int prio; /* Dynamic priority (lower = higher priority) */
int static_prio; /* Set by user (nice value converted) */
int normal_prio; /* Priority based on scheduling policy */
unsigned int rt_priority; /* Real-time priority (0-99) */
const struct sched_class *sched_class; /* Scheduling class (fair, rt, dl) */
struct sched_entity se; /* CFS/EEVDF scheduling entity */
struct sched_rt_entity rt; /* RT scheduling entity */
struct sched_dl_entity dl; /* Deadline scheduling entity */
unsigned int policy; /* SCHED_NORMAL, SCHED_FIFO, etc. */
int nr_cpus_allowed;
cpumask_t cpus_mask; /* CPUs this task may run on */
unsigned long wake_entry; /* Used by scheduler for wake-up */
int on_rq; /* Is it on a run queue? */
int on_cpu; /* Currently running on a CPU? */
int cpu; /* Which CPU? */
int recent_used_cpu; /* Last CPU used (for cache affinity) */
};
3. Memory Management Fields
struct task_struct {
struct mm_struct *mm; /* Memory descriptor (user space) */
struct mm_struct *active_mm; /* Active mm (for kernel threads borrowing user mm) */
/* For kernel threads: mm is NULL, active_mm is borrowed */
/* For user tasks: mm == active_mm */
};
The mm_struct (see Memory Management) contains:
- Page table root (
pgd) - Virtual memory areas (
vm_area_structlinked list and red-black tree) - Code, data, heap, and stack segment boundaries
mmap_lock(read-write semaphore protecting the address space)
# View memory layout
$ cat /proc/self/maps
55a8f4000000-55a8f4020000 r-xp 00000000 08:01 131074 /usr/bin/cat
55a8f4220000-55a8f4221000 r--p 00020000 08:01 131074 /usr/bin/cat
55a8f4221000-55a8f4222000 rw-p 00021000 08:01 131074 /usr/bin/cat
7f8c1c000000-7f8c1c021000 rw-p 00000000 00:00 0 [heap]
7f8c1c021000-7f8c1c200000 ---p 00000000 00:00 0
7ffc12340000-7ffc12361000 rw-p 00000000 00:00 0 [stack]
4. File System and Files
struct task_struct {
/* Filesystem info */
struct fs_struct *fs; /* CWD, root directory, umask */
/* Open file descriptors */
struct files_struct *files; /* File descriptor table */
};
/* include/linux/fs_struct.h */
struct fs_struct {
int users;
spinlock_t lock;
seqcount_t seq;
int umask;
int in_exec;
struct path root; /* Root filesystem path */
struct path pwd; /* Current working directory */
};
Threads in the same group share fs and files (set by CLONE_FS and CLONE_FILES).
5. Signal Handling
struct task_struct {
struct signal_struct *signal; /* Shared by all threads in group */
struct sighand_struct *sighand; /* Signal handlers (may be shared) */
sigset_t blocked; /* Blocked signals (per-thread) */
sigset_t real_blocked;
struct sigpending pending; /* Per-thread pending signals */
unsigned long sas_ss_sp; /* Alternate signal stack pointer */
size_t sas_ss_size; /* Alternate signal stack size */
};
/* include/linux/sched/signal.h */
struct signal_struct {
sigset_t shared_pending; /* Shared pending signals (process-directed) */
/* ... */
int group_exit_code; /* Exit code for the group */
int group_stop_count; /* For SIGSTOP/SIGCONT */
/* ... */
};
struct sighand_struct {
spinlock_t siglock;
struct k_sigaction action[_NSIG]; /* Signal handler table */
};
6. Credentials and Security
struct task_struct {
const struct cred __rcu *cred; /* Effective credentials */
const struct cred __rcu *real_cred; /* Objective credentials */
};
struct cred {
kuid_t uid; /* User ID */
kgid_t gid; /* Group ID */
kuid_t suid; /* Saved UID */
kgid_t sgid; /* Saved GID */
kuid_t euid; /* Effective UID */
kgid_t egid; /* Effective GID */
kuid_t fsuid; /* UID for filesystem checks */
kgid_t fsgid; /* GID for filesystem checks */
unsigned securebits; /* Security bits */
kernel_cap_t cap_inheritable;
kernel_cap_t cap_permitted;
kernel_cap_t cap_effective;
kernel_cap_t cap_bset; /* Capability bounding set */
kernel_cap_t cap_ambient;
/* ... */
};
7. Namespace and Cgroup Fields
struct task_struct {
/* Namespaces */
struct nsproxy *nsproxy;
/* Cgroup */
struct css_set __rcu *cgroups;
struct list_head cg_list;
};
8. Debugging and Tracing
struct task_struct {
unsigned ptrace; /* Ptrace flags */
struct list_head ptraced;
struct list_head ptrace_entry;
/* Performance events */
struct perf_event_context *perf_event_ctxp;
struct list_head perf_event_list;
/* Audit */
struct audit_context *audit_context;
};
The current Macro
The kernel frequently needs to access the task_struct of the currently running task. This is done via the current macro, which is architecture-specific:
/* x86-64: include/asm-generic/current.h */
/* The per-CPU variable 'current_task' is maintained by the scheduler */
DECLARE_PER_CPU(struct task_struct *, current_task);
static __always_inline struct task_struct *get_current(void)
{
return this_cpu_read_stable(current_task);
}
#define current get_current()
On x86-64, the GS base register points to the per-CPU area, making current a fast per-CPU variable read. On some architectures (like older ARM), current is derived from the kernel stack pointer:
/* ARM32: deriving current from stack pointer */
#define current (struct task_struct *)(thread_info->task)
/* where thread_info is at the bottom of the kernel stack */
Process List Traversal
The kernel provides several macros for iterating over task lists:
/* Iterate over all processes in the system */
struct task_struct *p;
for_each_process(p) {
printk(KERN_INFO "PID: %d, CMD: %s\n", p->pid, p->comm);
}
/* Iterate over all threads of a thread group */
struct task_struct *t;
for_each_thread(p, t) {
printk(KERN_INFO "TID: %d\n", t->pid);
}
The for_each_process macro walks the init_task.tasks list:
/* include/linux/sched/signal.h */
#define for_each_process(p) \
for (p = &init_task; (p = next_task(p)) != &init_task; )
#define next_task(p) \
list_entry_rcu((p)->tasks.next, struct task_struct, tasks)
flowchart LR
init["init_task"] -->|"tasks.next"| P1["Process 1"]
P1 -->|"tasks.next"| P2["Process 2"]
P2 -->|"tasks.next"| P3["Process 3"]
P3 -->|"tasks.next"| init
Finding Tasks by PID
The kernel uses a hash table for PID-to-task lookups:
/* kernel/pid.c */
struct task_struct *find_task_by_vpid(pid_t vnr)
{
return find_task_by_pid_ns(vnr, current->nsproxy->pid_ns_for_children);
}
struct task_struct *find_task_by_pid_ns(pid_t nr, struct pid_namespace *ns)
{
return pid_task(find_pid_ns(nr, ns), PIDTYPE_PID);
}
# Userspace PID lookup via /proc
$ ls /proc/1/
attr/ cgroup comm cwd@ exe@ fd/ ...
$ cat /proc/1/comm
systemd
Reference Counting and Lifecycle
task_struct Lifecycle
stateDiagram-v2
[*] --> alloc_task_struct: fork()
alloc_task_struct --> copy_process: dup_task_struct()
copy_process --> on_rq: wake_up_new_task()
on_rq --> running: schedule()
running --> on_rq: schedule()
running --> zombie: do_exit()
zombie --> [*]: release_task()
The task_struct is reference-counted. When a task exits:
do_exit()is called — the task becomes a zombie- The parent receives
SIGCHLD - The parent calls
wait()→release_task() release_task()frees thetask_structand kernel stack
/* kernel/exit.c - simplified */
void release_task(struct task_struct *p)
{
/* ... */
/* Decrement reference count, free if zero */
put_task_struct(p);
}
/* include/linux/sched/task.h */
static inline void put_task_struct(struct task_struct *t)
{
refcount_dec_and_test(&t->usage);
if (unlikely(refcount_read(&t->usage) == 0))
__put_task_struct(t);
}
void __put_task_struct(struct task_struct *tsk)
{
/* ... cleanup ... */
free_task(tsk); /* Frees task_struct and kernel stack */
}
Practical Examples
Reading task_struct from /proc
# Get comprehensive task information
$ cat /proc/self/status
Name: bash
Umask: 0022
State: S (sleeping)
Tgid: 500
Ngid: 0
Pid: 500
PPid: 1
TracerPid: 0
Uid: 1000 1000 1000 1000
Gid: 1000 1000 1000 1000
FDSize: 256
Groups: 27 1000
NStgid: 500
NSpid: 500
NSpgid: 500
NSsid: 500
VmPeak: 12340 kB
VmSize: 12340 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 5120 kB
VmRSS: 5120 kB
RssAnon: 2048 kB
RssFile: 3072 kB
RssShmem: 0 kB
Threads: 1
Inspecting task_struct with crash (kernel debugger)
# Start crash on a live system or vmcore
$ crash /usr/lib/debug/boot/vmlinux-$(uname -r) /proc/kcore
# Display current task_struct
crash> struct task_struct
struct task_struct {
struct thread_info thread_info;
volatile long state;
...
}
# Look at a specific task
crash> task 1
PID: 1 TASK: ffff888001234500 COMMAND: "systemd"
struct task_struct {
thread_info = {
flags = 0,
cpu = 0
},
state = 1,
stack = 0xffffc90000000000,
...
}
# Show all tasks in a thread group
crash> task -t 500
PID: 500 TASK: ffff888006789000 COMMAND: "bash"
PID: 501 TASK: ffff888006789200 COMMAND: "bash"
PID: 502 TASK: ffff888006789400 COMMAND: "bash"
Programmatic Access via /proc
# Get open files
$ ls -la /proc/self/fd
lrwx------ 1 user user 64 Jan 1 00:00 0 -> /dev/pts/0
lrwx------ 1 user user 64 Jan 1 00:00 1 -> /dev/pts/0
lrwx------ 1 user user 64 Jan 1 00:00 2 -> /dev/pts/0
# Get memory maps
$ cat /proc/self/maps | head -5
55a8f4000000-55a8f4020000 r-xp 00000000 08:01 131074 /usr/bin/cat
# Get cgroup membership
$ cat /proc/self/cgroup
0::/user.slice/user-1000.slice/session-1.scope
# Get namespace information
$ ls -la /proc/self/ns/
lrwxrwxrwx 1 user user 0 Jan 1 00:00 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 user user 0 Jan 1 00:00 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 user user 0 Jan 1 00:00 mnt -> 'mnt:[4026531840]'
lrwxrwxrwx 1 user user 0 Jan 1 00:00 net -> 'net:[4026531840]'
lrwxrwxrwx 1 user user 0 Jan 1 00:00 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 user user 0 Jan 1 00:00 user -> 'user:[4026531837]'
lrwxrwxrwx 1 user user 0 Jan 1 00:00 uts -> 'uts:[4026531838]'
Size and Performance Considerations
The task_struct is large, and its layout matters for cache performance:
# Check actual size on your kernel
$ pahole -C task_struct /usr/lib/debug/boot/vmlinux-$(uname -r) | wc -l
# Typically 500+ lines
# The structure is > 5KB on most configurations
$ pahole -C task_struct /usr/lib/debug/boot/vmlinux-$(uname -r) | tail -1
/* size: 5120, cachelines: 80, members: 200+ */
The kernel developers carefully organize frequently-accessed fields into the same cache lines:
/*
* Fields accessed during scheduling (hot path) are grouped at the top:
* - state, prio, sched_class, on_rq, on_cpu
* These are accessed every time schedule() runs.
*/
Further Reading
Related Topics
- Processes and Threads — The conceptual model
- Process Creation — How
task_structis created duringfork() - Process States — The
statefield and its transitions - Scheduler Overview — How scheduling fields are used
- Context Switching — How
task_structis swapped during context switches