Linux内核模块间通讯方法

嵌入式技术

1371人已加入

描述

Linux内核模块间通讯方法非常的多,最便捷的方法莫过于函数或变量符号导出,然后直接调用。默认情况下,模块与模块之间、模块与内核之间的全局变量是相互独立的,只有通过EXPORT_SYMBOL将模块导出才能对其他模块或内核可见。

符号导出函数

  1. EXPORT_SYMBOL():括号中定义的函数或变量对全部内核代码公开
  2. EXPORT_SYMBOL_GPL()EXPORT_SYMBOL类似,但范围只适合GPL许可的模块进行调用

一、内核符号表

Linux kallsyms,即内核符号表,其中会列出所有的Linux内核中的导出符号,在用户态下可以通过/proc/kallsyms访问,此时由于内核保护,看到的地址为0x0000000000000000,在root模式下可以看到真实地址。启用kallsyms需要编译内核时设置CONFIG_KALLSYMS为y。

/proc/kallsyms会显示内核中所有的符号,但是这些符号不是都能被其他模块引用的(绝大多数都不能),能被引用的符号是被EXPORT_SYMBOLEXPORT_SYMBOL_GPL导出的

内核模块在编译时符号的查找顺序:

  1. 在本模块中符号表中,寻找符号(函数或变量实现)
  2. 在内核全局符号表中寻找
  3. 在模块目录下的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.cmyusefunc.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.cmyusefunc.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.cmyusefunc.c,分别放置在不同目录;在myexportfunc.c文件中使用EXPORT_SYMBOL_GPL导出publicFunc函数以供myusefunc.c文件中的函数调用。myusefunc.c文件中要想成功调用publicFunc函数,必须进行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_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 < linux/init.h >
#include < linux/module.h >

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);
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.cmyusefunc.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,原因在于此时内核全局符号表中不存在publicFuncmyOwnVar符号:

[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中定义的publicFuncmyOwnVar符号成功被模块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 < 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");

重新通过make命令编译myexportfuncmyusefunc模块,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表示局部引用,定义在DataText,只能在模块内访问。模块类型为大写字母D和T表示全局引用,可以在模块外访问,其他类型类似。

3、使用EXPORT_SYMBOL_GPL导出符号

使用EXPORT_SYMBOL_GPL导出myexportfunc.c文件中的publicFunc函数,修改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_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 < linux/init.h >
#include < linux/module.h >

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);
//MODULE_LICENSE("GPL");

两个模块的Makefile文件均同上,分别通过make命令编译myexportfunc.cmyusefunc.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使用方法

  1. 在模块函数定义之后使用EXPORT_SYMBOL(函数名);
  2. 在调用该函数的模块中使用extern对之声明;
  3. 首先加载定义该函数的模块,再加载调用该函数的模块。【模块加载顺序的前后要求,一般就是依赖于符号调用】

insmod的时候并不是所有的函数都得到内核符号表去寻找对应的符号,每一个驱动在自己分配的空间里也会存储一份符号表,里面有关于这个驱动里使用到的变量以及函数的一些符号,首先驱动会在这里面找,如果发现找不到就会去公共内核符号表中搜索,搜索到了则该模块加载成功,搜索不到则该模块加载失败。

内核默认情况下,是不会在模块加载后把模块中的非静态全局变量以及非静态函数自动导出到内核符号表中的,需要显式调用宏EXPORT_SYMBOL才能导出。对于一个模块来讲,如果仅依靠自身就可以实现自已的功能,那么可以不需要要导出任何符号,只有其他模块中需要使用到该模块提供的函数时,就必须要进行导出操作。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分