Modle_init and Modle_exit Principles of Kernel Modules

Keywords: Linux Attribute Makefile glibc

Preface

When we develop a kernel driver, we are actually developing a module of the kernel. Any module will have an initialization load function and an uninstall function. When are they called? What's the difference between calling modules in static link form and dynamic load form? This article will answer these questions.

Any kernel module has at least the following structure:

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

static int __init hello_init(void)
{
    printk(KERN_ALERT "Hello World\n");
    return 0;
}

static void __exit hello_exit(void)
{
    printk(KERN_ALERT "Bye Bye World\n");
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("Dual BSD/GPL");

In this module, hello_init and hello_exit are the loading and unloading functions of the module. Both _init and _exit are macros. They encapsulate the compiler attributes used by the kernel. The functions modified by them are placed in specific segments by the compiler. For example, the functions modified by _init are placed in the. init.text segment. The functions in this segment are cleared after the initialization phase is completed to reduce unnecessary memory consumption. Their definition is / include/linux/init.h

/* These are for everybody (although not all archs will actually
   discard it in modules) */
#define __init		__section(.init.text) __cold notrace
#define __initdata	__section(.init.data)
#define __initconst	__constsection(.init.rodata)
#define __exitdata	__section(.exit.data)
#define __exit_call	__used __section(.exitcall.exit)

_ Both init and _exit are optional, but module_init and module_exit must be added, and only they can ultimately lead to load and unload functions being called.

Modular

There are two ways to run module code: static compilation and linking into the kernel, loading initialization during system startup; dynamic loading module compiled into the kernel through insmod dynamic loading and relocation. These two options can be selected in Makefile through the obj-y or obj-m options.  
Once the dynamically loadable module object code (.ko) is loaded and relocated to the kernel, its domain of action and the code of static link are completely equivalent. So the advantages of this mode of operation are obvious:

  1. The dynamic loading module can be run according to the needs of the system to expand the function of the kernel and unload it when not needed to release the memory space.
  2. When it is necessary to modify the functions of the kernel, only the corresponding modules need to be compiled, rather than the whole kernel need to be recompiled.

Because of this advantage, device driver development is basically compiled into dynamically loadable modules. However, it should be noted that some modules must be compiled into the kernel, run with the kernel, and never uninstall, such as vfs, platform_bus, etc.

So how can the same C code implement these two approaches? The answer is module_init macro! Let's analyze the module_init macro. (The Linux kernel version used here is 3.10.10)
Locate to include/linux/init.h in the Linux kernel source code, and you can see the following code:

#ifndef MODULE
// ellipsis
#define module_init(x)  __initcall(x);
// ellipsis
#else

#define module_init(initfn) \
    int init_module(void) __attribute__((alias(#initfn)));
// ellipsis
#endif

Obviously, MODULE is controlled by Makefile. The upper part is used to connect static compilation of modules into the kernel, and the lower part is used to compile dynamically loadable modules. Next, we analyze these two situations.

Static Link Mode

#define module_init(x)  __initcall(x);
|
--> #define __initcall(fn) device_initcall(fn)
    |
    --> #define device_initcall(fn)     __define_initcall(fn, 6)
        |
        --> #define __define_initcall(fn, id) \
                static initcall_t __initcall_##fn##id __used \
                __attribute__((__section__(".initcall" #id ".init"))) = fn

That is, module_init(hello_init) is expanded as:

static initcall_t __initcall_hello_init6 __used \
    __attribute__((__section__(".initcall6.init"))) = hello_init

Here initcall_t is the function pointer type, as follows:

typedef int (*initcall_t)(void);

GNU compilation toolchain supports user-defined section s, so when we read Linux source code, we will find that a large number of uses are as follows:

__attribute__((__section__("section-name"))) 

_ Attribute_ is used to specify special attributes of variables or structural bitfields, followed by a description of attributes in double brackets, whose grammatical format is _attribute_((attribute-list)). It has positional constraints, usually placed at the end of the declaration and before ";".  
The attribute-list here is _section_(".initcall6.init"). Typically, the compiler stores the generated code in the. text section. But sometimes other segments may be needed, or some functions and variables need to be stored in a special segment. The section attribute is used to specify that a function or variable should be stored in a specific segment.

So what this means is: Define a function pointer variable named _initcall_hello_init6 and initialize it as hello_init (pointing to hello_init); and store the function pointer variable in the. initcall 6.init code segment.

Next, we look at the link script (arch/$(ARCH)/kernel/vmlinux.lds.S) to see the. initcall 6.init section. Taking / arch/arm/kernel/vmlinux.lds.S as an example, there is a. init.data segment:

	.init.data : {
		INIT_DATA
		INIT_SETUP(16)
		INIT_CALLS
		CON_INITCALL
		SECURITY_INITCALL
		INIT_RAM_FS
	}

As you can see, the.init.data section contains INIT_CALLS, which is defined in / include/asm-generic/vmlinux.lds.h. INIT_CALLS can be expanded to:

#define INIT_CALLS							\
		VMLINUX_SYMBOL(__initcall_start) = .;			\
		*(.initcallearly.init)					\
		INIT_CALLS_LEVEL(0)					\
		INIT_CALLS_LEVEL(1)					\
		INIT_CALLS_LEVEL(2)					\
		INIT_CALLS_LEVEL(3)					\
		INIT_CALLS_LEVEL(4)					\
		INIT_CALLS_LEVEL(5)					\
		INIT_CALLS_LEVEL(rootfs)				\
		INIT_CALLS_LEVEL(6)					\
		INIT_CALLS_LEVEL(7)					\
		VMLINUX_SYMBOL(__initcall_end) = .;

Further expand as follows:

        __initcall_start = .;           \
        *(.initcallearly.init)          \
        __initcall0_start = .;          \
        *(.initcall0.init)              \
        *(.initcall0s.init)             \
        // Eliminate 1, 2, 3, 4, 5
        __initcallrootfs_start = .;     \
        *(.initcallrootfs.init)         \
        *(.initcallrootfss.init)        \
        __initcall6_start = .;          \
        *(.initcall6.init)              \
        *(.initcall6s.init)             \
        __initcall7_start = .;          \
        *(.initcall7.init)              \
        *(.initcall7s.init)             \
        __initcall_end = .;

These code segments are eventually organized sequentially in the kernel.img, which determines the execution order of some of the functions located in them (_initcall_hello_init6 in the. initcall 6.init segment). The. init or. initcalls segment is characterized by memory being freed when the kernel is booted. This can be seen from the kernel boot information:

Freeing unused kernel memory: 124K (80312000 - 80331000)

How is _initcall_hello_init6 stored in the.initcall6.init segment called? Let's look at the file init/main.c. The code is as follows:

start_kernel
|
--> rest_init
    |
    --> kernel_thread
        |
        --> kernel_init
            |
            --> kernel_init_freeable
                |
                --> do_basic_setup
                    |
                    --> do_initcalls
                        |
                        --> do_initcall_level(level)
                            |
                            --> do_one_initcall(initcall_t fn)

The kernel_init function is called as a kernel thread (which eventually starts the first user process init).  
We focus on the do_initcalls function as follows:

static void __init do_initcalls(void)
{
    int level;

    for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
        do_initcall_level(level);
}

The function do_initcall_level is as follows:

static void __init do_initcall_level(int level)
{
    // ellipsis
    for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
        do_one_initcall(*fn);
}

The function do_one_initcall is as follows:

int __init_or_module do_one_initcall(initcall_t fn)
{
    int ret;
    // ellipsis
    ret = fn();
    return ret;
}

initcall_levels are defined as follows:

static initcall_t *initcall_levels[] __initdata = {
    __initcall0_start,
    __initcall1_start,
    __initcall2_start,
    __initcall3_start,
    __initcall4_start,
    __initcall5_start,
    __initcall6_start,
    __initcall7_start,
    __initcall_end,
};

The members in initcall_levels[] are derived from the expansion of INIT_CALLS, such as "_initcall0_start=;", where _initcall0_start is a variable, which acts the same as the variables defined in the code, so that _initcall0_start can be used in the code. Therefore, these variables can be introduced in init/main.c by extern method, as follows:

extern initcall_t __initcall_start[];
extern initcall_t __initcall0_start[];
extern initcall_t __initcall1_start[];
extern initcall_t __initcall2_start[];
extern initcall_t __initcall3_start[];
extern initcall_t __initcall4_start[];
extern initcall_t __initcall5_start[];
extern initcall_t __initcall6_start[];
extern initcall_t __initcall7_start[];
extern initcall_t __initcall_end[];

It's basically clear here that in the do_initcalls function, each function pointer in the initcalls segment is traversed, and then the function pointer is executed. Because the compiler links the function pointers to the specified location according to the requirements of the link script, it can safely use do_one_initcall(*fn) to perform the related initialization functions.  
In our example, module_init(hello_init) is the initcalls segment of Level 6. Many peripheral drivers call module_init macros later. If static compilation is connected to the kernel, these function pointers will be inserted into the initcall6.init segment in the order of compilation, and then wait for the do_initcalls function call.

Dynamic Loading Mode

#define module_init(initfn)                 \
    static inline initcall_t __inittest(void)       \
    { return initfn; }                  \
    int init_module(void) __attribute__((alias(#initfn)));

_ The purpose of inittest is simply to check whether the defined function conforms to the initcall_t type, and if it is not the inittest type, the error will be reported at compile time. So the real macro definition is:

#define module_init(initfn)                 \
    int init_module(void) __attribute__((alias(#initfn)));

Therefore, when loading dynamically, you can directly define init_module and cleanup_module functions without using module_init and module_exit macros, and the effect is the same.  
The alias attribute is a unique attribute of gcc, which defines init_module as an alias for the function initfn. So module_init(hello_init) defines a variable name, init_module, whose address is the same as hello_init.  
The HelloWorld.mod.c file will be generated automatically in the process of compiling the dynamic loading module of the above example. The contents are as follows:

#include <linux/module.h>
#include <linux/vermagic.h>
#include <linux/compiler.h>

MODULE_INFO(vermagic, VERMAGIC_STRING);

struct module __this_module
__attribute__((section(".gnu.linkonce.this_module"))) = {
    .name = KBUILD_MODNAME,
    .init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
    .exit = cleanup_module,
#endif
    .arch = MODULE_ARCH_INIT,
};

static const char __module_depends[]
__used
__attribute__((section(".modinfo"))) =
"depends=";

As you can see, it defines a global variable of type module _this_module, whose member init is init_module (hello_init), and the variable is linked to the. gnu.linkonce.this_module segment.

The compiled HelloWorld.ko needs to be loaded into the kernel through insmod. Since insmod is a user-level command provided by busybox, we need to read the source code of busybox. The code is sorted out as follows: (file busybox/modutils/insmod.c)

insmod_main
|
--> bb_init_module
    |
    --> init_module

init_module is defined as follows: (file busybox/modutils/modutils.c)

#define init_module(mod, len, opts) syscall(__NR_init_module, mod, len, opts)

Therefore, the system calls the sys_init_module function corresponding to the kernel layer.

Back to the Linux kernel source code (kernel/module.c), code combing:

SYSCALL_DEFINE3(init_module, ...)
|
-->load_module
    |
    --> do_init_module(mod)
        |
        --> do_one_initcall(mod->init);

In the file (include/linux/syscalls.h), there are:

#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)

Thus the sys_init_module function is formed.

Related articles

Seeing System Call Principle from glibc Source Code

Posted by quintus on Wed, 26 Jun 2019 12:12:32 -0700