kernel——module

模块机制让kernel有伸缩性,既保有宏内核的高效,又有一定微内核的稳定性。

1. 单个模块

1.1 模块的编译

1.1.1 源码树内编译

在源码树内添加模块

linux-5.16.2# touch drivers/char/hello.c

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

static int __init hello_init(void)
{
        printk("hello world\n");
        return 0;
}

static void __exit hello_exit(void)
{

}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
  • __init 和 __exit 让被修饰的函数加入 init 或 exit 段,因为 init 函数和 exit 函数只需要执行一次,所以当其被执行后kernel可以很方便的释放 init段 和 exit段的代码,以节省内存
  • module_init 和 module_exit 宏声明的函数为模块入口和出口函数
  • MODULE_LICENSE 若不是 GPL 等开源协议,kernel会禁用一些功能

将模块加入Kbuild
vi drivers/char/Kconfig

config HELLO
    tristate "hello demo"
    help
        demo for module
  • HELLO 会生成 变量 CONFIG_HELLO,写入 .config中

vi driver/char/Makefile

obj-$(CONFIG_HELLO) += hello.o
  • kbuild使用 -f 分别加载源码不同目录下的Makefile,每个Makefile需要告诉Kbuid 本模块的 obj-y 或 obj-m(不同模块的obj-y 和 obj-m不会累加)。
  • kbuild根据 .config的内容生成 include/config/auto.conf,其中定义了 变量 CONFIG_HELLO,kbuild会包含auto.conf,以定义 obj-$(CONFIG_HELLO)为 obj-m 或 obj-y,若为 obj-m ,则意味需构造名为 hello.ko的模块,若为 obj-y,生成一个 built-in.a,各个子目录下的 built-in.a 会链接成 父目录的 built-in.a ,最后链接成 vmlinux

make menuconfig
将hello设置为M

make modules

  • 构造所有目录下 obj-m

将ko放到modules目录
mv hello.ko /lib/modules/5.16.2

加载模块
modprobe hello

卸载模块
rmmod hello

1.1.2 out-of-tree编译

准备单独目录
root@ubuntu:~/wlt/build/my/hello# ls
hello.c Makefile

cat Makefile

.PHONY: all clean

obj-m := hello.o

CROSS_COMPILE := arm-linux-gnueabi-
KBUILD_DIR := /root/wlt/build/linux-5.16.2

all:
        make -C $(KBUILD_DIR) M=$(PWD) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) modules

clean:
        make -C $(KBUILD_DIR) M=$(PWD) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) modules clean
  • 此Makefile会被引用两次,第一次实际有效内容如下
.PHONY: all clean

CROSS_COMPILE := arm-linux-gnueabi-
KBUILD_DIR := /root/wlt/build/linux-5.16.2

all:
        make -C $(KBUILD_DIR) M=$(PWD) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) modules

clean:
        make -C $(KBUILD_DIR) M=$(PWD) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) modules clean
  • 由于定义了 M变量,所以kernel/Makefile会 make -f $(M),此时有效内容如下
obj-m := hello.o

若是多个目标文件构成一个模块,则这样写

.PHONY: all clean

obj-m := hello.o
hello-objs := main.o add.o sub.o

CROSS_COMPILE := arm-linux-gnueabi-
KBUILD_DIR := /root/wlt/build/linux-5.16.2

all:
        make -C $(KBUILD_DIR) M=$(PWD) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) modules

clean:
        make -C $(KBUILD_DIR) M=$(PWD) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) modules clean
  • obj-m := hello.o 定义一个模块 hello
  • hello-objs := main.o add.o sub.o, hello模块由 main.o add.o sub.o 目标文件构成
  • 由于 main.o add.o sub.o 会被链接成 module.o,这里module名称为hello,所以 会生成 hello.o ,所以 不能 写成 hello-objs := hello.o add.o sub.o

1.2 模块参数

使用 module_param 导出参数
module_param(name, type, perm)

在main.c加入

#include <linux/moduleparam.h>

static int num;
module_param(num, int, 0664);

module_param(name, type, perm)
perm不能为 0666

模块加载后,可以在 /sys/module/hello/parameters/ 下 读写参数值

可以通过uboot给模块传参,bootargs 添加 'hello.num=111'

2. 两个模块

2.1 符号导出和引用

使用 EXPORT_SYMBOL(sym) 导出符号

构造math模块

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

int math_add(int a, int b)
{
        return a + b;
}
EXPORT_SYMBOL(math_add);

