编写一个内核模块,在模块中引用另一个模块的导出函数。
编写提供plus导出函数的module_plus模块
(1)源程序
#include
#include
#include
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编写的具体格式以后分析~~~~~~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,
};
此外它还提供了模块的版本信息和依赖关系.(1)源程序
#include
#include
#include
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”函数未定义内核编程中关于查找导出符号的函数,具体的函数解释请看导出符号章节。
/*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没有任何问题,问题解决,看来该方法可行。(3)编写测试模块读另一个模块的状态
#include
#include
#include
#include
#include
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可以看到,虽然这时的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