一个模块引用另一个模块导出的函数

/*本篇由真胖子同志打造,原创辛苦,转载请标明出处http://blog.csdn.net/figtingforlove/article/details/20067463*/

编写一个内核模块,在模块中引用另一个模块的导出函数。
编写提供plus导出函数的module_plus模块

(1)源程序

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

MODULE_LICENSE("GPL");

static int init(void)
{
	printk("<0>""\nplus_init success!\n");
	return 0;
}

static int exit(void)
{
	printk("<0>""\nplus_exit success!\n");
	return0;
}

int plus(int a, int b)
{
    return(a+b);
}

EXPORT_SYMBOL(plus);
module_init(init);
module_exit(exit);

(2)Makefile文件

ifneq ($(KERNELRELEASE),)
    obj-m :=module_plus.o
else
    KERNELDIR := /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)
default:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

endif

Makefile编写的具体格式以后分析~~~~~~
(3)编译,加载,查看输出
make
sudoinsmod module_plus.ko
dmesg后可以看到终端显示“plus_initsuccess!”
lsmod可以看到如下显示,表明模块加载成功了
Module Size Used by
module_plus 12630 0
cat /proc/kallsyms查看内核符号表可以看到模块导出的函数
0000000000000000T plus [module_plus]
现在已经有了一个模块module_plus提供了一个导出函数,在编写另一个模块之前我们先看一下该模块的构成。
上述make编译在模块的目录下生成了几个重要的文件,看看他们都表示什么

module_plus.mod.c文件,也就是前面所说的由模块编译工具链生成的,在来看看那个有趣的声明

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,
};

此外它还提供了模块的版本信息和依赖关系.
Module.symvers文件提供了模块导出的函数信息,包括函数地址,函数名,所在模块目录
0x668f06c7 plus /home/l/ll/otn项目/项目测试代码/内核模块实例/模块符号导出实例/symbol/module_plus EXPORT_SYMBOL
编写module_test模块,引用plus函数,按照一般程序设计代码如下

(1)源程序

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

MODULE_LICENSE("GPL");

extern int plus(int,int);
static int hello_init()
{
	printk(KERN_ALERT"Hello world\n");
	inti ;
	int c = 0;

	c = plus(1,2);
	printk(KERN_ALERT"c = %d\n",c);
	return0;
}

static int hello_exit()
{
	printk(KERN_ALERT"Goodbye world\n");
	return0;
}
module_init(hello_init);
module_exit(hello_exit);

(2)编译make,可以看到一个警告说“plus”函数未定义
WARNING:"plus"[/home/l/ll/otn项目/项目测试代码/内核模块实例/模块符号导出实例/hello/hello.ko]undefined!
(3)加载sudoinsmod hello.ko
insmod:error inserting 'hello.ko': -1 Invalid parameters
说是有不可用的参数,why?看看输出信息dmesg
[2052.726996] hello: no symbol version for plus
[2052.727001] hello: Unknown symbol plus (err -22)
原来是找不到“plus”符号,奇怪ing,明明已经导出了啊,why?
。。。。。这时候想到刚才module_plus模块加载后查看内核符号表似的输出
cat /proc/kallsyms查看内核符号表可以看到模块导出的函数
0000000000000000T plus [module_plus]
奇怪的00000000000,也就是说内核符号表中该函数的地址竟然是0,也就是未知,难怪加载模块的时候找不到对应的函数代码。So?How?
Answer1 :想到module_plus模块不是生成了Mododule.symvers符号视图么,那么用这个文件替换当前hello模块目录下生成的Mododule.symvers文件
编译,success
加载,success
dmesg,[ 2278.175896] c = 3
好吧,他成功了,butwhy?
发挥伟大的大脑~~~~~~回顾一下前面的模块加载部分,在发现“为解决的引用”符号时,的处理函数
Answer2:既然内核符号表导出函数地址是0,而函数确实导出了,那我们就找到这个有加载的Module_plus模块中plus函数的地址

内核编程中关于查找导出符号的函数,具体的函数解释请看导出符号章节。

/*inclede/module.h*/

/*Find a symbol and return it, along with, (optional) crc and(optional) module which owns it */

const struct kernel_symbol *find_symbol(const char *name,struct module **owner,const unsigned long **crc,bool gplok,bool warn)
{
	struct find_symbol_arg fsa;
	fsa.name= name;
	fsa.gplok= gplok;
	fsa.warn= warn;

	if(each_symbol(find_symbol_in_section, &fsa)) {
		if(owner)
			*owner= fsa.owner;
		if(crc)
			*crc= fsa.crc;
		return fsa.sym;
	}

	DEBUGP("Failedto find symbol %s\n", name);
	return NULL;
}
EXPORT_SYMBOL(find_symbol);

