Linux Kernel Execution Contexts: Process Context vs Interrupt Context – Best Linux Device Drivers Course

 

 

Free Linux Kernel Programming Course — Module 6

Linux Kernel Execution Contexts

Process Context vs Interrupt Context — Everything You Need to Know

Level
Beginner
Read Time
~20 min
Updated
Linux 6.x
Cost
100% Free
Linux Kernel Process Context Interrupt Context Device Drivers LKM Kernel Internals

🎓 What You Will Learn

  • What kernel execution context means and why it matters for writing Linux kernel code
  • The difference between process context and interrupt context
  • How a user-space thread transitions into kernel space via a system call
  • How hardware interrupts trigger the interrupt service routine (ISR) in interrupt context
  • Why these two contexts have different restrictions and how that affects driver development
  • How to write safe kernel code that respects the rules of each context
  • How to check the current execution context in a Linux kernel module
📄 Prerequisites

Before diving in, make sure you are comfortable with:

  • Basic C programming (pointers, structs, functions)
  • What a Linux process is and how it runs in user space
  • A rough idea of what the Linux kernel does (it manages hardware and resources)
  • Basic understanding of system calls (e.g. read(), write())

No kernel programming experience is required — this is a free Linux kernel course built for beginners.

Introduction: Why Does Execution Context Matter?

One of the most important concepts in Linux kernel programming is understanding where and how your kernel code is running at any given moment. This is called the kernel execution context.

When you write a Linux kernel module or a device driver, you are not writing a normal program. The code you write can execute in very different environments depending on what triggered it. Some code runs because a user application asked for something. Other code runs because a hardware device fired an interrupt signal. These two situations have completely different rules about what you can and cannot do.

Getting this wrong is one of the most common sources of kernel bugs, crashes, and system freezes. This free Linux kernel development tutorial explains it clearly from the ground up.

💡 Key Idea: Every line of kernel code runs in one of exactly two contexts — process context or interrupt context. Knowing which one you are in determines what kernel APIs you can safely call.

The Monolithic Kernel Architecture of Linux

Linux is a monolithic kernel. This means that the entire kernel — memory management, file systems, device drivers, network stack — all run in a single privileged address space called kernel space.

In contrast, a microkernel design would run many of these services in separate user-space processes. Linux chose the monolithic approach for performance. The trade-off is that a bug anywhere in the kernel can affect the entire system.

Linux Memory Layout: User Space vs Kernel Space
KERNEL SPACE (Privileged / Ring 0)
Kernel Code & Data
Device Drivers
Interrupts / ISRs
——— USER / KERNEL BOUNDARY ———
USER SPACE (Unprivileged / Ring 3)
Process A
(e.g. bash)
Process B
(e.g. nginx)
Process C
(e.g. app)
Each process has its own Virtual Address Space (VAS)

The important point is this: user-space processes run in unprivileged mode (Ring 3 on x86). When they need to do something that requires kernel access — like reading a file or sending data over a network — they make a system call, which causes the CPU to switch to privileged kernel mode (Ring 0).

What is Process Context in the Linux Kernel?

When a user-space process or thread makes a system call, the CPU saves the user-space register state and switches execution into kernel mode. From this point, the kernel code is running — but critically, it is running on behalf of that specific user process. This is called process context.

The System Call Path into Process Context

Here is the flow step by step:

  1. A user-space thread calls something like read(fd, buf, count)
  2. The C library wraps this in a software interrupt or syscall instruction
  3. The CPU switches from Ring 3 (user) to Ring 0 (kernel)
  4. The kernel’s system call handler takes over
  5. The kernel runs the relevant driver or VFS code — all in process context
  6. When done, it returns control back to user space
