浅谈内核的module模块

浅谈内核的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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值