您的位置:首页 > 运维架构 > Linux

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

2014-02-27 20:21 246 查看

/*本篇由真胖子同志打造,原创辛苦,转载请标明出处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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