Linux Kernel Module – Entry Points, Exit Points

Prev_Lec

Next_Lec

Linux Kernel Module – Part 3

Entry Points, Exit Points and the 0 / -E Return Convention
⚙️ Kernel 6.x Updated
🆓 Free Course
🎯 Beginner Friendly

Topics Covered
module_init()
module_exit()
__init macro
__exit macro
static qualifier
0/-E convention
errno
insmod failure

In the last tutorial we saw what module macros are. Now let us look at the real heart of a kernel module — the init function and exit function. Every kernel module must have these two functions. They are the entry and exit points of your module’s life cycle.

We will also learn an important kernel convention — the 0/-E return rule — which you will use throughout your kernel development career.

There Is No main() in a Kernel Module

When you write a normal C program, execution always starts from main(). That is the rule in user-space applications.

Kernel modules are different. They are not applications. They run directly inside the kernel — in privileged mode, with full access to hardware and kernel data structures. Because of this, the concept of main() simply does not apply here.

Instead, a kernel module has two specific functions:

Lifecycle of a Kernel Module
insmod / modprobe init function runs Module is now active in kernel
↕ Module does its job (handles devices, intercepts calls, etc.)
rmmod exit function runs Module is unloaded from kernel

You register these two functions using the module_init() and module_exit() macros. You pass the name of your function to each macro:

module_init(my_init_function);
module_exit(my_exit_function);

Think of these like a constructor and destructor pair from C++. When your module is loaded (insmod), the init function runs. When it is removed (rmmod), the exit function runs.

The Correct Function Signature

The kernel expects a very specific signature for both functions. Here is the standard form:

static int  __init  modulename_init(void);
static void __exit  modulename_exit(void);

Let us break down each keyword:

Breaking Down the Function Signature
Keyword What it means
static Makes the function private to this module. It will not be visible or accessible from other modules. This is good practice — these functions are meant only for this module’s own lifecycle.
__init Tells the linker to place this function in a special memory section called .init.text. After the module is fully loaded and the init function has run, the kernel can free that section of memory. Smart memory optimization.
__exit Similar idea — places exit function in .exit.text. If the module is compiled directly into the kernel (built-in, not loadable), this function is completely dropped at link time because built-in code can never be removed.
int vs void Init returns int (success=0, failure=negative errno). Exit returns void — it cannot fail, it must always complete cleanup.
(void) Neither function takes any parameters. The kernel calls them with no arguments.

The __init and __exit Macros — Memory Sections

Let us understand __init more clearly with a diagram. Kernel memory is divided into sections. The __init macro places code in a special section that is freed after use:

Memory Sections — What __init and __exit Do
Kernel/Module Memory Layout
.text
Normal code that stays forever
module_register_etc()
Stays loaded
.init.text
__init functions go here
→ freed after init! my_init()
Freed once module loads
.exit.text
__exit functions go here
→ dropped if built-in my_exit()
Dropped for built-in builds

This is a memory-saving technique. The init function only runs once when the module loads. After that, there is no reason to keep that code in memory. The kernel is clever enough to discard it. Same with exit — if the driver is compiled into the kernel image and can never be removed, the exit function is dead code and the linker drops it entirely.

For a loadable module, the effect is smaller but still real. Every byte matters at scale in embedded systems.

Naming Convention for Init and Exit Functions

The kernel community follows a consistent naming style for these functions:

static int  __init  mydriver_init(void);
static void __exit  mydriver_exit(void);

The pattern is: <modulename>_init and <modulename>_exit.

This is just a convention — technically you can name these functions anything you want since you pass the function pointer to module_init() and module_exit(). But following the convention makes the code easy to read and maintain, especially in a large team or in the upstream kernel.

Examples from real kernel drivers:

/* From a USB driver */
module_init(usb_storage_driver_init);
module_exit(usb_storage_driver_exit);

/* From a network driver */
module_init(e1000_init_module);
module_exit(e1000_exit_module);

The 0 / -E Return Convention

This is one of the most important conventions in Linux kernel programming. The init function returns an int. What value should it return?

The Linux kernel follows a very clear rule called the 0/-E convention:

The 0 / -E Return Rule
Return 0 ✅ Success — module is loaded
Return -ERRNO ❌ Failure — module load is aborted

The negative errno value is very specific. You return the negative of the error code you want user-space to see. Here are common ones:

return -ENOMEM;   /* memory allocation failed */
return -ENODEV;   /* device not found */
return -EINVAL;   /* invalid parameter */
return -EPERM;    /* permission denied */
return -EIO;      /* input/output error */

These error codes are defined in the kernel source under:

include/uapi/asm-generic/errno-base.h
include/uapi/asm-generic/errno.h

What Happens When Init Returns a Negative Value?

When your init function returns a non-zero negative value, a chain of events happens involving the kernel and glibc. Let us trace this flow:

Flow When Module Init Fails (returns -ENOMEM)
Step Who What happens
1 Your init function Returns -ENOMEM to the kernel
2 Linux Kernel Aborts module loading. The finit_module() syscall returns -1 to user-space
3 glibc glue code Multiplies the kernel’s error by -1, sets the global variable errno = ENOMEM
4 insmod tool Reads errno, prints an error message like insmod: ERROR: could not insert module hello.ko: Cannot allocate memory

Notice something important: kernel space returns -ENOMEM (negative), but user-space errno is set to ENOMEM (positive). The glibc layer handles that sign flip. You do not need to worry about this in your kernel code — just return the negative value.

Here is a practical example in code:

static int __init demo_init(void)
{
    void *ptr;

    /* Try to allocate 512 bytes of kernel memory */
    ptr = kmalloc(512, GFP_KERNEL);
    if (!ptr) {
        pr_err("demo_mod: memory allocation failed\n");
        return -ENOMEM;   /* tell caller: out of memory */
    }

    pr_info("demo_mod: loaded successfully\n");
    return 0;   /* success */
}

static void __exit demo_exit(void)
{
    /* cleanup goes here */
    pr_info("demo_mod: unloaded\n");
    /* exit returns void — no return value needed */
}
⚠️ Important: If your init function returns any non-zero value, the module is NOT loaded. The exit function will also NOT be called in this case. Make sure you clean up any partial work inside the init function before returning the error.

Understanding errno — A Quick Note

In user-space C programming, errno is a global variable that stores the error code of the last failed system call. It lives inside the user process’s memory — specifically in the uninitialized data segment (BSS segment).

When a Linux system call fails, it returns -1 and sets errno to a positive error code. Functions like perror() and strerror() translate this error code into a human-readable message by looking it up in an internal error message table.

errno in User Space vs Kernel Error Codes
Location Value Type
Kernel code -ENOMEM (negative) Returned to kernel framework
glibc flips sign → ENOMEM Sets user-space errno
User-space program errno == ENOMEM (positive) Read by perror() / strerror()

This design keeps things clean: kernel returns negative codes to indicate error type, user-space reads positive codes via errno. As a kernel developer you always work with the negative form.

Putting It All Together — Minimal Kernel 6.x Module

Here is a complete, correct kernel module that uses everything we have learned across these tutorials — macros, init, exit, and the proper return value:

// SPDX-License-Identifier: GPL-2.0
/*
 * hello_lkm.c - Minimal Hello World Kernel Module
 * Tested on Linux Kernel 6.6 / 6.8 / 6.12 LTS
 */

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

MODULE_AUTHOR("Your Name <your@email.com>");
MODULE_DESCRIPTION("Minimal Hello World LKM - EmbeddedPathashala");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.0");

/*
 * hello_lkm_init - called when module is loaded via insmod/modprobe
 * Returns: 0 on success, negative errno on failure
 */
static int __init hello_lkm_init(void)
{
    pr_info("hello_lkm: module loaded! Hello from kernel space.\n");
    return 0;
}

/*
 * hello_lkm_exit - called when module is removed via rmmod
 */
static void __exit hello_lkm_exit(void)
{
    pr_info("hello_lkm: module removed. Goodbye!\n");
}

module_init(hello_lkm_init);
module_exit(hello_lkm_exit);

And the Makefile to build it:

obj-m += hello_lkm.o

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

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

clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean

Build and test:

# Build
make

# Load the module
sudo insmod hello_lkm.ko

# Check kernel log
dmesg | tail -5

# Remove the module
sudo rmmod hello_lkm

# Verify log again
dmesg | tail -5

Interview Questions
Q1. Why does a kernel module not have a main() function?

Answer: main() is a concept from user-space C programming — it is the entry point the C runtime calls when the OS launches a process. Kernel modules are not processes; they are pieces of code that get loaded directly into the running kernel’s address space. The kernel itself calls the init function at load time using the pointer registered with module_init(). There is no process, no C runtime, and no main().

Q2. What is the purpose of the __init macro?

Answer: __init is a compiler hint that places the annotated function into a special ELF section called .init.text. After the init function finishes running, the kernel can reclaim (free) that section of memory because the function will never be called again. This saves physical RAM — important in embedded and resource-constrained systems. For built-in drivers (compiled into the kernel image rather than as a loadable module), the memory savings are even more significant.

Q3. What happens if a kernel module’s init function returns a non-zero value?

Answer: The kernel aborts the module loading process. The module is NOT inserted into the kernel. The finit_module() or init_module() system call returns -1 to user-space, glibc sets errno to the positive version of the error code, and insmod prints an error message. Importantly, the exit function is NOT called in this case — so the init function itself must clean up any resources it partially allocated before returning the error.

Q4. Why does the exit function return void? Can it return an error?

Answer: The exit function returns void because module removal is expected to always succeed. Once rmmod is called and the kernel decides to unload the module (after checking the reference count is zero), the exit function MUST complete all cleanup. There is no mechanism to “cancel” the unload by returning an error. The kernel design philosophy here is: if you allowed the exit to fail, you could end up with a partially-removed module and leaked resources in the kernel.

Q5. Explain the 0/-E return convention used in Linux kernel modules.

Answer: The kernel follows a clear convention for init function return values: return 0 on success, return a negative errno value on failure. For example, return -ENOMEM for memory allocation failure, return -ENODEV if a device is not found. The negative sign is important — the kernel and glibc work together to convert this into a positive errno value in user-space. The user-space tool (insmod) then reads errno and prints a meaningful error message. This convention is used throughout the kernel, not just in modules.

Q6. What does the static keyword do in the init and exit function signatures?

Answer: Marking both functions static limits their scope to the current translation unit (the .c file). This means these functions cannot be called or referenced from other modules or other kernel code. Since the init and exit functions are only meant to be called by the kernel’s module framework via the registered function pointers, there is no reason to make them globally visible. Using static is good practice — it avoids naming conflicts across the kernel and follows the principle of minimum visibility.

You Have Covered the Core LKM Concepts!

You now understand kernel headers, module macros, entry/exit points, and the return value convention. In the next part we will build and run our first real module on a live kernel 6.x system.

Prev_Lec
Next_Lec

 

Leave a Reply

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