Linux内核模块间通讯方法
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
#include
#include
char myOwnVar[30]="Linux kernel communication.";
static int __init myfunc_init(void)
{
printk("Hello,this is my own module!\n");
return 0;
}
static void __exit myfunc_exit(void)
{
printk("Goodbye,this is my own clean module!\n");
}
void publicFunc(void)
{
printk(KERN_INFO "This is public module and used for another modules.\n");
}
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
#include
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.\n");
publicFunc();
showVar();
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_INFO "Goodbye this is myusefunc module.\n");
}
void showVar(void)
{
printk(KERN_INFO "%s\n", 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 119392 May 25 04:42 myusefunc.o
[root@localhost 24]# insmod ./myusefunc.ko
[root@localhost 24]#
[root@localhost 24]# lsmod | grep -E "myexportfunc|myusefunc"
myusefunc 16384 0
myexportfunc 16384 1 myusefunc
注:这里必须先
insmod myexportfunc
模块,再insmod myusefunc
模块。假如先insmod myusefunc
模块,则会发生如下错误:
[root@localhost 24]# insmod ./myusefunc.ko insmod: ERROR: could not insert module ./myusefunc.ko: Unknown symbol in module [root@localhost 24]# lsmod | grep myusefunc [root@localhost 24]#
且通过
lsmod
查看可知,模块未能加载成功。
Linux内核知道的所有符号都列在/proc/kallsyms
中。让我们在这个文件中搜索我们的符号。
[root@localhost 24]# cat /proc/kallsyms | grep -E "publicFunc|myOwnVar"
ffffffffc0480030 r __ksymtab_myOwnVar [myexportfunc]
ffffffffc04800e4 r __kstrtab_myOwnVar [myexportfunc]
ffffffffc0480040 r __ksymtab_publicFunc [myexportfunc]
ffffffffc04800ed r __kstrtab_publicFunc [myexportfunc]
ffffffffc047f000 T publicFunc [myexportfunc]
ffffffffc0481000 D myOwnVar [myexportfunc]
我们可以看到,我们导出的符号列在内核识别的符号中。上述列表信息具体含义解释:
第一列,是该符号在内核地址空间中的地址
第二列,是符号属性,小写表示局部符号,大写表示全局符号(具体含义参考man nm)
第三列,表示符号字符串(即函数名或变量等)
第四列,表示加载的驱动名称
如果您查看编译模块所在目录中的Module.symvers
文件,将看到一行类似于以下内容
[root@localhost tmp]# cat Module.symvers
0x4db1caee publicFunc /tmp/tmp/myexportfunc EXPORT_SYMBOL
0x519e6cfa myOwnVar /tmp/tmp/myexportfunc EXPORT_SYMBOL
Module.symvers`包含所有导出的列表符号,`Module.symvers` file 的语法格式: `
`;当内核编译选项`CONFIG_MODVERSIONS`关闭时,所有的`CRC`值都为`0x00000000
上述编译并且加载模块完毕后, 通过dmesg
可以看到打印信息如下:
[root@localhost 24]# dmesg
[1028204.777932] Hello,this is my own module!
[1028215.008381] Hello,this is myusefunc module.
[1028215.008385] This is public module and used for another modules.
[1028215.008386] Linux kernel communication.
通过打印信息可知,publicFunc
函数以成功实现在其他内核模块内调用。接下来,我们卸载内核模块,再看下会发生什么?
[root@localhost tmp]# rmmod myexportfunc
rmmod: ERROR: Module myexportfunc is in use by: myusefunc
报错了,很诧异吧,为什么会无法卸载呢,我们从报错信息中就可以看出端倪, myexportfunc
模块正在被myusefunc
模块使用,所以无法卸载。通过modinfo
命令,我们也可以看出myusefunc
模块的依赖关系:
[root@localhost 24]# modinfo myusefunc.ko
filename: /tmp/tmp/24/myusefunc.ko
license: GPL
rhelversion: 8.7
srcversion: 092199D11396603B6377902
depends: myexportfunc
name: myusefunc
vermagic: 4.18.0-394.el8.x86_64 SMP mod_unload modversions
通过上述depends
行的结果可以看出,myusefunc
模块依赖myexportfunc
模块。
因此卸载也是需要按照顺序进行,先卸载调用模块,再卸载被调用模块,方可保证卸载成功。
[root@localhost tmp]# rmmod myusefunc
[root@localhost tmp]# rmmod myexportfunc
按照如上所说进行卸载,果然成功了,再通过dmesg
查看打印的信息是什么,如下:
[root@localhost ~]# dmesg
[ 635.296204] Hello,this is my export module!
[ 646.274636] Hello,this is myusefunc module.
[ 646.274655] This is public function and used for another modules.
[ 646.274657] Linux kernel communication.
[ 676.093397] Goodbye this is myusefunc module.
[ 683.385341] Goodbye,this is my export clean module!
[root@localhost ~]#
2、EXPORT_SYMBOL_GPL导出符号
同样定义两个源文件myexportfunc.c
和myusefunc.c
,分别放置在不同目录;在myexportfunc.c
文件中使用EXPORT_SYMBOL_GPL
导出publicFunc
函数以供myusefunc.c
文件中的函数调用。myusefunc.c
文件中要想成功调用publicFunc
函数,必须进行extern
声明,否则编译时会报错。源码如下:
myexportfunc.c
文件:
/* myexportfunc.c */
#include
#include
#include
char myOwnVar[30]="Linux kernel communication.";
static int __init myfunc_init(void)
{
printk("Hello,this is my own module!\n");
return 0;
}
static void __exit myfunc_exit(void)
{
printk("Goodbye,this is my own clean module!\n");
}
void publicFunc(void)
{
printk(KERN_INFO "This is public module and used for another modules.\n");
}
module_init(myfunc_init);
module_exit(myfunc_exit);
EXPORT_SYMBOL_GPL(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
#include
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.\n");
publicFunc();
showVar();
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_INFO "Goodbye this is myusefunc module.\n");
}
void showVar(void)
{
printk(KERN_INFO "%s\n", myOwnVar);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
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
分别通过make
命令编译myexportfunc.c
和myusefunc.c
文件:
[root@localhost tmp]# ls
24 Makefile myexportfunc.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]# cd 24
[root@localhost 24]# ls
Makefile myusefunc.c
[root@localhost 24]# vi 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 24]#
[root@localhost 24]# cd ..
[root@localhost tmp]# ls
24 Makefile modules.order Module.symvers myexportfunc.c myexportfunc.ko myexportfunc.mod.c myexportfunc.mod.o myexportfunc.o
[root@localhost tmp]# insmod ./myexportfunc.ko
[root@localhost tmp]#
[root@localhost tmp]# cd 24
[root@localhost 24]# ls
Makefile modules.order Module.symvers myusefunc.c myusefunc.ko myusefunc.mod.c myusefunc.mod.o myusefunc.o
[root@localhost 24]# insmod ./myusefunc.ko
[root@localhost 24]#
[root@localhost 24]# cat /proc/kallsyms | grep publicFunc
ffffffffc0480040 r __ksymtab_publicFunc [myexportfunc]
ffffffffc04800ed r __kstrtab_publicFunc [myexportfunc]
ffffffffc047f000 t publicFunc [myexportfunc]
[root@localhost 24]# cat /proc/kallsyms | grep myOwnVar
ffffffffc0480030 r __ksymtab_myOwnVar [myexportfunc]
ffffffffc04800e4 r __kstrtab_myOwnVar [myexportfunc]
ffffffffc0481000 D myOwnVar [myexportfunc]
[root@localhost 24]# dmesg
[1039412.015206] Hello,this is my own module!
[1039426.559478] Hello,this is myusefunc module.
[1039426.559482] This is public module and used for another modules.
[1039426.559482] Linux kernel communication.
注:
使用 EXPORT_SYMBOL_GPL
导出的符号的模块类型为小写字母t,表示局部引用使用 EXPORT_SYMBOL
导出的符号的模块类型为大写字母D,表示全局引用
上述内容显示了成功在内核模块调用其他内核模块的函数或变量的过程,接下来,让我们一起看一下,哪些情况下会导致无法调用其他内核模块函数或变量,并且会报Unknown symbol
错误。
三、Unknown symbol错误
Unknown symbol
说明 **有些函数不认识(未定义)**。
1、使用EXPORT_SYMBOL导出符号
使用EXPORT_SYMBOL
宏导出函数及变量的情形下,不插入模块myexportfunc
,直接插入模块myusefunc
,会出现Unknown symbol in module
,原因在于此时内核全局符号表中不存在publicFunc
和myOwnVar
符号:
[root@localhost tmp]# lsmod | grep myexportfunc
[root@localhost tmp]#
[root@localhost tmp]# cd 24
[root@localhost 24]# insmod myusefunc.ko
insmod: ERROR: could not insert module myusefunc.ko: Unknown symbol in module
#dmesg查看报错原因
[root@localhost 24]# dmesg
[1025403.614123] myusefunc: Unknown symbol publicFunc (err 0)
[1025403.614144] myusefunc: Unknown symbol myOwnVar (err 0)
先插入模块myexportfunc
,再插入模块myusefunc
,通过dmesg
查看,此时模块myexportfunc
中定义的publicFunc
和myOwnVar
符号成功被模块myusefunc
使用:
[root@localhost tmp]# insmod ./myexportfunc.ko
[root@localhost tmp]# cd 24
[root@localhost 24]# ls
Makefile modules.order Module.symvers myusefunc.c myusefunc.ko myusefunc.mod.c myusefunc.mod.o myusefunc.o
[root@localhost 24]# insmod ./myusefunc.ko
[root@localhost 24]# cat /proc/kallsyms | grep myOwnVar
ffffffffc0480030 r __ksymtab_myOwnVar [myexportfunc]
ffffffffc04800e4 r __kstrtab_myOwnVar [myexportfunc]
ffffffffc0481000 D myOwnVar [myexportfunc]
[root@localhost 24]#
[root@localhost 24]# cat /proc/kallsyms | grep publicFunc
ffffffffc0480040 r __ksymtab_publicFunc [myexportfunc]
ffffffffc04800ed r __kstrtab_publicFunc [myexportfunc]
ffffffffc047f000 T publicFunc [myexportfunc]
[root@localhost 24]# dmesg
[1025729.139236] Hello,this is my own module!
[1025739.579713] Hello,this is myusefunc module.
[1025739.579717] This is public module and used for another modules.
[1025739.579718] Linux kernel communication.
2、未使用EXPORT_SYMBOL导出符号
将myexportfunc.c
中的EXPORT_SYMBOL
注释掉,myexportfunc
源码文件修改如下:
/* myexportfunc.c */
#include
#include
#include
char myOwnVar[30]="Linux kernel communication.";
static int __init myfunc_init(void)
{
printk("Hello,this is my own module!\n");
return 0;
}
static void __exit myfunc_exit(void)
{
printk("Goodbye,this is my own clean module!\n");
}
void publicFunc(void)
{
printk(KERN_INFO "This is public module and used for another modules.\n");
}
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");
重新通过make
命令编译myexportfunc
和myusefunc
模块,myusefunc
源文件、Makefile
文件及编译方法同上,首先插入模块myexportfunc
,此时全局符号表中的符号类型为d和t,再插入模块myusefunc
报错,无法访问模块myexportfunc
中的符号。
[root@localhost tmp]# insmod ./myexportfunc.ko
[root@localhost tmp]# cat /proc/kallsyms | grep myOwnVar
ffffffffc0481000 d myOwnVar [myexportfunc]
[root@localhost tmp]# cat /proc/kallsyms | grep publicFunc
ffffffffc047f00c t publicFunc [myexportfunc]
[root@localhost tmp]# cd 24
[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
WARNING: "publicFunc" [/tmp/tmp/24/myusefunc.ko] undefined!
WARNING: "myOwnVar" [/tmp/tmp/24/myusefunc.ko] undefined!
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 24]# insmod ./myusefunc.ko
insmod: ERROR: could not insert module ./myusefunc.ko: Unknown symbol in module
[root@localhost 24]# dmesg
[1027498.802299] Goodbye this is myusefunc module.
[1027502.210478] Goodbye,this is my own clean module!
[1027507.431371] Hello,this is my own module!
[1027519.232574] myusefunc: Unknown symbol publicFunc (err 0)
[1027519.232594] myusefunc: Unknown symbol myOwnVar (err 0)
原因在于,模块类型为小写字母d和t表示局部引用,定义在Data
和Text
,只能在模块内访问。模块类型为大写字母D和T表示全局引用,可以在模块外访问,其他类型类似。
3、使用EXPORT_SYMBOL_GPL导出符号
使用EXPORT_SYMBOL_GPL
导出myexportfunc.c
文件中的publicFunc
函数,修改myexportfunc.c
源文件如下:
/* myexportfunc.c */
#include
#include
#include
char myOwnVar[30]="Linux kernel communication.";
static int __init myfunc_init(void)
{
printk("Hello,this is my own module!\n");
return 0;
}
static void __exit myfunc_exit(void)
{
printk("Goodbye,this is my own clean module!\n");
}
void publicFunc(void)
{
printk(KERN_INFO "This is public module and used for another modules.\n");
}
module_init(myfunc_init);
module_exit(myfunc_exit);
EXPORT_SYMBOL_GPL(publicFunc);
EXPORT_SYMBOL(myOwnVar);
MODULE_DESCRIPTION("First Personel Module");
MODULE_AUTHOR("Lebron James");
MODULE_LICENSE("GPL");
将myusefunc.c
中的MODULE_LICENSE("GPL")
注释掉,myusefunc.c
源码文件修改如下:
/* myusefunc.c */
#include
#include
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.\n");
publicFunc();
showVar();
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_INFO "Goodbye this is myusefunc module.\n");
}
void showVar(void)
{
printk(KERN_INFO "%s\n", myOwnVar);
}
module_init(hello_init);
module_exit(hello_exit);
//MODULE_LICENSE("GPL");
两个模块的Makefile
文件均同上,分别通过make命令编译myexportfunc.c
和myusefunc.c
文件,之后使用insmod
命令先后插入两个模块,如下:
[root@localhost tmp]# insmod ./myexportfunc.ko
[root@localhost tmp]# cd 24
[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
WARNING: modpost: missing MODULE_LICENSE() in /tmp/tmp/24/myusefunc.o
see include/linux/module.h for more information
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 24]#
[root@localhost 24]# insmod ./myusefunc.ko
insmod: ERROR: could not insert module ./myusefunc.ko: Unknown symbol in module
[root@localhost 24]# dmesg
[1030402.772230] Hello,this is my own module!
[1030428.231104] myusefunc: Unknown symbol publicFunc (err 0)
通过上面结果可知,在被调用模块使用EXPORT_SYMBOL_GPL
宏导出函数或变量后,调用该函数或变量的调用模块必须包含MODULE_LICENSE("GPL")
宏,否则,在加载该模块时,将会报Unknown symbol
错误。即只有包含GPL许可权的模块才能调用EXPORT_SYMBOL_GPL
宏导出的符号。
**License(许可证)**:
Linux是一款免费的操作系统,采用了GPL协议,允许用户可以任意修改其源代码。 GPL协议的主要内容是软件产品中即使使用了某个GPL协议产品提供的库, 衍生出一个新产品,该软件产品都必须采用GPL协议,即必须是开源和免费使用的, 可见GPL协议具有传染性。因此,我们可以在Linux使用各种各样的免费软件。 在以后学习Linux的过程中,可能会发现我们安装任何一款软件,从来没有30天试用期或者是要求输入激活码的。
内核模块许可证有
“GPL”,“GPL v2”,“GPL and additional rights”,“Dual SD/GPL”,“Dual MPL/GPL”,“Proprietary”
。
四、总结
如果你的模块需要输出符号给其他模块使用,符号必须在模块文件的全局部分输出, 在任何函数之外, 因为宏定义扩展成一个特殊用途的并被期望是全局存取的变量的声明。 这个变量存储于模块的一个特殊的可执行部分( 一个 “ELF 段” ), 内核通过这个部分在加载时找到模块输出的变量。编译生成ko模块之后,用insmod
命令加载此模块到内核。这个程序加载模块的代码段和数据段到内核。
使用你的模块输出符号的其他模块同样通过insmod加载到内核,insmod
在加载的过程中使用公共内核符号表来解析模块中未定义的符号(即通过extern声明的符号),公共内核符号表中包含了所有的全局内核项(即函数和变量)的地址,这是实现模块化驱动程序所必需的。
同时也可以导出自身模块中的任何内核符号到公共内核符号表,如图:
通常情况下,模块只需实现自己的功能,而无需导出任何符号。但是,如果其他模块需要从某个模块中获得好处时,我们也可以导出符号。
驱动也是存在于内核空间的,它的每一个函数每一个变量都会有对应的符号,这部分符号也可以称作内核符号,它们不导出(EXPORT_SYMBOL
)就只能为自身所用,导出后就可以成为公用,对于导出的那部分内核符号就是我们常说的内核符号表。
EXPORT_SYMBOL使用方法:
在模块函数定义之后使用 EXPORT_SYMBOL
(函数名);在调用该函数的模块中使用 extern
对之声明;首先加载定义该函数的模块,再加载调用该函数的模块。【模块加载顺序的前后要求,一般就是依赖于符号调用】
insmod
的时候并不是所有的函数都得到内核符号表去寻找对应的符号,每一个驱动在自己分配的空间里也会存储一份符号表,里面有关于这个驱动里使用到的变量以及函数的一些符号,首先驱动会在这里面找,如果发现找不到就会去公共内核符号表中搜索,搜索到了则该模块加载成功,搜索不到则该模块加载失败。
内核默认情况下,是不会在模块加载后把模块中的非静态全局变量以及非静态函数自动导出到内核符号表中的,需要显式调用宏EXPORT_SYMBOL
才能导出。对于一个模块来讲,如果仅依靠自身就可以实现自已的功能,那么可以不需要要导出任何符号,只有其他模块中需要使用到该模块提供的函数时,就必须要进行导出操作。
以上就是良许教程网为各位朋友分享的Linu系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你 !