//得到符号表的函数符号地址
void*__symbol_get(const char *symbol)
{
	struct module *owner;
	const struct kernel_symbol *sym;
	preempt_disable();
	sym= find_symbol(symbol, &owner, NULL, true, true);
	if(sym && strong_try_module_get(owner))
		sym= NULL;
	preempt_enable();
	return sym ? (void *)sym->value : NULL;
}

EXPORT_SYMBOL(__symbol_get);

修改上述程序代码如下,我们用__symbol_get获取函数指针:

typedef int (*plus)(int a, int b);
//extern int plus(int,int);
static int hello_init()
{
	printk(KERN_ALERT"Hello world\n");
	plus p = NULL;
	inti ;
	
	p= (plus)__symbol_get("plus") ;
	int c = 0;
	//c= plus(1,2);
	c = p(1,2);
	printk(KERN_ALERT"c = %d\n",c);
	return 0;
}

make,insmod,dmesg没有任何问题,问题解决,看来该方法可行。
[14534.773267]plus_init success!
[14681.389583]Hello world
[14681.389592]c = 3
总结一下,看来模块加载的时候会在处理引用的符号时根据依赖关系搜索该符号,得到所引用函数的地址。
在不做处理时,hello模块加载无法找到plus函数,不能建立模块间的依赖关系。hello.mod.c文件中

static const char __module_depends[]__used
__attribute__((section(".modinfo")))=
"depends=";
answer1:模块在加载的时候,根据Module.symvers文件,知道会用到那个导出的引用,并且得到模块module_plus
static const char __module_depends[]
__used__attribute__((section(".modinfo")))=
"depends=mosule_plus";
answer2 :在模块加载的时候,根据内核导出模块处理“为解决引用”符号的原则,找到到处符号表,重定位,找到符号地址~~find_symbol
大概就是这样,也许有误差,但不影响理解。But,如果此时卸载模块module_plusrmmodmodule_plus会提示ERROR:Module module_plus is inuse,我们已经卸载了引用它的模块了,为什么还卸载不了?那我们就再编写一个模块去读module_plus模块的状态,看看为什么。

(3)编写测试模块读另一个模块的状态

#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/list.h>
#include<linux/cpumask.h>
static int __init mymod_init(void)
{
	struct module *mod;
	struct module_use *use;
	int cpu;
	//打印本模块的模块名和模块状态
	printk(KERN_ALERT"[insmod mymod] name:%s, state:%d\n",THIS_MODULE->name,THIS_MODULE->state);
	//遍历模块列表,查找target模块
	list_for_each_entry(mod,THIS_MODULE->list.prev,list)
	{
		if(strcmp(mod->name,"module_plus")==0){
			//1.打印模块的模块名、模块状态、引用计数
			printk(KERN_ALERT"name:%s,state:%d, refcnt:%lu ",mod->name,mod->state,module_refcount(mod));
			//1.打印出所有依赖module_plus的模块名(遍历mod->source_list)
			if(!list_empty(&mod->source_list)){
				list_for_each_entry(use,&mod->source_list,source_list)
				printk(KERN_ALERT"%s\t",use->source->name);
			}else
				printk(KERN_ALERT"used by NULL\n");
			/*
			//2.将模块的引用计数变为0
			for_each_possible_cpu(cpu){
				per_cpu_ptr(mod->refptr, cpu)->decs = 0;
				per_cpu_ptr(mod->refptr,cpu)->incs = 0;
			}
			//2.再看看module_plus的名称、状态、引用计数
			printk(KERN_ALERT"name:%s,state:%d, refcnt:%lu\n",mod->name,mod->state,module_refcount(mod));
			*/
		}
	}
	return 0;
	}

static int __exit mymod_exit(void)
{
	printk(KERN_ALERT"[rmmodmymod] name:%s state:%d\n",THIS_MODULE->name,THIS_MODULE->state);
	return0;
}

module_init(mymod_init);
module_exit(mymod_exit);
MODULE_AUTHOR("lilei");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Whymodule can not be removed");

先来分下下代码

/*linux/module.h*/
/*模块状态*/
208 enum module_state {
209 	MODULE_STATE_LIVE, // 0
210 	MODULE_STATE_COMING, // 1
211 	MODULE_STATE_GOING, // 2
212 	MODULE_STATE_UNFORMED, // 3
213};

