# 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:

```bash
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:

```text
/proc/scheduler_demo
```

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

Example:

```bash
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:

```cpp
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.

```cpp
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:

```cpp
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:

```cpp
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:

```cpp
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:

```cpp
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:

```text
/proc/cpuinfo
/proc/meminfo
/proc/modules
/proc/uptime
```

Our module simply adds another entry:

```text
/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:

```cpp
seq_printf(...)
```

---

# Creating the `/proc` Entry

The module creates the file using:

```cpp
proc_create(...)
```

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

```cpp
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:

```cpp
module_init(...)
```

Cleanup:

```cpp
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](https://github.com/aj333git/linux_kernel_scheduler)** Explore the complete source code, build files, and module implementation on GitHub.
