How Does the current Macro Work in Linux? – Free Linux Device Driver Training

Accessing task_struct: The current Macro and Task Iteration

Free Linux Kernel Development Course — EmbeddedPathashala | Part 2

Part 2
current Macro & Task Iteration
Free
Linux Kernel Course
Practical
Kernel Module Code

The current Macro and Task Iteration in Linux Kernel Programming

Welcome to Part 2 of our free Linux kernel development course tutorial on struct task_struct. In Part 1, we explored what the task structure is, its key members, and how all task structures are organized in a linked list. Now we dive into the most practical part: how to access the task structure from your own kernel module code, how to iterate over all running tasks, and how to build a real kernel module that works with task information. This is directly applicable to Linux device drivers development and embedded systems kernel work.

📚 What You Will Learn in Part 2
  • How the current macro works and what it gives you
  • The architecture-specific implementation strategy behind current
  • How to safely use current in a kernel module — and its limitations
  • How to iterate over all processes and threads using for_each_process() and for_each_process_thread()
  • How to find a task by PID using find_get_pid() and pid_task()
  • A complete practical kernel module that demonstrates task structure access
  • Best practices for safe task_struct access in production kernel code

The current Macro: Your Gateway to the Running Task’s task_struct

When you write kernel code — a system call handler, a device driver interrupt handler, a kernel module’s proc file read handler — you are always executing in the context of some task. The question is: how do you find out which task is running this code right now, and how do you get a pointer to its task_struct?

The answer is the current macro. This macro evaluates to a pointer of type struct task_struct *, pointing to the task structure of the task that is currently executing on the current CPU. It is the single most used macro in all of Linux kernel programming.

#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/module.h>

static int __init my_module_init(void)
{
    /* 'current' gives us the task_struct of whoever loaded this module */
    pr_info("Module loaded by process: %s (PID: %d)\n",
            current->comm, current->pid);

    pr_info("TGID (user-visible PID): %d\n", current->tgid);
    pr_info("Effective UID: %d\n",
            from_kuid(&init_user_ns, current_euid()));

    return 0;
}

static void __exit my_module_exit(void)
{
    pr_info("Module unloaded by: %s (PID: %d)\n",
            current->comm, current->pid);
}

module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");

When you run insmod my_module.ko, the my_module_init function runs in the context of the shell process that invoked insmod. So current->comm will show something like insmod and current->pid will show the PID of the insmod process.

How Does the current Macro Work Internally?

The implementation of current is architecture-dependent, but the design goal is always the same: return the pointer to the running task’s task_struct as fast as possible, ideally in O(1) time without touching memory unnecessarily.

How current is Implemented Across Architectures
x86 / x86_64

Uses a per-CPU variable stored in a special per-CPU area. The kernel keeps a per-CPU pointer to the current task, updated by the scheduler on every context switch. Accessing it requires reading from the per-CPU storage, which is very fast on modern CPUs.

ARM / ARM64

On older ARM, the task pointer was derived from the stack pointer (since each task has a fixed-size kernel stack and the task structure was at the bottom). On modern ARM64 with VMAP_STACK, a dedicated register or per-CPU variable is used instead.

RISC-V / Other RISC

RISC architectures with many general-purpose registers dedicate one register to always hold the current task pointer. This makes current a single register read — as fast as it gets.

Despite different implementations, all architectures guarantee O(1) access to the current task_struct pointer

From the kernel module programmer’s perspective, you never need to know or care about these differences. The current macro abstracts all of this away — just include <linux/sched.h> and use it.

When current is Valid — and When It Is Not

The current macro is only meaningful in process context — that is, when kernel code is running on behalf of a specific user-space process. This includes system call handlers, kernel module init/exit functions, and work queue callbacks.

In interrupt context (top-half IRQ handlers, softirqs, tasklets), there is technically a “current” task, but it is whatever task happened to be running when the interrupt fired. The interrupt has nothing to do with that task. So accessing current in an interrupt handler gives you a meaningless value — it does not represent the task that triggered the interrupt.

Process Context vs Interrupt Context: Where current is Meaningful
✅ Process Context
(current is valid)
  • System call handlers
  • Kernel module init/exit
  • Character device read/write/ioctl
  • proc/sysfs file operations
  • Kernel threads (current = the kthread itself)
  • Work queue handlers
