我们先看一下代码:
#include <linux/module.h>
#include <linux/init.h>
// 模块安装函数
static int __init chrdev_init(void)
{
printk(KERN_INFO "chrdev_init helloworld init\n");
return 0;
}
// 模块卸载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
}
module_init(chrdev_init);
module_exit(chrdev_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("Mark"); // 描述模块的作者
MODULE_DESCRIPTION("test for driver"); // 描述模块的介绍信息
MODULE_ALIAS("alias test"); // 描述模块的别名信息
MODULE_xxx 这种宏作用是用来添加模块描述信息
我们可以使用modinfo查看模块相关的信息:例如我在ubuntu上使用:
modinfo modules_test.ko,可以看到:
filename: /mnt/hgfs/edit/2019-05-02/drivers/first_drivers/module_test.ko
alias: alias test
description: test for driver
author: Mark
license: GPL
srcversion: 79217AF7022E18502BC7A9F
depends:
vermagic: 3.13.0-164-generic SMP mod_unload modversions 686 retpoline
我们使用insmod命令来安装这个模块,rmmod来卸载模块,lsmod可以显示已经安装的所有模块,还有modprobe和depmod两个命令,depmod是用来生成模块的依赖关系的,modprode也是用来加载模块的,只是在加载的时候,会首先安装它依赖的模块,所以depmod和modprobe是一起用的。
module_init(chrdev_init);
module_exit(chrdev_exit);
这两个宏是用来申明我们的模块加载函数和模块的卸载函数的,也就是我们加载模块的时候(insmod或者modprobe)就会执行module_init指定的函数chrdev_init,卸载模块rmmod的时候就会执行module_exit指定的函数chrdev_exit
这两个宏定义在: include/linux/init.h中
#ifndef MODULE
#define module_init(x) __initcall(x);
#define module_exit(x) __exitcall(x);
#else
/* Each module must use one module_init(). */
#define module_init(initfn)
static inline initcall_t __inittest(void)
{ return initfn; }
int init_module(void) attribute((alias(#initfn)));
/* This is only required if you want to be unloadable. */
#define module_exit(exitfn) \
static inline exitcall_t __exittest(void) \
{ return exitfn; } \
void cleanup_module(void) __attribute__((alias(#exitfn)));
#endif
这里就有两种情况了,定义为模块还是定义为非模块,所谓模块就是指定为M,生成.ko文件。所谓非模块就是集成到内核里面去了,Y选项
先看定义为非模块:也就是集成在内核里面了
定义继续往下:
#define __initcall(fn) device_initcall(fn)
定义继续往下:
#define device_initcall(fn) __define_initcall(“6”,fn,6)
定义继续往下:
#define __define_initcall(level,fn,id)
static initcall_t _initcall##fn##id __used
attribute((section(".initcall" level “.init”))) = fn
所以就是这样的:
static initcall_t __initcall_chrdev_init6 __used
attribute((section(".initcall" 6".init"))) = chrdev_init
分析一下:
- initcall_t 是一个函数指针,typedef int (*initcall_t)(void);
- 函数指针变量的名字是:__initcall_chrdev_init6
- __used 也是一个宏,还可以继续追,这个是告诉编译器我们定义的__initcall_chrdev_init6变量有用,即使不使用也不要报警告
- attribute((section(".initcall" 6".init"))),attribute__用来指定变量或结构位域的特殊属性,其后的双括弧中的内容是属性说明,它的语法格式为:attribute ((attribute-list))。这里的attribute-list为__section(“.initcall6.init”),section属性就是用来指定将一个函数、变量存放在特定的段中。那么这里的意思就是:定义一个函数指针,它的值是chrdev_init,然后把这个指针变量放到.initcall6.init段中。
- 我们查看内核的链接脚本:vim arch/arm/kernel/vmlinux.lds.S,可以看到有一个INIT_CALLS段。
- 我们看INIT_CALLS的定义,在这个文件里面:vim include/asm-generic/vmlinux.lds.h ,
#define INIT_CALLS
VMLINUX_SYMBOL(__initcall_start) = .;
INITCALLS
VMLINUX_SYMBOL(__initcall_end) = .;
7.再看INITCALLS
#define INITCALLS
*(.initcallearly.init)
VMLINUX_SYMBOL(__early_initcall_end) = .;
*(.initcall0.init)
*(.initcall0s.init)
*(.initcall1.init)
*(.initcall1s.init)
*(.initcall2.init)
*(.initcall2s.init)
*(.initcall3.init)
*(.initcall3s.init)
*(.initcall4.init)
*(.initcall4s.init)
*(.initcall5.init)
*(.initcall5s.init)
*(.initcallrootfs.init)
*(.initcall6.init)
*(.initcall6s.init)
*(.initcall7.init)
*(.initcall7s.init)
8.所以:
#define INIT_CALLS
VMLINUX_SYMBOL(__initcall_start) = .;
*(.initcallearly.init)
VMLINUX_SYMBOL(__early_initcall_end) = .;
*(.initcall0.init)
*(.initcall0s.init)
*(.initcall1.init)
*(.initcall1s.init)
*(.initcall2.init)
*(.initcall2s.init)
*(.initcall3.init)
*(.initcall3s.init)
*(.initcall4.init)
*(.initcall4s.init)
*(.initcall5.init)
*(.initcall5s.init)
*(.initcallrootfs.init)
*(.initcall6.init)
*(.initcall6s.init)
*(.initcall7.init)
*(.initcall7s.init)
VMLINUX_SYMBOL(__initcall_end) = .;
9.所以上面的这些段,在我们linux启动的时候,都会被加载。而且这些段在位置上也是有先后之分的。例如:.initcall0.init就会在.initcall6.init前面被加载。
10…init 或者 .initcalls 段的特点就是,当内核启动完毕后,这个段中的内存会被释放掉。这一点从内核启动信息可以看到:Freeing unused kernel memory: 124K (80312000 - 80331000)
那么问题来了,上面的那个段是在哪里被加载的?
start_kernel
rest_init
kernel_init
do_basic_setup
do_initcalls
do_one_initcall
static void __init do_initcalls(void)
{
initcall_t *fn;
for (fn = __early_initcall_end; fn < __initcall_end; fn++)
do_one_initcall(*fn);
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}
kernel_init是一个内核线程,它工作在内核层,可是最后会调用一个应用层的程序,变成应用层,它是所有进程的父进程和祖宗进程。
看到这里基本上就明白了。它会使用函数指针的形式,从init段的开始,一个一个函数去调用,直到段尾。
__early_initcall_end和__initcall_end是在include/asm-generic/vmlinux.lds.h定义的,具体可以去看。
那我们再看一下,如果定义为模块。
原型:
#define module_init(initfn)
static inline initcall_t __inittest(void)
{ return initfn; }
int init_module(void) attribute((alias(#initfn)));
1.__inittest 仅仅是为了检测定义的函数是否符合 initcall_t 类型,如果不是 __inittest 类型在编译时将会报错
2.所以可以看成:
static inline initcall_t
int init_module(void) attribute((alias(#initfn)));
alias 属性是 gcc 的特有属性,将定义 init_module 为函数 initfn 的别名。所以 module_init(chrdev_init) 的作用就是定义一个变量名 init_module,其地址和 chrdev_init是一样的
3.我们最前面编译的代码会生成一个module_test.mod.c的文件:
#include <linux/module.h>
#include <linux/vermagic.h>
#include <linux/compiler.h>
MODULE_INFO(vermagic, VERMAGIC_STRING);
__visible 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 struct modversion_info ____versions[]
__used
attribute((section("__versions"))) = {
{ 0xc7b3657, __VMLINUX_SYMBOL_STR(module_layout) },
{ 0x50eedeb8, __VMLINUX_SYMBOL_STR(printk) },
};
static const char __module_depends[]
__used
attribute((section(".modinfo"))) =
“depends=”;
MODULE_INFO(srcversion, “79217AF7022E18502BC7A9F”);
从上面的程序看:struct module __this_module,定义了一个__this_module的全局变量,放在".gnu.linkonce.this_module"这个段上。有个 .init = init_module,,这个init_module就是代表的chrdev_init
编译生成的module_test.ko文件需要通过insmod来加载,由于 insmod 是 busybox 提供的用户层命令,所以我们需要阅读 busybox 源码。调用如下:(文件 busybox/modutils/ insmod.c)
insmod_main
|
–> bb_init_module
|
–> init_module
而 init_module 定义如下:(文件 busybox/modutils/modutils.c)
#define init_module(mod, len, opts) syscall(__NR_init_module, mod, len, opts)
因此,该系统调用对应内核层的 sys_init_module 函数。
回到Linux内核源代码(kernel/module.c),代码梳理:
SYSCALL_DEFINE3(init_module, …)
|
–>load_module
|
–> do_init_module(mod)
|
–> do_one_initcall(mod->init);
文件(include/linux/syscalls.h)中,有:
#define SYSCALL_DEFINE3(name, …) SYSCALL_DEFINEx(3, _##name, VA_ARGS)
从而形成 sys_init_module 函数。
最后的一部分还是没有分析清楚,先不分析了。
参考:https://blog.csdn.net/lu_embedded/article/details/51432616
部分素材来源于网络,如有侵权,请联系作者删除,谢谢!