浅谈内核的module模块
以前一直不知道 linux 驱动的 .ko 到底是个什么,有什么用,所以简单的分析一下。
ko文件如何形成,它是什么格式?
首先随便写个 helloworld 驱动,make V=1 打开详细编译log查看(我的这里是 kobj.ko)
取出关键的部分:
gcc -DKBUILD_MODNAME='"kobj"' -DMODULE -c -o /home/mxg/workSpace/Intermals_of_Linux_device_driver/chap09/kobj/kobj.mod.o /home/mxg/workSpace/Intermals_of_Linux_device_driver/chap09/kobj/kobj.mod.c
ld -r -m elf_x86_64 -z max-page-size=0x200000 -T ./scripts/module-common.lds --build-id -o /home/mxg/workSpace/Intermals_of_Linux_device_driver/chap09/kobj/kobj.ko /home/mxg/workSpace/Intermals_of_Linux_device_driver/chap09/kobj/kobj.o /home/mxg/workSpace/Intermals_of_Linux_device_driver/chap09/kobj/kobj.mod.o
我们可以看到,编译的时候,加了 -DMODULE 选项,即定义了宏:MODULE
这使得 module_init 这个驱动入口函数发生了变化,发生什么变化了呢?
/* Each module must use one module_init(). module.h */
#define module_init(initfn) \
static inline initcall_t __maybe_unused __inittest(void) \
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn)));
即,当有MODULE宏定义的时候,这时的 module_init 就是
int init_module(void) attribute((alias(#initfn)))
即,定义了一个新的函数 init_module,它是initfn的另外一个名字。
接下来我们再看自动生成的 kobj.mod.c文件内容
__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,
};
#ifdef CONFIG_RETPOLINE
MODULE_INFO(retpoline, "Y");
#endif
static const char __module_depends[]
__used
__attribute__((section(".modinfo"))) =
"depends=";
生成了一个结构体,__this_module,这个结构体就是 填写字符驱动的 file options结构体的 owner变量
这里的 .init 的 init_module 就是刚才说的 initfn
static const struct file_operations fb_fops = {
.owner = THIS_MODULE,
#ifdef MODULE
extern struct module __this_module;
#define THIS_MODULE (&__this_module)
接下来继续看编译log,里面的ld部分,简单就是
ld -T xx.lds -o xx.ko kobj.ko kobj.o kobj.mod.c.o
这又是在干嘛? 其实就是将两个 gcc -c 编译出来的 2个.o文件通过ld命令合成一个.o文件,注意 xx.lds必须是要被引用的,并且是空的,要不然会自动引用默认的linux脚本(脚本内容是合成一个可执行文件)
所以最终 kobj.ko就是个 .o文件…
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 4696 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 19
Section header string table index: 16
.o文件是需要重新定位的,所以接下来谈一下
ko加载
ko通过 insmod命令加入系统里面,而 insmod 最终调用系统函数 load_moudle 进行最ko(就是.o)文件进行解析,
他如何做呢?
①取出ELF中符号表的所有函数,将未定义的函数重定位。那如何重定位不认识的函数呢?
首先作为内核,比如将函数A导出,则会用到EXPORT_SYMBOL(A);,而EXPORT_SYMBOL就是将函数A放在放在段 ___ksymtab中,
所以如果被加载的ko中,用到A函数了,当 通过系统调用进入load_moudle函数的时候,就会在___ksymtab找到A函数的地址值。
(内核在装在到 内存运行的时候,所有的地址都是明确的(编译成2进制文件后就决定了)
所以load_moudle里面的代码里面涉及到地址的地方也都被提前定死了(只不过通过系统调用可以调用这段代码,所以看起来像是动态的感觉),比如___ksymtab这个段的地址编译后定死为yy。系统调用时,调用到load_moudle函数,执行此处代码,自然就知道yy这个地址。
)
②接下来就是要找到结构体
__visible struct module __this_module __attribute__((section(".gnu.linkonce.this_module")))
刚才已经讲过 kobj.mod.c中的此结构体,所在的段是.gnu.linkonce.this_module,所以先找到它,怎么找?
根据ELF的标准来找,通过读取此段在ELF中的偏移位置
readelf -S kobj.ko
[11] .gnu.linkonce.thi PROGBITS 0000000000000000 00000580
0000000000000340 0000000000000000 WA 0 0 64
找到此结构体后,调用 init就是调用 initfn函数了(驱动入口处)
接下来简单说一下这个.owner 是做什么用的
static const struct file_operations fb_proc_fops = {
.owner = THIS_MODULE,
};
上面已经分析过了,THIS_MODULE就是__this_module,而__this_module就是 kobj.mod.c定义的结构体,充当此模块的管理地位,代表着此模块。因为 module_init(initfn)中的驱动入口initfn 是 此__this_module结构体的一部分,即 .init成员。。
他主要是管理 此ko,即此module模块的生命周期,以及安全方面的考虑。
比如打开一个 字符设备,open函数 chrdev_open
static inline int module_is_live(struct module *mod)
{
return mod->state != MODULE_STATE_GOING;
}
bool try_module_get(struct module *module)
{
bool ret = true;
if (module) {
preempt_disable();
/* Note: here, we can fail to get a reference */
if (likely(module_is_live(module) &&
atomic_inc_not_zero(&module->refcnt) != 0))
trace_module_get(module, _RET_IP_);
else
ret = false;
preempt_enable();
}
return ret;
}
#define fops_get(fops) \
(((fops) && try_module_get((fops)->owner) ? (fops) : NULL))
#define fops_put(fops) \
do { if (fops) module_put((fops)->owner); } while(0)
#define replace_fops(f, fops) \
do { \
struct file *__file = (f); \
fops_put(__file->f_op); \
BUG_ON(!(__file->f_op = (fops))); \
} while(0)
static int chrdev_open(struct inode *inode, struct file *filp)
{
struct cdev *p;
struct cdev *new = NULL;
p = inode->i_cdev;
kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
new = container_of(kobj, struct cdev, kobj);
inode->i_cdev = p = new;
fops = fops_get(p->ops);
if (!fops)
goto out_cdev_put;
replace_fops(filp, fops);
if (filp->f_op->open) {
ret = filp->f_op->open(inode, filp);
if (ret)
goto out_cdev_put;
}
}
可以看到,open设备时,调用fops_get来判断此模块是不是被卸载了,如果被卸载,则退出goto out_cdev_put,顺便提一下卸载的时候是:
static int try_stop_module(struct module *mod, int flags, int *forced)
{
/* Mark it as dying. */
mod->state = MODULE_STATE_GOING;
return 0;
}
SYSCALL_DEFI
NE2(delete_module, const char __user *, name_user,
unsigned int, flags)
{
ret = try_stop_module(mod, flags, &forced);
if (ret != 0)
goto out;
}
如果没有被卸载,则调用 replace_fops 函数,即,struct file * file 中的 fops 赋值为 ( inode->i_cdev->ops)
即 file->fops = inode->i_cdev->ops
另外提一嘴,replace_fops中的 fops_put用调用 module_put,用意是将 此模块的使用的计数 -1;前面fops_get中 try_module_get的 atomic_inc_not_zero 是+1的操作。
关于什么作用可以看:
https://blog.csdn.net/Dummkopfer/article/details/80380264