int math_sub(int a, int b)
{
        return a - b;
}
EXPORT_SYMBOL(math_sub);

static int __init math_init(void)
{
        return 0;
}

module_init(math_init);

MODULE_LICENSE("GPL");

math编译成功后会生成
root@ubuntu:~/wlt/build/my/math# cat Module.symvers
0x00000000 math_sub /root/wlt/build/my/math/math EXPORT_SYMBOL
0x00000000 math_add /root/wlt/build/my/math/math EXPORT_SYMBOL

构造 hello 模块,引用math的 add

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

extern int math_add(int a, int b);

static int a;
static int b;

module_param(a, int, 0664);
module_param(b, int, 0664);

static int __init hello_init(void)
{
        printk("a + b : %d\n", math_add(a, b));
        return 0;
}

static void __exit hello_exit(void)
{
        printk("hello exit\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");

hello的Makefile 需要添加 match 的 导出符号表

ifneq ($(KERNELRELEASE),)

obj-m := hello.o
hello-objs := main.o

else

.PHONY: all clean

KBUILD_EXTRA_SYMBOLS += /root/wlt/build/my/math/Module.symvers
export KBUILD_EXTRA_SYMBOLS

CROSS_COMPILE := arm-linux-gnueabi-
KBUILD_DIR := /root/wlt/build/linux-5.16.2

all:
        make -C $(KBUILD_DIR) M=$(PWD) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) modules

clean:
        make -C $(KBUILD_DIR) M=$(PWD) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) modules clean

endif

2.2 模块依赖和自动加载

将模块放到 /lib/modules/${KVERSION}/
生成模块依赖
depmod -a
自动加载
modprobe hello
自动卸载
modprobe -r hello

3. 模块和驱动

一个字符设备驱动

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

#define DEV_MAJOR    222
#define DEV_NAME     "hello"

static char hello_buf[256];

static ssize_t
hello_read (struct file *fp, char __user *ubuf,
                size_t size, loff_t *pos)
{
        unsigned long p = *pos;
        unsigned long count = size;

        if (p >= 256)
                return -1;
        if (count > 256 - p)
                count = 256 - p;

        return copy_to_user(ubuf, hello_buf + p, count);
}

static ssize_t
hello_write (struct file *fp, const char __user *ubuf,
                size_t size, loff_t *pos)
{
        unsigned long p = *pos;
        unsigned long count = size;

        if (p >= 256)
                return -1;
        if (count > 256 - p)
                count = 256 - p;

        return copy_from_user((char *)hello_buf + p, ubuf, count);
}

static int
hello_open (struct inode *inode, struct file *fp)
{
        return 0;
}

static int
hello_release (struct inode *inode, struct file *fp)
{
        return 0;
}

static struct file_operations hello_ops = {
        .owner = THIS_MODULE,
        .read = hello_read,
        .write = hello_write,
        .open = hello_open,
        .release = hello_release
};

static int __init
hello_init(void)
{
        // 静态分配 主设备号,
        // 绑定  主设备号 和 设备名 和 fops
        // 如此,可通过 文件系统 找到 主设备号,进而找到 驱动
        if (register_chrdev(DEV_MAJOR, DEV_NAME, &hello_ops) < 0) {
                printk("Failed to register_chrdev");
                return -1;
        }

        return 0;
}