Process Context: System Call Flow
User Space Thread
calls read(fd, buf, n)
↓
syscall / software interrupt
CPU SWITCHES TO KERNEL MODE (Ring 0)
Syscall Dispatcher
sys_read() entry
→
VFS Layer
file_operations
→
Driver Code
runs in process context
Process Context Properties:
  • Runs in the context of a specific process (has a valid current pointer)
  • Synchronous — the calling process blocks until done
  • Can sleep / block (e.g. wait for I/O)
  • Can access user-space memory (carefully)
  • Can use mutex_lock(), kmalloc(GFP_KERNEL), etc.

The key property of process context is that there is always a well-defined current process. The kernel uses a macro called current to access the task structure (struct task_struct) of the running process. In process context, current always points to something meaningful.

Another critical property: code running in process context is allowed to sleep. If the kernel needs to wait for something — like waiting for a disk read to complete — it can put the current process to sleep and let the scheduler run another process. This is perfectly safe in process context.

✅ Remember for Driver Development: Most of a device driver’s logic runs in process context — the open(), read(), write(), ioctl(), and release() file operations are all called from process context when a user-space application calls them.

Code Example: Logging the Current Process Name in Process Context

Inside a kernel module’s file operation, you can access the name of the current process like this:

#include <linux/kernel.h>
#include <linux/sched.h>   /* for 'current' macro */

static ssize_t my_driver_read(struct file *filp, char __user *buf,
                               size_t count, loff_t *f_pos)
{
    /* 'current' is a macro pointing to the task_struct of the
     * process that called read() on our device file.
     * This is valid because we are in PROCESS CONTEXT.
     */
    pr_info("my_driver: read() called by process: %s (PID %d)\n",
            current->comm,    /* process name, max 16 chars */
            current->pid);    /* process ID */

    /* ... rest of read logic ... */
    return 0;
}

The current macro is a per-CPU variable that always points to the struct task_struct of the currently running task. It is valid only in process context. Accessing it in interrupt context can give you garbage or point to a random process that happened to be running when the interrupt fired.

What is Interrupt Context in the Linux Kernel?

Now here is where things get more complex — and more interesting. Your code does not always enter the kernel because a process asked for something. Hardware devices can demand attention at any time. When a keyboard key is pressed, when a network packet arrives, when a disk transfer completes — the hardware sends an interrupt signal to the CPU.

The CPU temporarily stops whatever it was doing, saves its current state, and jumps to a special function called the Interrupt Service Routine (ISR) or interrupt handler. This handler runs in interrupt context.

How Hardware Interrupts Work

Every hardware device is assigned an IRQ (Interrupt Request) line. When the device needs attention:

  1. The device asserts its IRQ line to the interrupt controller (e.g. APIC on x86)
  2. The interrupt controller signals the CPU
  3. The CPU finishes its current instruction, saves registers onto the stack
  4. The CPU switches to kernel mode and calls the registered interrupt handler
  5. The ISR runs — in interrupt context
  6. After the ISR returns, the CPU restores the saved state and resumes what it was doing
Interrupt Context: Hardware IRQ Flow
CPU was running:
Any process or kernel code
Example: nginx serving an HTTP request
HW IRQ fires!
(e.g. NIC packet)
↓
CPU saves state
& switches to
kernel mode
ISR runs:
Interrupt Handler Code
INTERRUPT CONTEXT
(no sleeping allowed!)
↩ After ISR returns — CPU restores state and resumes previous task
Interrupt Context Restrictions:
  • Cannot sleep — will cause a kernel panic
  • Cannot block — no waiting for resources
  • current pointer is not meaningful (random process)
  • Cannot use mutex_lock() (may sleep)
  • Cannot use kmalloc(GFP_KERNEL) (may sleep)
  • Must use kmalloc(GFP_ATOMIC) instead
  • Must be fast — keep interrupt handler as short as possible

The most important rule in interrupt context is: you cannot sleep. The reason is straightforward. When an interrupt fires, it preempts whatever was running. The kernel cannot just put the interrupt handler to sleep — there is nothing to wake it up in a meaningful way, and it would hold up the interrupted task indefinitely. Sleeping in interrupt context causes a kernel panic.