⚠️ Interrupt Context
(current is meaningless)
  • Hardware IRQ handlers (top half)
  • Softirq handlers
  • Tasklet handlers
  • Timer callbacks (non-hrtimer work)
current exists but refers to whatever process was preempted — not the one that caused the interrupt

You can check whether you are in interrupt context using the in_interrupt() macro. A well-written Linux device driver should always be aware of which context it is executing in.

#include <linux/interrupt.h>

void my_function(void)
{
    if (in_interrupt()) {
        /* We are in interrupt context - do NOT trust current */
        pr_warn("Running in interrupt context\n");
    } else {
        /* Safe to use current here */
        pr_info("Running in process context, task: %s\n", current->comm);
    }
}

Finding a Task by PID in Kernel Code

Sometimes you need to get the task_struct of a specific process given its PID — for example, from a kernel module that receives a PID from user space via ioctl. The correct way to do this on modern kernels involves two steps: convert the integer PID to a struct pid * (the kernel’s internal PID representation), then get the task from that.

#include <linux/pid.h>
#include <linux/sched.h>

struct task_struct *get_task_by_pid_nr(pid_t nr)
{
    struct task_struct *task = NULL;
    struct pid *pid_struct;

    /* Convert integer PID to struct pid (RCU protected) */
    rcu_read_lock();
    pid_struct = find_get_pid(nr);
    if (pid_struct) {
        task = pid_task(pid_struct, PIDTYPE_PID);
        if (task)
            get_task_struct(task);   /* Increment reference count */
        put_pid(pid_struct);
    }
    rcu_read_unlock();

    return task;   /* Caller must call put_task_struct(task) when done */
}

/* Usage example */
void example_usage(pid_t target_pid)
{
    struct task_struct *task = get_task_by_pid_nr(target_pid);
    if (!task) {
        pr_warn("No task found for PID %d\n", target_pid);
        return;
    }

    pr_info("Found task: %s (PID: %d)\n", task->comm, task->pid);

    /* Always release the reference when done */
    put_task_struct(task);
}
✅ Why struct pid? The Linux kernel uses an internal struct pid rather than raw integer PIDs for all internal task lookups. This is because integer PIDs can be reused after a process exits. The struct pid keeps a reference-counted object that remains valid until explicitly released, avoiding the PID recycling problem.

Complete Practical Kernel Module: Listing All Tasks

Let’s bring everything together with a practical kernel module that iterates over all running tasks and prints key information about each one. This is a clean, safe, well-commented example that follows modern kernel coding practices and is a great exercise for anyone following our free Linux kernel development course.

Module Source Code

/*
 * task_info.c — Kernel module to iterate and display all running tasks
 * Part of EmbeddedPathashala Free Linux Kernel Development Course
 *
 * Build: make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
 * Load:  sudo insmod task_info.ko
 * View:  dmesg | tail -50
 * Remove: sudo rmmod task_info
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/sched/signal.h>   /* for_each_process, for_each_process_thread */
#include <linux/mm_types.h>
#include <linux/rcupdate.h>

MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("Display all running task info — Free Linux Kernel Course");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.0");

/* Helper: return a human-readable task state string */
static const char *get_task_state_str(struct task_struct *t)
{
    unsigned int state = READ_ONCE(t->__state);

    if (state == TASK_RUNNING)
        return "RUNNING/RUNNABLE";
    if (state & TASK_INTERRUPTIBLE)
        return "SLEEPING(interruptible)";
    if (state & TASK_UNINTERRUPTIBLE)
        return "SLEEPING(uninterruptible)";
    if (state & __TASK_STOPPED)
        return "STOPPED";
    if (state & __TASK_TRACED)
        return "TRACED";
    return "UNKNOWN";
}

static int __init task_info_init(void)
{
    struct task_struct *process, *thread;
    unsigned long process_count = 0, thread_count = 0;

    pr_info("=== EmbeddedPathashala: Task Info Module Loaded ===\n");
    pr_info("Loaded by: %s (PID %d, TGID %d)\n",
            current->comm, current->pid, current->tgid);

    pr_info("\n%-20s %6s %6s %-10s %s\n",
            "Name", "PID", "TGID", "State", "mm?");
    pr_info("%-20s %6s %6s %-10s %s\n",
            "--------------------", "------", "------",
            "----------", "---");

    /*
     * Iterate over all tasks safely using RCU.
     * for_each_process_thread gives us every task including threads.
     */
    rcu_read_lock();

    for_each_process_thread(process, thread) {
        pr_info("%-20s %6d %6d %-22s %s\n",
                thread->comm,
                thread->pid,
                thread->tgid,
                get_task_state_str(thread),
                thread->mm ? "user" : "kthread");
        thread_count++;
        if (thread == process)
            process_count++;
    }

    rcu_read_unlock();

    pr_info("\n=== Summary: %lu processes, %lu total tasks (threads) ===\n",
            process_count, thread_count);

    return 0;  /* 0 = success, module stays loaded */
}

