模块机制让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之间,所以模块初始化函数会被释放。