Code Example: Registering and Writing an Interrupt Handler

Here is how you register an interrupt handler in a Linux kernel module:

#include <linux/interrupt.h>
#include <linux/kernel.h>

#define MY_IRQ   11         /* example IRQ number */
#define MY_DEV   "my_device"

/* The interrupt handler - runs in INTERRUPT CONTEXT */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
    /* RULES in interrupt context:
     * - Do NOT sleep (no mutex_lock, no wait_event, no msleep, etc.)
     * - Do NOT access user space memory
     * - Keep this function SHORT and FAST
     * - Use GFP_ATOMIC if you need to allocate memory
     * - 'current' does NOT refer to any meaningful process here
     */

    pr_info("IRQ %d received - in INTERRUPT CONTEXT\n", irq);

    /* Acknowledge hardware, read status register, queue work, etc. */
    /* For longer work, use a tasklet or workqueue (bottom half) */

    return IRQ_HANDLED;
}

/* Called during module init - runs in PROCESS CONTEXT */
static int __init my_driver_init(void)
{
    int ret;

    /* request_irq() can sleep, so we call it from process context */
    ret = request_irq(MY_IRQ,           /* IRQ number */
                      my_irq_handler,   /* handler function */
                      IRQF_SHARED,      /* flags */
                      MY_DEV,           /* device name */
                      (void *)my_irq_handler); /* dev_id */
    if (ret) {
        pr_err("Failed to register IRQ %d: %d\n", MY_IRQ, ret);
        return ret;
    }

    pr_info("IRQ %d registered successfully\n", MY_IRQ);
    return 0;
}

static void __exit my_driver_exit(void)
{
    /* free_irq() must also be called from process context */
    free_irq(MY_IRQ, (void *)my_irq_handler);
    pr_info("IRQ %d released\n", MY_IRQ);
}
⚠ Warning: Never call mutex_lock(), down() (semaphore), msleep(), wait_event(), or any other sleeping function from inside an interrupt handler. This will crash your kernel with a message like “BUG: sleeping function called from invalid context”.

Process Context vs Interrupt Context: Side-by-Side Comparison

Property Process Context Interrupt Context
Triggered by System call, page fault, kernel thread Hardware IRQ from peripheral device
Execution flow Synchronous (top-down) Asynchronous (bottom-up)
current pointer Valid — points to calling process Not meaningful (random process)
Can sleep? Yes No (kernel panic if you try)
Can block? Yes No
Memory allocation kmalloc(GFP_KERNEL) kmalloc(GFP_ATOMIC) only
Locking mutex_lock(), down() Spinlocks only (spin_lock_irq())
User memory access Yes (via copy_to_user()) No
Must be fast? Not strictly required Yes — keep as short as possible
Example in drivers .read(), .write(), .open(), .ioctl() ISR registered with request_irq()

How to Detect the Current Execution Context in Your Kernel Code

Linux provides a kernel helper to check whether your code is running in interrupt context. This is useful for writing safe kernel modules and drivers that work correctly regardless of how they are called.

#include <linux/interrupt.h>

void my_kernel_function(void)
{
    if (in_interrupt()) {
        /* We are in interrupt context (hardirq or softirq) */
        pr_info("Running in INTERRUPT CONTEXT\n");
        /* Do NOT sleep. Use GFP_ATOMIC for allocations. */
    } else {
        /* We are in process context */
        pr_info("Running in PROCESS CONTEXT\n");
        pr_info("Current process: %s (PID %d)\n",
                current->comm, current->pid);
        /* Can sleep if needed. GFP_KERNEL is fine. */
    }
}

There are also more specific helpers:

  • in_hardirq() — true if running in a hardware IRQ handler
  • in_softirq() — true if running in a softirq or tasklet
  • in_atomic() — true if sleeping is not allowed (includes spinlock-held sections)
  • in_task() — true if running in a normal process/thread context