static void __exit
hello_exit(void)
{
        unregister_chrdev(DEV_MAJOR, DEV_NAME);
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

测试程序

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


int main()
{
        int fd;
        char buf[256];

        if ((fd = open("/dev/hello", O_RDWR)) < 0) {
                perror("open");
                return -1;
        }
        if (write(fd, "hello", 5) < 0) {
                perror("write");
                return -1;
        }
        if (read(fd, buf, sizeof(buf)) < 0) {
                perror("read");
                return -1;
        }
        printf("read : %s\n", buf);

        close(fd);

        return 0;
}

测试
加载驱动后,创建设备节点
mknod /dev/hello c 222 0
运行测试程序
yangxr@vexpress:/root # ./hello_test
read : hello

3. 模块加载流程

3.1 外部加载

3.1.1 调用栈

[<8010f358>] (unwind_backtrace) from [<8010b254>] (show_stack+0x10/0x14)
[<8010b254>] (show_stack) from [<80836b74>] (dump_stack_lvl+0x40/0x4c)
[<80836b74>] (dump_stack_lvl) from [<7f00500c>] (hello_init+0xc/0x1000 [hello])
[<7f00500c>] (hello_init [hello]) from [<80101f90>] (do_one_initcall+0x48/0x1e8)
[<80101f90>] (do_one_initcall) from [<808313b0>] (do_init_module+0x54/0x208)
[<808313b0>] (do_init_module) from [<801aca54>] (load_module+0x2058/0x2670)
[<801aca54>] (load_module) from [<801ad1c4>] (sys_init_module+0x158/0x170)
[<801ad1c4>] (sys_init_module) from [<80100060>] (ret_fast_syscall+0x0/0x54)

关键是三个函数
load_module : 检查模块信息,加载模块,重定位代码,构造 mod 对象
do_init_module:执行mod对象的init函数,删除 __init 段
do_one_initcall:调用 init 函数

3.1.2 init函数的调用

  1287 int __init_or_module do_one_initcall(initcall_t fn)
  1288 {
  1295     .....
  1297     ret = fn();

  3713 static noinline int do_init_module(struct module *mod)
  3714 {
           .....
  3733     if (mod->init != NULL)
  3734         ret = do_one_initcall(mod->init);

要调用init函数,必须构造 mod 对象。

模块编译时 生成 mod 对象

root@ubuntu:~/wlt/build/my/hello# cat hello.mod.c
...
__visible struct module __this_module
__section(".gnu.linkonce.this_module") = {
        .name = KBUILD_MODNAME,
        .init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
        .exit = cleanup_module,
#endif
        .arch = MODULE_ARCH_INIT,
};
...

定义 mod 对象 在 .gnu.linkonce.this_module 段,
所以 模块加载时,是从 .gnu.linkonce.this_module段 加载 mod 对象,
而 .init = init_module,说明实际的 init 函数 符号名为 init_module

  129 /* Each module must use one module_init(). */
  130 #define module_init(initfn)                 \
  131     static inline initcall_t __maybe_unused __inittest(void)        \
  132     { return initfn; }                  \
  133     int init_module(void) __copy(initfn)            \
  134         __attribute__((alias(#initfn)));        \
  135     __CFI_ADDRESSABLE(init_module, __initdata);

可见 module_init宏的作用是 定义一个 函数 init_module,并将 initfn的代码复制给
init_module,并区别名。
所以 module_init(hello_init)后, init_module 就是 hello_init。

3.1.3 模块化加载总结

insmod命令会加载模块到内存,对模块进行合法性检查,并进行重定位,
由于 mod 对象位于 .gnu.linkonce.this_module 段,可以直接使用 mod 对象,
调用 mod->init 函数完成 模块初始化,释放 __init 段的内存。

3.2 模块内嵌内核的初始化

3.2.1 module_init 分析

module_init定义
include/linux/module.h

  #ifndef MODULE

  #define module_init(x)  __initcall(x);

  #else /* MODULE */

  /* Each module must use one module_init(). */
  #define module_init(initfn)                 \
      static inline initcall_t __maybe_unused __inittest(void)        \
      { return initfn; }                  \
      int init_module(void) __copy(initfn)            \
          __attribute__((alias(#initfn)));        \
      __CFI_ADDRESSABLE(init_module, __initdata);

#endif

而 MODULE 在 Makefile中定义

KBUILD_CFLAGS_KERNEL :=   
KBUILD_CFLAGS_MODULE  := -DMODULE k

所以若 编译的是 module 则定义 MODULE,否则不定义,
这里是内嵌内核,所以module_init的定义是

  #define module_init(x)  __initcall(x);

__initcall的定义是

  253 #define __unique_initcall(fn, id, __sec, __iid)         \
  254     ____define_initcall(fn,                 \
  255         __initcall_stub(fn, __iid, id),         \
  256         __initcall_name(initcall, __iid, id),       \
  257         __initcall_section(__sec, __iid))

  259 #define ___define_initcall(fn, id, __sec)           \
  260     __unique_initcall(fn, id, __sec, __initcall_id(fn))

#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
#define device_initcall(fn)     __define_initcall(fn, 6)
 #define __initcall(fn) device_initcall(fn)

所以__initcall最终展开为 ____define_initcall,
而____define_initcall定义

  239 #ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS
  240 #define ____define_initcall(fn, __stub, __name, __sec)      \
  241     __define_initcall_stub(__stub, fn)          \
  242     asm(".section   \"" __sec "\", \"a\"        \n" \
  243         __stringify(__name) ":          \n" \
  244         ".long  " __stringify(__stub) " - . \n" \
  245         ".previous                  \n");   \
  246     static_assert(__same_type(initcall_t, &fn));
  247 #else
  248 #define ____define_initcall(fn, __unused, __name, __sec)    \
  249     static initcall_t __name __used             \
  250         __attribute__((__section__(__sec))) = fn;
  251 #endif

CONFIG_HAVE_ARCH_PREL32_RELOCATIONS 的定义在 include/generated/autoconf.h(就是Kbuild生成的)。
我们看汇编(因为汇编更清晰)

  240 #define ____define_initcall(fn, __stub, __name, __sec)      \
  241     __define_initcall_stub(__stub, fn)          \
  242     asm(".section   \"" __sec "\", \"a\"        \n" \   // 定义段
  243         __stringify(__name) ":          \n" \           // 在该段定义一个符号
  244         ".long  " __stringify(__stub) " - . \n" \       // 这个符号对应的空间为long大小,值为 __stringify(__stub) - . 
  245         ".previous                  \n");   \           // 使用以前的段(本段定义结束)

    其中 __stringify(__stub) - .  为 函数地址 - 段起始地址,即这里保存一个偏移地址。
    通过此偏移地址和段起始地址就能找到 函数地址。

示例

module_init(hello_init);
__initcall(hello_init);
device_initcall(hello_init, 6);
__define_initcall(hello_init, 6);
___define_initcall(hello_init, 6, .initcall6);

.section ".initcall6.init", "a"
__initcall_hello_init6:
.long hello_init - .
.previous

在 段 .initcall6.init 定义一个符号 __initcall_hello_init6,值为 偏移地址(函数地址 - 段起始地址)。

总结:
module_init(fn)
定义 一个偏移值,定义在 特定的段 .initcall6.init.

3.2.2 init函数的调用

从上面我们获得关键信息 initcall6,搜索可得:

  1317 extern initcall_entry_t __initcall_start[];
  1318 extern initcall_entry_t __initcall0_start[];
  1319 extern initcall_entry_t __initcall1_start[];
  1320 extern initcall_entry_t __initcall2_start[];
  1321 extern initcall_entry_t __initcall3_start[];
  1322 extern initcall_entry_t __initcall4_start[];
  1323 extern initcall_entry_t __initcall5_start[];
  1324 extern initcall_entry_t __initcall6_start[];
  1325 extern initcall_entry_t __initcall7_start[];
  1326 extern initcall_entry_t __initcall_end[];
  1327
  1328 static initcall_entry_t *initcall_levels[] __initdata = {
  1329     __initcall0_start,
  1330     __initcall1_start,
  1331     __initcall2_start,
  1332     __initcall3_start,
  1333     __initcall4_start,
  1334     __initcall5_start,
  1335     __initcall6_start,
  1336     __initcall7_start,
  1337     __initcall_end,
  1338 };

定义一个指针数组,并引用一些符号,但这和 .initcall6 段没关系

搜索 __initcallx_start ,发现定义在链接脚本中

 59  .init.data : AT(ADDR(.init.data) - 0) { KEEP(*(SORT(___kentry+*))) *(.init.data init.data.*) *(.meminit.data*    ) *(.init.rodata .init.rodata.*) . = ALIGN(8); __start_ftrace_events = .; KEEP(*(_ftrace_events)) __stop_ftrac    e_events = .; __start_ftrace_eval_maps = .; KEEP(*(_ftrace_eval_map)) __stop_ftrace_eval_maps = .; *(.meminit.    rodata) . = ALIGN(8); __clk_of_table = .; KEEP(*(__clk_of_table)) KEEP(*(__clk_of_table_end)) . = ALIGN(8); __    reservedmem_of_table = .; KEEP(*(__reservedmem_of_table)) KEEP(*(__reservedmem_of_table_end)) . = ALIGN(8); __    timer_of_table = .; KEEP(*(__timer_of_table)) KEEP(*(__timer_of_table_end)) . = ALIGN(8); __cpu_method_of_tabl    e = .; KEEP(*(__cpu_method_of_table)) KEEP(*(__cpu_method_of_table_end)) . = ALIGN(8); __cpuidle_method_of_tab    le = .; KEEP(*(__cpuidle_method_of_table)) KEEP(*(__cpuidle_method_of_table_end)) . = ALIGN(32); __dtb_start =     .; KEEP(*(.dtb.init.rodata)) __dtb_end = .; . = ALIGN(8); __irqchip_of_table = .; KEEP(*(__irqchip_of_table))     KEEP(*(__irqchip_of_table_end)) . = ALIGN(8); __earlycon_table = .; KEEP(*(__earlycon_table)) __earlycon_tabl    e_end = .; . = ALIGN(8); __kunit_suites_start = .; KEEP(*(.kunit_test_suites)) __kunit_suites_end = .; . = ALI    GN(16); __setup_start = .; KEEP(*(.init.setup)) __setup_end = .; __initcall_start = .; KEEP(*(.initcallearly.init)) __initcall0_start = .; KEEP(*(.initcall0.init)) KEEP(*(.initcall0s.init)) __initcall1_start = .; KEEP(*(    .initcall1.init)) KEEP(*(.initcall1s.init)) __initcall2_start = .; KEEP(*(.initcall2.init)) KEEP(*(.initcall2s    .init)) __initcall3_start = .; KEEP(*(.initcall3.init)) KEEP(*(.initcall3s.init)) __initcall4_start = .; KEEP(    *(.initcall4.init)) KEEP(*(.initcall4s.init)) __initcall5_start = .; KEEP(*(.initcall5.init)) KEEP(*(.initcall    5s.init)) __initcallrootfs_start = .; KEEP(*(.initcallrootfs.init)) KEEP(*(.initcallrootfss.init)) __initcall6    _start = .; KEEP(*(.initcall6.init)) KEEP(*(.initcall6s.init)) __initcall7_start = .; KEEP(*(.initcall7.init))     KEEP(*(.initcall7s.init)) __initcall_end = .; __con_initcall_start = .; KEEP(*(.con_initcall.init)) __con_ini    tcall_end = .; . = ALIGN(4); __initramfs_start = .; KEEP(*(.init.ramfs)) . = ALIGN(8); KEEP(*(.init.ramfs.info    )) }

__initcall0_start = .; KEEP((.initcall0.init)) KEEP((.initcall0s.init)) __initcall1_start = .;
可见,链接脚本将 .initcallN.init 的段的 内容链接到一起,并 定义 __initcallN_start 符号为 该段的起始地址, __initcallN+1_start符号为该段的结束地址。
一共有 7个 initcall段,最后的结束地址用符号 __initcall_end 标记。

结合c代码中定义,可知:
所有 module_init 修饰的函数 fn,会定义一个 long类型的变量,值为相对于 fn 的偏移量,所有 变量会组成一个数组,定义在 .init.data段,数组起始地址用 __initcall6_start 标记,结束地址用 __initcall7_start标记。所有初始化相关数组又构成一个大数组 initcall_levels
所以要调用函数 fn,必须遍历 函数 initcall_levels.

搜索initcall_levels的使用。

  1287 int __init_or_module do_one_initcall(initcall_t fn)
  1288 {
          ...
  1295
  1296     do_trace_initcall_start(fn);
  1297     ret = fn();
  1298     do_trace_initcall_finish(fn, ret);
          ...

  1313     return ret;
  1314 }


  1358 static void __init do_initcall_level(int level, char *command_line)
  1359 {
  1360     initcall_entry_t *fn;
          ...
  1369     for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
  1370         do_one_initcall(initcall_from_entry(fn));
  1371 }

可见 若 level为 6,则执行 __initcall6_start数组的所有函数
而initcall_from_entry 为 当 地址值 加 内存的值,而内存中记录相对于module_init修饰的函数的偏移量,所以获得 模块初始化函数的地址。

  250 static inline void *offset_to_ptr(const int *off)
  251 {
  252     return (void *)((unsigned long)off + *off);
  253 }

  122 static inline initcall_t initcall_from_entry(initcall_entry_t *entry)
  123 {
  124     return offset_to_ptr(entry);
  125 }

do_initcall_level被do_initcalls调用,do_initcalls完成所有初始化函数的执行

  1373 static void __init do_initcalls(void)
  1374 {
  1375     int level;
  1376     size_t len = strlen(saved_command_line) + 1;
  1377     char *command_line;
  1378
  1379     command_line = kzalloc(len, GFP_KERNEL);
  1380     if (!command_line)
  1381         panic("%s: Failed to allocate %zu bytes\n", __func__, len);
  1382
  1383     for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) {
  1384         /* Parser modifies command_line, restore it each time */
  1385         strcpy(command_line, saved_command_line);
  1386         do_initcall_level(level, command_line);
  1387     }
  1388
  1389     kfree(command_line);
  1390 }

而 do_basic_setup 调用 do_initcalls
kernel_init_freeable 调用 do_basic_setup
kernel_init 调用 kernel_init_freeable

3.2.3 释放空间

kernel_init 调用完 kernel_init_freeable后 释放 无用的空间。

  1490 static int __ref kernel_init(void *unused)
  1491 {
  1492     int ret;
  1493
  1494     /*
  1495      * Wait until kthreadd is all set-up.
  1496      */
  1497     wait_for_completion(&kthreadd_done);
  1498
  1499     kernel_init_freeable();
  1500     /* need to finish all async __init code before freeing the memory */
  1501     async_synchronize_full();
  1502
  1503     system_state = SYSTEM_FREEING_INITMEM;
  1504     kprobe_free_init_mem();
  1505     ftrace_free_init_mem();
  1506     kgdb_free_init_mem();
  1507     exit_boot_config();
  1508     free_initmem();
  1509     mark_readonly();
  1510

如下所示,将释放__init_begin 到 __init_end 指针指向的地址之间的空间。并打印 Free unused kernel image memory xxK

  513 void free_initmem(void)
  514 {
  515     fix_kernmem_perms();
  516
  517     poison_init_mem(__init_begin, __init_end - __init_begin);
  518     if (!machine_is_integrator() && !machine_is_cintegrator())
  519         free_initmem_default(-1);
  520 }
  521

2527 static inline unsigned long free_initmem_default(int poison)
2528 {
2529     extern char __init_begin[], __init_end[];
2530
2531     return free_reserved_area(&__init_begin, &__init_end,
2532                   poison, "unused kernel image (initmem)");
2533 }

  8115 unsigned long free_reserved_area(void *start, void *end, int poison, const char *s)
  8116 {
  8117     void *pos;
  8118     unsigned long pages = 0;
  8119
  8120     start = (void *)PAGE_ALIGN((unsigned long)start);
  8121     end = (void *)((unsigned long)end & PAGE_MASK);
  8122     for (pos = start; pos < end; pos += PAGE_SIZE, pages++) {
  8123         struct page *page = virt_to_page(pos);
  8124         void *direct_map_addr;
  8125
  8126         /*
  8127          * 'direct_map_addr' might be different from 'pos'
  8128          * because some architectures' virt_to_page()
  8129          * work with aliases.  Getting the direct map
  8130          * address ensures that we get a _writeable_
  8131          * alias for the memset().
  8132          */
  8133         direct_map_addr = page_address(page);
  8134         /*
  8135          * Perform a kasan-unchecked memset() since this memory
  8136          * has not been initialized.
  8137          */
  8138         direct_map_addr = kasan_reset_tag(direct_map_addr);
  8139         if ((unsigned int)poison <= 0xFF)
  8140             memset(direct_map_addr, poison, PAGE_SIZE);
  8141
  8142         free_reserved_page(page);
  8143     }
  8144
  8145     if (pages && s)
  8146         pr_info("Freeing %s memory: %ldK\n", s, K(pages));
  8147
  8148     return pages;
  8149 }

__init_begin和 __init_end在链接脚本 arch/arm/kernel/vmlinux.lds中定义,
而其中有定义

将 .init.text 段放到 __init_begin 和 __init_end 之间。
我们回顾 module_init(fn) 时, fn 的声明为

static int __init fn(void) 

根据目的,我们需要把 fn 的定义放到 __init_begin 和 __init_end 之间,以让其在执行后被释放。
我们查看 __init 宏的定义

   50 #define __init      __section(".init.text") __cold  __latent_entropy __noinitretpoline __nocfi

可见 __init 就是将修饰的符号 定义在 .init.text段,
再看连接脚本中 .init.text的位置

 30  __init_begin = .;
   ...
 . = ALIGN(8); .init.text : AT(ADDR(.init.text) - 0) { _sinittext = .; *(.init.text .init.text.*) *(.text.star    tup) *(.meminit.text*) _einittext = .; }
   ...
 65  __init_end = .;

可见 .init.text在 __init_begin和 __init_end之间,所以 被 __init宏修饰的 符号将会被free_initmem释放。

3.2.4 总结

模块内嵌内核时,module_init宏将 定义一个 内存空间 使用 基址加变址的方式保存 被修饰的函数地址,并将所有同类型空间链接到同一段.initcall6.init ,并定义 符号 __initcall6_start作为数组首地址,内核初始化时会遍历数组以执行模块初始化函数。所有的初始化完成后,会释放 __init_begin 到__init_end之间的空间,由于 模块 初始化函数使用 __init 宏定义,会被连接到 __init_begin和__init_end之间,所以模块初始化函数会被释放。

上一篇:python __init__.py导入


下一篇:模块准备2