Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/DeelerDev/linux/llms.txt

Use this file to discover all available pages before exploring further.

Linux kernel drivers live either as loadable kernel modules (.ko files inserted at runtime) or as code compiled directly into the kernel image. Both share the same APIs and follow the same driver model; the distinction is purely about when the code is linked and whether it can be removed while the system is running. Understanding this split is the first step toward building any driver, because it determines your build process, your deployment strategy, and which lifecycle hooks matter to you.

Kernel modules vs. built-in drivers

A loadable module is an ELF shared object that the kernel can insmod at any time after boot. It is unloaded with rmmod and can be auto-loaded by modprobe based on device aliases recorded in the module’s .ko file. A built-in driver is linked into vmlinux during the kernel build; it starts with the rest of the kernel and cannot be removed. The choice comes down to flexibility versus startup time. Embedded systems with a fixed hardware set often prefer built-in drivers to avoid a root filesystem at early boot. Desktop and server distributions use modules so that a single kernel binary can support thousands of different devices.

Anatomy of a kernel module

Every module must define an init function and optionally an exit function, and must declare its license.
#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jane Developer <jane@example.com>");
MODULE_DESCRIPTION("A minimal example kernel module");

static int __init hello_init(void)
{
    pr_info("hello: module loaded\n");
    return 0;
}

static void __exit hello_exit(void)
{
    pr_info("hello: module unloaded\n");
}

module_init(hello_init);
module_exit(hello_exit);
MacroPurpose
module_init()Registers the function called when the module is inserted
module_exit()Registers the function called when the module is removed
MODULE_LICENSE()Declares the license; "GPL" enables access to GPL-only kernel symbols
MODULE_AUTHOR()Recorded in the .ko and visible via modinfo
MODULE_DESCRIPTION()Human-readable description shown by modinfo
The __init annotation places the function in a special section that the kernel frees after init completes; __exit is discarded entirely for built-in drivers.

Compiling an out-of-tree module

Out-of-tree modules are built against an installed kernel header tree using a small Makefile:
obj-m += hello.o

KDIR ?= /lib/modules/$(shell uname -r)/build

all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean
Run make in the source directory. The kernel build system reads obj-m and produces hello.ko.
You need the kernel headers or full kernel source tree for the running kernel. On Debian/Ubuntu, install linux-headers-$(uname -r). On Fedora/RHEL, install kernel-devel.

Loading and unloading modules

1

Insert a module manually

sudo insmod hello.ko
insmod loads the exact file you specify. It does not resolve dependencies; if your module requires another module, load that one first.
2

Check the kernel log

dmesg | tail -5
Your pr_info() messages appear here. Use pr_err() and pr_warn() for errors and warnings.
3

List loaded modules

lsmod | grep hello
Shows the module name, size in bytes, and use count.
4

Remove the module

sudo rmmod hello
Calls your module_exit() function and frees the module’s memory.
5

Use modprobe for production

sudo modprobe hello
sudo modprobe -r hello
modprobe reads /lib/modules/$(uname -r)/modules.dep to resolve dependencies and can auto-load modules by device alias.

Character devices

Character devices expose a file-like interface in /dev. User space opens the device node and calls read(), write(), and ioctl() as with any file. The driver registers a set of file_operations callbacks that the kernel invokes for each syscall.

Major and minor numbers

Every character device is identified by a major number (which driver handles the device) and a minor number (which instance within that driver). The pair is encoded as a dev_t value. You can allocate a major number statically or dynamically:
#include <linux/fs.h>
#include <linux/cdev.h>

#define MYDEV_MAJOR 0   /* 0 = dynamic allocation */
#define MYDEV_MINOR 0
#define MYDEV_COUNT 1

static dev_t mydev_num;
static struct cdev mydev_cdev;

static int mydev_open(struct inode *inode, struct file *filp)
{
    return 0;
}

static ssize_t mydev_read(struct file *filp, char __user *buf,
                           size_t count, loff_t *ppos)
{
    return 0;
}

static const struct file_operations mydev_fops = {
    .owner  = THIS_MODULE,
    .open   = mydev_open,
    .read   = mydev_read,
};

static int __init mydev_init(void)
{
    int ret;

    ret = alloc_chrdev_region(&mydev_num, MYDEV_MINOR, MYDEV_COUNT, "mydev");
    if (ret < 0)
        return ret;

    cdev_init(&mydev_cdev, &mydev_fops);
    mydev_cdev.owner = THIS_MODULE;

    ret = cdev_add(&mydev_cdev, mydev_num, MYDEV_COUNT);
    if (ret) {
        unregister_chrdev_region(mydev_num, MYDEV_COUNT);
        return ret;
    }

    pr_info("mydev: registered major=%d minor=%d\n",
            MAJOR(mydev_num), MINOR(mydev_num));
    return 0;
}

static void __exit mydev_exit(void)
{
    cdev_del(&mydev_cdev);
    unregister_chrdev_region(mydev_num, MYDEV_COUNT);
}

module_init(mydev_init);
module_exit(mydev_exit);
MODULE_LICENSE("GPL");
After loading this module, create the device node with:
sudo mknod /dev/mydev c $(cat /sys/class/mydev/mydev/dev | cut -d: -f1) 0
Or use udev rules so the node appears automatically.
register_chrdev() is the older single-function API that allocates a full range of 256 minor numbers for one major. Prefer alloc_chrdev_region() plus cdev_add() for new drivers; they give finer control over the number of minor devices allocated.

Overview of driver types

Character drivers

Expose a byte-stream interface via /dev nodes. Used for serial ports, input devices, sensors, and most custom hardware. Implement file_operations callbacks.

Block drivers

Expose fixed-size block I/O, typically for storage. The kernel queues and merges requests before dispatching them. Implement block_device_operations and a request queue.

Network drivers

Manage net_device structs and transmit/receive packets. Do not appear in /dev; user space interacts through sockets and ioctl. Implement net_device_ops.

Platform drivers

For SoC-integrated peripherals described in Device Tree or ACPI tables. Use platform_driver and platform_device; the kernel’s platform bus matches them automatically.
For most embedded and SoC work, start with a platform driver. It integrates cleanly with Device Tree, devm_* resource management, and the kernel’s power management framework with minimal boilerplate.

Build docs developers (and LLMs) love