/*模块引用计数*/

224 struct module_ref {
225 	unsigned long incs;
226 	unsigned long decs;
227} __attribute((aligned(2 * sizeof(unsigned long))));
228

/*kernel/module.c*/
/*获取模块的引用计数函数*/

774 unsigned long module_refcount(struct module *mod)
775{
776 	unsigned long incs = 0, decs = 0;
777 	int cpu;
778
779 	for_each_possible_cpu(cpu)
780 		decs += per_cpu_ptr(mod->refptr, cpu)->decs;
794 	smp_rmb();
795 	for_each_possible_cpu(cpu)
796 		incs += per_cpu_ptr(mod->refptr, cpu)->incs;
797 	return incs - decs;
798}

现在对代码1编译,加载模块,dmesg
[8276.079311] [insmod mymod] name:a, state:1
[8276.079317] name:module_plus, state:0,refcnt:1
[8276.079318] used by NULL
这里stat:1表示mod->state= MODULE_STATE_COMING,说明这个模块正在执行mod->init函数,还没有执行到mod->state= MODULE_STATE_LIVING。可以看到module_plusde的引用计数是1。简单的想,是不是把这个模块的这个值变为0,就可以了?试试吧,去掉注释把代码2加进来,老过程。。。
[8276.079311] [insmod mymod] name:a, state:1
[8276.079317] name:module_plus, state:0, refcnt:1
[8276.079318] used by NULL
[8276.079321] name:module_plus, state:0,refcnt:0
[8307.155957]
[8307.155957] plus_exit success!
[8358.543440] [rmmod mymod] name:a state:2
模块是成功卸载了,但是为什么?

可以看到,虽然这时的module_plus并没有其它模块依赖它了但refcnt=1。why?很容易想到是我们在hello模块中添加的代码有bug,应该是在某个地方将模块计数加1了。我们在编写module_plus的时候,通过调用__symbol_get查找符号

1886 void *__symbol_get(const char *symbol)
1887{
1888 	struct module *owner;
1889 	const struct kernel_symbol *sym;
1890
1891 	preempt_disable();
1892 	sym = find_symbol(symbol, &owner, NULL, true, true);
1893 	if (sym && strong_try_module_get(owner))
1894 		sym = NULL;
1895 	preempt_enable();
1896
1897 	return sym ? (void *)sym->value : NULL;
1898}

/*查看源代码find_symbol没有问题,再看看strong_try_module_get*/

190 static inline int strong_try_module_get(struct module *mod)
191{
192 	BUG_ON(mod && mod->state ==MODULE_STATE_UNFORMED);
193  	if (mod && mod->state == MODULE_STATE_COMING)
194  		return -EBUSY;
195 	if (try_module_get(mod))
196 		return 0;
197 	else
198 	return -ENOENT;
199}

/*再看try_module_get*/

951 bool try_module_get(struct module *module)
952{
953 	bool ret = true;
954
955 	if (module) {
956 		preempt_disable();
957
958 		if (likely(module_is_live(module))) {
959 			__this_cpu_inc(module->refptr->incs);
960 			trace_module_get(module, _RET_IP_);
961 		} else
962 			ret = false;
963
964 		preempt_enable();
965 	}
966 	return ret;
967}

惊喜的发现果然__this_cpu_inc(module->refptr->incs);对模块的引用计数加了1,不出所料(尼玛的不出,找了好久,才想到~。。~)这就是为什么refcnt=1了,怪不得不能卸载模块,所以说内核代码也不能随便引用,特别是在不了解他的细节的时候。现在了解了原因,那我们换个函数find_symbol试试。因为直接更改模块的这个变量值总觉得不是很规范。

static int hello_init()
{
	printk(KERN_ALERT"Hello world\n");
	plus p = NULL;
	const struct kernel_symbol *sym;;
	int c = 0;

	sym = find_symbol("plus",THIS_MODULE, NULL, true, true);
	if(sym) {
		p= (plus*)sym->value;
		c= p(1,2);
	}else
	sym= NULL;
	p= NULL;
	printk(KERN_ALERT"c = %d\n",c);
	return 0;
}

编译,加载,dmesg
[14534.773267]plus_init success!
[14681.389583]Hello world
[14681.389592]c = 3
ok!noproblem ~~~正常卸载了,至于符合规范这回事,我也不知道,可这也算是一种方法吧。
2.6内核的解决~~请参考
http://blog.csdn.net/zhangskd/article/details/7945140


  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值