Skip to main content

Command Palette

Search for a command to run...

Understanding the Linux Scheduler with a Kernel Module

Updated
6 min readView as Markdown
Understanding the Linux Scheduler with a Kernel Module

The Linux scheduler is one of the most important components of the operating system. Every running program, background service, and kernel thread eventually interacts with the scheduler.

In this article, I will build a Linux Kernel Module (LKM) that exposes scheduler information through the /proc filesystem, allowing us to inspect processes directly from kernel space.

The module targets Ubuntu 22.04 (Linux Kernel 5.15.x) and uses only modern kernel APIs.


What I will Learn

By the end of this article I will understand:

  • Linux scheduler fundamentals
  • What task_struct represents
  • The current process pointer
  • Process priorities
  • Nice values
  • Process states
  • CPU assignment
  • The /proc filesystem
  • The seq_file interface
  • Safe kernel reads using READ_ONCE()
  • Process iteration using for_each_process()

Why Build This Project?

Most Linux users interact with commands like:

ps
top
htop

These utilities obtain process information from the kernel.

Instead of only using these tools, this project demonstrates how the kernel itself provides scheduler information.

This makes it an excellent project for learning:

  • Linux Kernel Programming
  • Operating Systems
  • Device Driver Development
  • Embedded Linux
  • Linux Internals

Project Overview

The kernel module creates a new file:

/proc/scheduler_demo

Reading this file displays scheduler information about every process currently running on the system.

Example:

cat /proc/scheduler_demo

How the Module Works

The execution flow is straightforward:

  1. Load the kernel module.
  2. Register a new /proc entry.
  3. The user reads the file.
  4. The kernel invokes a callback.
  5. Process information is collected.
  6. Results are returned to userspace.

Building Blocks Used

Component Purpose
Kernel Module Extends kernel functionality
/proc Exposes runtime kernel information
seq_file Safely generates formatted output
task_struct Represents a process
Scheduler APIs Read scheduler information

Understanding task_struct

Every process in Linux is represented by a task_struct.

It contains information such as:

  • Process ID
  • Process name
  • Scheduling priority
  • Nice value
  • Current CPU
  • Memory information
  • Parent and child relationships
  • Process state
  • Credentials
  • Scheduling class

Nearly every scheduler-related API operates on a task_struct.


The current Pointer

Linux always knows which process is currently executing.

That process is accessible through:

current

From this pointer we can obtain information such as:

  • PID
  • Process name
  • Priority
  • Nice value
  • Current scheduler state

Enumerating Every Process

The kernel provides an iterator for traversing every process.

for_each_process(task)
{
    /* Scheduler information */
}

This macro walks through the kernel's process list and allows the module to inspect each task.


Reading the Current CPU

Every process executes on a CPU core.

The kernel provides:

task_cpu(task)

which returns the CPU currently associated with that task.

This is useful when studying:

  • SMP systems
  • CPU scheduling
  • Load balancing
  • Processor affinity

Understanding Priorities

Linux scheduling uses several priority values.

This project prints:

  • Scheduler priority
  • Static priority
  • Normal priority
  • Nice value

Together these values determine how the scheduler treats a process.


Nice Values

User processes can influence scheduling through the nice value.

Typical range:

Nice Value Meaning
-20 Highest priority
0 Default
+19 Lowest priority

The module retrieves it using:

task_nice(task)

Process States

Processes constantly transition between states.

Examples include:

  • Running
  • Runnable
  • Sleeping
  • Stopped
  • Zombie

The module safely reads the scheduler state using:

READ_ONCE(task->__state)

Using READ_ONCE() prevents compiler optimizations from producing inconsistent reads when scheduler data changes concurrently.


Detecting Runnable Tasks

The module checks whether a process is runnable using:

task_is_running(task)

This modern helper is preferred over manually interpreting scheduler state bits.


Why Use /proc?

The /proc filesystem is designed to expose runtime kernel and process information.

Examples include:

/proc/cpuinfo
/proc/meminfo
/proc/modules
/proc/uptime

Our module simply adds another entry:

/proc/scheduler_demo

Why seq_file?

Kernel output can become very large.

Instead of manually managing buffers, Linux provides the seq_file interface.

Benefits include:

  • Automatic buffering
  • Safe iteration
  • Large output support
  • Simpler implementation
  • Better maintainability

The module prints information using:

seq_printf(...)

Creating the /proc Entry

The module creates the file using:

proc_create(...)

When the module is unloaded, it removes the entry using:

remove_proc_entry(...)

This keeps the filesystem clean and avoids leaving stale entries behind.


Kernel Module Lifecycle

Every Linux Kernel Module follows a lifecycle.

Initialization:

module_init(...)

Cleanup:

module_exit(...)

Initialization registers resources, while cleanup releases them before the module is unloaded.



APIs Covered

API Purpose
current Current executing process
task_struct Process descriptor
for_each_process() Iterate over all processes
task_cpu() CPU associated with a task
task_nice() Retrieve nice value
task_is_running() Check runnable state
READ_ONCE() Safe concurrent read
proc_create() Create /proc entry
remove_proc_entry() Remove /proc entry
seq_printf() Generate /proc output
single_open() Connect /proc with callback
module_init() Module initialization
module_exit() Module cleanup

Key Takeaways

This project demonstrates several important Linux kernel concepts in a compact and practical example.

You learned how to:

  • Create a Linux Kernel Module
  • Add custom entries to the /proc filesystem
  • Traverse every process in the kernel
  • Read scheduler metadata
  • Display process priorities and nice values
  • Access CPU information
  • Safely inspect process states
  • Generate formatted kernel output using seq_file
  • Follow modern Linux Kernel 5.15 programming practices

Although intentionally simple, this module forms a strong foundation for studying advanced scheduler topics such as the Completely Fair Scheduler (CFS), scheduling classes, CPU affinity, load balancing, kernel threads, and scheduler internals.


Conclusion

The Linux scheduler is responsible for deciding which process runs, when it runs, and on which CPU. By exposing scheduler information through a custom /proc entry, this project provides a practical way to explore those internals without modifying the kernel itself.

For learning Linux kernel programming, operating systems, embedded Linux, or device driver development, implementing small projects like this is one of the most effective ways to understand how the kernel works beneath the surface.


GitHub Repository: 👉 linux_kernel_scheduler Explore the complete source code, build files, and module implementation on GitHub.