static void __exit task_info_exit(void)
{
    pr_info("=== EmbeddedPathashala: Task Info Module Unloaded ===\n");
}

module_init(task_info_init);
module_exit(task_info_exit);

Makefile

obj-m += task_info.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Building and Running

# Step 1: Install kernel headers for your running kernel
$ sudo apt install linux-headers-$(uname -r)   # Ubuntu/Debian
# or
$ sudo dnf install kernel-devel                  # Fedora

# Step 2: Build the module
$ make

# Step 3: Load it
$ sudo insmod task_info.ko

# Step 4: View the output
$ dmesg | tail -80

# Step 5: Remove the module
$ sudo rmmod task_info

# Step 6: Verify it's unloaded
$ lsmod | grep task_info

Sample Output

[12345.678901] === EmbeddedPathashala: Task Info Module Loaded ===
[12345.678902] Loaded by: insmod (PID 4521, TGID 4521)
[12345.678903]
[12345.678904] Name                    PID   TGID State      mm?
[12345.678905] --------------------  ------  ------ ---------- ---
[12345.678906] swapper/0                  0      0 RUNNING/R  kthread
[12345.678907] systemd                    1      1 SLEEPING(i user
[12345.678908] kthreadd                   2      2 SLEEPING(i kthread
[12345.678909] rcu_gp                     3      3 SLEEPING(u kthread
...
[12345.679100] === Summary: 187 processes, 412 total tasks (threads) ===
💡 Observation: Notice that kernel threads (like kthreadd, rcu_gp) show mm? = kthread because their mm pointer is NULL. User processes show mm? = user because they have a real virtual address space described by mm_struct.

Best Practices for task_struct Access in Linux Kernel Development

Best Practices Summary
Practice Why It Matters How To Do It
Always use RCU when iterating tasks Task list is modified concurrently rcu_read_lock() / rcu_read_unlock()
Increment reference count before storing pointer Prevents use-after-free if task exits get_task_struct() / put_task_struct()
Use helper functions, not direct field writes Maintains internal consistency set_current_state(), commit_creds()
Do not use current in interrupt context current is not meaningful in IRQ handlers Check in_interrupt() first
Compile against exact kernel headers task_struct layout changes between versions Use linux-headers-$(uname -r)
Use READ_ONCE() for __state field Prevents compiler from caching the value READ_ONCE(task->__state)

Performance Considerations

Iterating over all tasks using for_each_process_thread() is an O(N) operation where N is the total number of tasks. On a busy server with thousands of threads, this can be slow. For production kernel code, consider:

  • Avoiding frequent full task list scans: If you need to monitor specific tasks, track them by storing their struct pid * (not raw PIDs, not raw pointers) and look up only what you need.
  • Using process namespaces correctly: If your module is namespace-aware, use for_each_process() within the correct namespace context rather than scanning all tasks globally.
  • Minimizing time holding RCU read lock: The RCU read-side critical section should be as short as possible. Do not call sleeping functions or do heavy computation while holding the RCU read lock.
  • Prefer per-CPU data for frequent hot-path access: If your driver needs to track per-task state frequently, consider using a hash table keyed on PID rather than scanning the whole task list.
🎯 Summary — Part 2
  • The current macro gives you a pointer to the task_struct of the currently executing task — it is the most fundamental tool in Linux kernel programming
  • current is implemented in an architecture-specific, O(1) manner; never re-implement it yourself
  • current is only valid in process context; in interrupt context it gives a meaningless value
  • Use for_each_process_thread() inside an RCU read lock to safely iterate over all running tasks
  • Use find_get_pid() + pid_task() + get_task_struct() to safely look up and hold a reference to a task by PID
  • Always release references with put_task_struct() to prevent memory leaks in the kernel
  • This free Linux kernel development course teaches these fundamentals as building blocks for real Linux device drivers development

Frequently Asked Questions (FAQ)

Q1: What is struct task_struct in the Linux kernel?

struct task_struct is the central data structure in the Linux kernel that represents a running task — which can be a process or a thread. Every task on the system, including kernel threads, has exactly one task_struct instance allocated in kernel memory. It stores all information the kernel needs to manage and schedule that task: its state, PID, memory layout, open files, credentials, signal handlers, CPU scheduling parameters, and much more.

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

At the Linux kernel level, both processes and threads are represented by task_struct and are both called “tasks.” The difference is in what they share. A thread shares its virtual address space (mm_struct), open file table (files_struct), and signal handlers with its sibling threads, while a process (after fork() without sharing) gets its own copy-on-write copies of these. This sharing is controlled by the flags passed to the clone() system call, which is what pthread_create() uses under the hood.

Q3: Why is the current macro useful in Linux device driver development?

In a Linux device driver, when user space calls read(), write(), or ioctl() on your device, your driver’s file operation functions are called in the context of the user process making the call. Using current inside these handlers lets you access the calling process’s credentials (to do permission checks), its memory space (to copy data from user space), its PID (for logging and auditing), and more. This is fundamental to writing correct, secure device drivers.

Q4: What is the task list and how does the kernel organize all task_struct objects?

All task_struct objects in kernel memory are linked together in a circular doubly linked list called the “task list.” Each task_struct contains an embedded struct list_head tasks member that acts as the linked list node. This is an intrusive linked list — the node is inside the structure rather than wrapping it. The list starts at init_task (the idle process, PID 0) and wraps around. The kernel provides macros like for_each_process() and for_each_process_thread() to iterate over this list safely.

Q5: How do I safely get a task_struct pointer from a PID number in a kernel module?

Never directly cast a PID integer to a pointer. Instead, use find_get_pid(pid_nr) to get a reference-counted struct pid *, then use pid_task(pid_struct, PIDTYPE_PID) to get the task_struct *. Call get_task_struct(task) to increment its reference count before releasing the RCU lock and using the pointer outside the critical section. When you are finished, call put_task_struct(task) to release the reference. This sequence prevents use-after-free bugs that would otherwise crash the kernel.

Q6: Can I modify task_struct fields from my kernel module?

Technically you have direct access to task_struct fields from kernel module code, but you should almost never modify them directly. The kernel provides helper functions for any field that needs to be changed — for example, set_current_state() for task state, commit_creds() for credentials, and scheduling functions for priority changes. Direct modification of these fields without the proper locking and memory barriers will cause kernel panics, security vulnerabilities, or subtle data corruption that is very hard to debug.

Q7: How is TASK_RUNNING different from “actually running on a CPU”?

In the Linux kernel, TASK_RUNNING (value 0 in the __state field) means the task is runnable — either it is currently executing on a CPU, or it is waiting in the run queue to be scheduled. The kernel does not have a separate “currently on CPU” state in task_struct. To find out if a task is actually executing at this instant, you would need to check which task is running on each CPU, which is done differently (via per-CPU data structures in the scheduler). For most kernel programming purposes, “TASK_RUNNING” simply means “ready to run or running.”

Q8: What is the difference between PID and TGID in Linux kernel task_struct?

At the kernel level, every task (including every thread) has its own unique pid. The tgid (thread group ID) is shared by all threads that belong to the same process — it equals the PID of the thread group leader (the main thread). When you call getpid() in user space, you get the TGID, not the kernel-level PID. This is why getpid() returns the same value for all threads of a process. The kernel-level PID of a thread is visible as the LWP (light-weight process) column in ps -eLf.

Q9: Is this course truly free? Where can I find all the Linux kernel tutorials?

Yes, this is a completely free Linux kernel development course provided by EmbeddedPathashala at embeddedpathashala.com. All tutorials covering Linux kernel programming, Linux device drivers development, and embedded systems are available at no cost. The course covers everything from setting up your build environment to writing advanced character device drivers and platform drivers.

Q10: Do I need to know task_struct to write Linux device drivers?

Yes, understanding task_struct is important for Linux device drivers development even if you don’t work with it directly every day. When your driver’s file operations (read, write, ioctl) are called, they run in process context and current is available. You need it for credential checking (who is calling?), for memory access (copy_to_user / copy_from_user), for sending signals to user processes from driver code, and for understanding scheduling impacts of your driver. It is a foundational concept in our free Linux device drivers course.

Leave a Reply

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