📝 Note for Linux 6.x: In modern kernels, in_interrupt() returns true for both hard IRQ and soft IRQ contexts. The behaviour is consistent across Linux 5.x and 6.x. Always prefer in_hardirq() or in_softirq() when you need to distinguish between them.

Top Halves and Bottom Halves: Splitting Interrupt Work

A well-written interrupt handler should do as little work as possible in interrupt context. The reason: while your ISR is running, the same interrupt (and often other interrupts) are disabled on the current CPU. If your handler takes too long, other hardware events get delayed and the system becomes unresponsive.

Linux solves this with a top-half / bottom-half design:

Interrupt Handling: Top Half vs Bottom Half
⚡ TOP HALF (Interrupt Context = Hardirq)
  • Runs immediately when hardware IRQ fires
  • Must be very fast (microseconds)
  • Acknowledge the hardware interrupt
  • Read critical data from device registers
  • Schedule bottom-half work
  • Cannot sleep
↓ schedules ↓
Softirq

Static, compile-time. Used by network stack, block layer. Still in softirq context (no sleeping).

Tasklet

Dynamic. Built on softirq. Common in older drivers. Cannot sleep. Runs on same CPU.

Workqueue

Runs in PROCESS CONTEXT via kernel thread. Can sleep! Best for longer deferred work.

For new driver code, workqueues are the recommended mechanism for deferred work because they run in process context and can sleep. Tasklets are considered legacy in modern kernels (Linux 6.x onwards discourages new tasklet usage).

Common Mistakes and How to Avoid Them

❌ Mistake 1: Sleeping in Interrupt Context

Calling mutex_lock(), msleep(), wait_event(), or kmalloc(GFP_KERNEL) from an ISR. This causes a kernel panic with “BUG: sleeping function called from invalid context”.

Fix: Use spin_lock() and kmalloc(GFP_ATOMIC) in ISRs. Move slow work to a workqueue.

❌ Mistake 2: Using current in Interrupt Context

Accessing current->pid or current->comm inside an interrupt handler. In interrupt context, current points to whatever process happened to be running when the interrupt fired — which is meaningless for your driver.

Fix: Only use current in process context. Use in_interrupt() to guard if unsure.

❌ Mistake 3: Doing Heavy Work in the ISR

Writing complex data processing logic directly inside the interrupt handler. This delays other interrupts and can make the system sluggish.

Fix: Keep the ISR minimal. Schedule a workqueue for the real work.

❌ Mistake 4: Forgetting to Free the IRQ

Calling request_irq() in module_init() but not calling free_irq() in module_exit(). This leaves the IRQ reserved even after the module is removed.

Fix: Always pair request_irq() with free_irq() in cleanup.

Best Practices for Writing Context-Aware Kernel Code

  • Always know your context. Before writing any kernel function, decide: will this be called from process context, interrupt context, or both?
  • Use in_interrupt() defensively in shared utility functions to catch wrong usage early.
  • Keep ISRs minimal. Do the bare minimum (ack the hardware, read status, wake up waiting process or schedule bottom half) and return.
  • Prefer workqueues over tasklets for deferred work in modern kernels (Linux 5.x / 6.x).
  • Use the correct memory allocation flag: GFP_KERNEL in process context, GFP_ATOMIC in interrupt or atomic context.
  • Use the correct locking primitives: mutexes in process context only, spinlocks in interrupt context.
  • Enable CONFIG_DEBUG_ATOMIC_SLEEP during development to catch sleeping-in-wrong-context bugs at runtime.

