Linux内核模块间通讯方法非常的多,最便捷的方法莫过于函数或变量符号导出,然后直接调用。默认情况下,模块与模块之间、模块与内核之间的全局变量是相互独立的,只有通过EXPORT_SYMBOL
将模块导出才能对其他模块或内核可见。
符号导出函数
EXPORT_SYMBOL()
:括号中定义的函数或变量对全部内核代码公开EXPORT_SYMBOL_GPL()
和EXPORT_SYMBOL
类似,但范围只适合GPL许可的模块进行调用
一、内核符号表
Linux kallsyms,即内核符号表,其中会列出所有的Linux内核中的导出符号,在用户态下可以通过/proc/kallsyms
访问,此时由于内核保护,看到的地址为0x0000000000000000
,在root模式下可以看到真实地址。启用kallsyms需要编译内核时设置CONFIG_KALLSYMS
为y。
/proc/kallsyms
会显示内核中所有的符号,但是这些符号不是都能被其他模块引用的(绝大多数都不能),能被引用的符号是被EXPORT_SYMBOL
或EXPORT_SYMBOL_GPL
导出的
内核模块在编译时符号的查找顺序:
- 在本模块中符号表中,寻找符号(函数或变量实现)
- 在内核全局符号表中寻找
- 在模块目录下的Module.symvers文件中寻找
内核符号表类型
内核符号表就是在内核内部函数或变量中可供外部引用的函数和变量的符号表(/proc/kallsyms),表格如下:
符号类型 | 名称 | 说明 |
---|---|---|
A | Absolute | 符号的值是绝对值,并且在进一步链接过程中不会被改变 |
B | BSS | 符号在未初始化数据区或区(section)中,即在BSS段中 |
C | Common | 符号是公共的。公共符号是未初始化的数据。在链接时,多个公共符号可能具有同一名称。如果该符号定义在其他地方,则公共符号被看作是未定义的引用 |
D | Data | 符号在已初始化数据区中 |
G | Global | 符号是在小对象已初始化数据区中的符号。某些目标文件的格式允许对小数据对象(例如一个全局整型变量)可进行更有效的访问 |
I | Inderect | 符号是对另一个符号的间接引用 |
N | Debugging | 符号是一个调试符号 |
R | Read only | 符号在一个只读数据区中 |
S | Small | 符号是小对象未初始化数据区中的符号 |
T | Text | 符号是代码区中的符号 |
U | Undefined | 符号是外部的,并且其值为0(未定义) |
V | Weaksymbol | 弱符号 |
W | Weaksymbol | 弱符号 |
- | Stabs | 符号是a.out目标文件中的一个stab符号,用于保存调试信息 |
? | Unknown | 符号的类型未知,或者与具体文件格式有关 |
注 :符号属性,小写表示局部符号,大写表示全局符号
二、Linux内核符号导出
1、EXPORT_SYMBOL导出符号
这里我们定义两个源文件myexportfunc.c
和myusefunc.c
,分别放置在不同目录;在myexportfunc.c
文件中导出publicFunc
函数和变量myOwnVar
以供myusefunc.c
文件中的函数调用。myusefunc.c
文件中要想成功调用publicFunc
函数和myOwnVar
变量,必须进行extern
声明,否则编译时会报错。源码如下:
myexportfunc.c
文件:
/* myexportfunc.c */
#include < linux/module.h >
#include < linux/kernel.h >
#include < linux/init.h >
char myOwnVar[30]="Linux kernel communication.";
static int __init myfunc_init(void)
{
printk("Hello,this is my own module!
");
return 0;
}
static void __exit myfunc_exit(void)
{
printk("Goodbye,this is my own clean module!
");
}
void publicFunc(void)
{
printk(KERN_INFO "This is public module and used for another modules.
");
}
module_init(myfunc_init);
module_exit(myfunc_exit);
EXPORT_SYMBOL(publicFunc);
EXPORT_SYMBOL(myOwnVar);
MODULE_DESCRIPTION("First Personel Module");
MODULE_AUTHOR("Lebron James");
MODULE_LICENSE("GPL");
myexportfunc.c
文件的Makefile
文件:
ifneq ($(KERNELRELEASE),)
$(info "2nd")
obj-m:=myexportfunc.o
else
KDIR :=/lib/modules/$(shell uname -r)/build
PWD :=$(shell pwd)
all:
$(info "1st")
make -C $(KDIR) M=$(PWD) modules
clean:
rm -f *.ko *.o *.mod.o *.symvers *.cmd *.mod.c *.order *.mod
endif
myusefunc.c
文件:
/* myusefunc.c */
#include < linux/init.h >
#include < linux/module.h >
MODULE_LICENSE("GPL");
extern void publicFunc(void);
extern char myOwnVar[30];
void showVar(void);
static int __init hello_init(void)
{
printk(KERN_INFO "Hello,this is myusefunc module.
");
publicFunc();
showVar();
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_INFO "Goodbye this is myusefunc module.
");
}
void showVar(void)
{
printk(KERN_INFO "%s
", myOwnVar);
}
module_init(hello_init);
module_exit(hello_exit);
myusefunc.c
文件的Makefile
文件:
KBUILD_EXTRA_SYMBOLS += /tmp/tmp/Module.symvers
ifneq ($(KERNELRELEASE),)
$(info "2nd")
obj-m:=myusefunc.o
else
KDIR :=/lib/modules/$(shell uname -r)/build
PWD :=$(shell pwd)
all:
$(info "1st")
make -C $(KDIR) M=$(PWD) modules
clean:
rm -f *.ko *.o *.mod.o *.symvers *.cmd *.mod.c *.order *.mod
endif
注:
KBUILD_EXTRA_SYMBOLS
用来告诉内核当前module
需要引用另外一个module
导出的符号。KBUILD_EXTRA_SYMBOLS
后需要写绝对路径,相对路径会出错,因为scripts/mod/modpost
执行时, 以其在内核目录的路径为起始点进行解析。
分别通过make
命令编译myexportfunc.c
和myusefunc.c
文件:
[root@localhost tmp]# make
"1st"
make -C /lib/modules/4.18.0-394.el8.x86_64/build M=/tmp/tmp modules
make[1]: Entering directory '/usr/src/kernels/4.18.0-394.el8.x86_64'
"2nd"
CC [M] /tmp/tmp/myexportfunc.o
Building modules, stage 2.
"2nd"
MODPOST 1 modules
CC /tmp/tmp/myexportfunc.mod.o
LD [M] /tmp/tmp/myexportfunc.ko
make[1]: Leaving directory '/usr/src/kernels/4.18.0-394.el8.x86_64'
[root@localhost tmp]# ls -l
total 488
drwxr-xr-x 3 root root 139 May 25 04:40 24
-rw-r--r-- 1 root root 260 May 24 13:34 Makefile
-rw-r--r-- 1 root root 32 May 25 04:40 modules.order
-rw-r--r-- 1 root root 114 May 25 04:40 Module.symvers
-rw-r--r-- 1 root root 655 May 25 04:40 myexportfunc.c
-rw-r--r-- 1 root root 237256 May 25 04:40 myexportfunc.ko
-rw-r--r-- 1 root root 826 May 25 04:40 myexportfunc.mod.c
-rw-r--r-- 1 root root 117856 May 25 04:40 myexportfunc.mod.o
-rw-r--r-- 1 root root 121336 May 25 04:40 myexportfunc.o
[root@localhost tmp]# cd 24/
[root@localhost 24]# ls
Makefile myusefunc.c
[root@localhost 24]# make
"1st"
make -C /lib/modules/4.18.0-394.el8.x86_64/build M=/tmp/tmp/24 modules
make[1]: Entering directory '/usr/src/kernels/4.18.0-394.el8.x86_64'
"2nd"
CC [M] /tmp/tmp/24/myusefunc.o
Building modules, stage 2.
"2nd"
MODPOST 1 modules
CC /tmp/tmp/24/myusefunc.mod.o
LD [M] /tmp/tmp/24/myusefunc.ko
make[1]: Leaving directory '/usr/src/kernels/4.18.0-394.el8.x86_64'
[root@localhost tmp]# cd ..
[root@localhost tmp]# insmod ./myexportfunc.ko
[root@localhost tmp]# cd 24/
[root@localhost 24]# ls -l
total 488
-rw-r--r-- 1 root root 305 May 24 13:34 Makefile
-rw-r--r-- 1 root root 32 May 25 04:42 modules.order
-rw-r--r-- 1 root root 114 May 25 04:42 Module.symvers
-rw-r--r-- 1 root root 557 May 24 13:33 myusefunc.c
-rw-r--r-- 1 root root 235832 May 25 04:42 myusefunc.ko
-rw-r--r-- 1 root root 898 May 25 04:42 myusefunc.mod.c
-rw-r--r-- 1 root root 117984 May 25 04:42 myusefunc.mod.o
-rw-r--r-- 1 root root