Key Takeaways

  • Every piece of Linux kernel code runs in one of two contexts: process context or interrupt context
  • Process context is entered via system calls or processor exceptions. current is valid, sleeping is allowed, and most kernel APIs are available
  • Interrupt context is entered via hardware IRQs. Sleeping is forbidden, current is meaningless, and only atomic operations are allowed
  • Device driver file operations (read, write, ioctl) run in process context; ISRs registered with request_irq() run in interrupt context
  • For deferred interrupt work, use workqueues (process context, can sleep) rather than tasklets (deprecated pattern)
  • Use in_interrupt(), in_hardirq(), and in_task() to programmatically check your context
  • Always use GFP_ATOMIC for memory allocation in interrupt context and GFP_KERNEL in process context

Frequently Asked Questions (FAQ)

Q1: Can a kernel module function be called in both process context and interrupt context?

Yes. A utility function inside your driver can theoretically be called from both contexts. In that case, you must write it to be safe for the most restrictive context (interrupt context) — meaning no sleeping, use GFP_ATOMIC for allocations, and use spinlocks. Alternatively, have two separate code paths guarded by in_interrupt().

Q2: What happens if I accidentally call mutex_lock() in interrupt context?

The kernel detects this and prints a warning or panic depending on your kernel configuration. With CONFIG_DEBUG_ATOMIC_SLEEP=y, the kernel immediately prints a stack trace and BUG message. Without it, the system may silently corrupt state or freeze.

Q3: Is printk() safe to call from interrupt context?

Yes. printk() and pr_info(), pr_err() etc. are safe to call from both process context and interrupt context. They use an internal ring buffer and are designed to be atomic-safe.

Q4: What is a softirq and how does it differ from a hardirq?

A hardirq is a true hardware interrupt triggered by a device. A softirq is a deferred software mechanism that runs after hardirqs are processed. Softirqs are statically defined in the kernel and include the network receive path (NET_RX_SOFTIRQ) and timer processing (TIMER_SOFTIRQ). Both run in interrupt context and cannot sleep.

Q5: Do kernel threads run in process context or interrupt context?

Kernel threads run entirely in process context. They are scheduled by the Linux scheduler just like user-space threads but they never execute in user space. Since they run in process context, they can sleep, use mutexes, and call most kernel APIs. Workqueues are implemented using kernel threads.

Q6: What is the difference between GFP_KERNEL and GFP_ATOMIC?

GFP_KERNEL tells the memory allocator that it is allowed to sleep if memory is not immediately available (it may wait for pages to be reclaimed). GFP_ATOMIC tells the allocator it must not sleep — it will either succeed immediately from a reserved memory pool or fail. Use GFP_ATOMIC in interrupt context and spinlock-held sections.

Q7: Can I use copy_to_user() or copy_from_user() in interrupt context?

No. These functions may sleep (they can trigger a page fault which requires process context to resolve). They should only be called from process context, typically inside your driver’s read() and write() file operations.

Q8: How do I pass data from an interrupt handler to process context?

The standard patterns are: (1) write data into a shared buffer protected by a spinlock, then wake up a sleeping process using wake_up(); (2) queue work onto a workqueue from the ISR; (3) use a kernel FIFO (kfifo) for lock-free data transfer between ISR and process context. The waiting process (in process context) can block on wait_event_interruptible() until the ISR signals it.

Q9: Is this relevant for writing Linux device drivers in 2025 and beyond?

Absolutely. The process context / interrupt context distinction is fundamental and unchanged since the earliest Linux kernels. It remains essential knowledge for anyone learning Linux kernel development or writing device drivers on Linux 6.x. Every driver that handles hardware interrupts must understand these rules.

Q10: Where can I learn more about Linux kernel programming for free?

EmbeddedPathashala offers a completely free Linux kernel programming course covering LKM development, memory management, synchronization, device drivers, and more. Continue with the next lecture in this series below.

Continue Your Free Linux Kernel Journey

EmbeddedPathashala offers 100% free courses on Linux kernel programming, device drivers, embedded systems, and Bluetooth/BLE development.

🎓 Browse All Free Courses

Leave a Reply

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