Linux内核模块通信|符号导出解析

原创 Linux二进制 2023-05-25 20:44

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),表格如下:

符号类型名称说明
AAbsolute符号的值是绝对值,并且在进一步链接过程中不会被改变
BBSS符号在未初始化数据区或区(section)中,即在BSS段中
CCommon符号是公共的。公共符号是未初始化的数据。在链接时,多个公共符号可能具有同一名称。如果该符号定义在其他地方,则公共符号被看作是未定义的引用
DData符号在已初始化数据区中
GGlobal符号是在小对象已初始化数据区中的符号。某些目标文件的格式允许对小数据对象(例如一个全局整型变量)可进行更有效的访问
IInderect符号是对另一个符号的间接引用
NDebugging符号是一个调试符号
RRead only符号在一个只读数据区中
SSmall符号是小对象未初始化数据区中的符号
TText符号是代码区中的符号
UUndefined符号是外部的,并且其值为0(未定义)
VWeaksymbol弱符号
WWeaksymbol弱符号
-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 
#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.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 
#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.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 
#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命令编译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 
#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.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才能导出。对于一个模块来讲,如果仅依靠自身就可以实现自已的功能,那么可以不需要要导出任何符号,只有其他模块中需要使用到该模块提供的函数时,就必须要进行导出操作。


Linux二进制 Linux编程、内核模块、网络原创文章分享,欢迎关注"Linux二进制"微信公众号
评论 (0)
  •   无人机蜂群电磁作战仿真系统软件,是专门用于模拟、验证无人机蜂群在电磁作战环境中协同、干扰、通信以及对抗等能力的工具。下面从功能需求、技术架构、典型功能模块、发展趋势及应用场景等方面展开介绍:   应用案例   目前,已有多个无人机蜂群电磁作战仿真系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润无人机蜂群电磁作战仿真系统。这些成功案例为无人机蜂群电磁作战仿真系统的推广和应用提供了有力支持。   功能需求   电磁环境建模:模拟构建复杂多样的电磁环境,涵盖各类电磁干扰源与
    华盛恒辉l58ll334744 2025-04-17 16:49 148浏览
  • 近日,全球6G技术与产业生态大会(简称“全球6G技术大会”)在南京召开。紫光展锐应邀出席“空天地一体化与数字低空”平行论坛,并从6G通信、感知、定位等多方面分享了紫光展锐在6G前沿科技领域的创新理念及在空天地一体化技术方面的研发探索情况。全球6G技术大会是6G领域覆盖广泛、内容全面的国际会议。今年大会以“共筑创新 同享未来”为主题,聚焦6G愿景与关键技术、安全可信、绿色可持续发展等前沿主题,汇聚国内外24家企业、百余名国际知名高校与科研代表共同商讨如何推动全行业6G标准共识形成。6G迈入关键期,
    紫光展锐 2025-04-17 18:55 202浏览
  • 1. 在Ubuntu官网下载Ubuntu server  20.04版本https://releases.ubuntu.com/20.04.6/2. 在vmware下安装Ubuntu3. 改Ubuntu静态IP$ sudo vi /etc/netplan/00-installer-config.yaml# This is the network config written by 'subiquity'network:  renderer: networkd&nbs
    二月半 2025-04-17 16:27 156浏览
  • 现阶段,Zigbee、Z-Wave、Thread、Wi-Fi与蓝牙等多种通信协议在智能家居行业中已得到广泛应用,但协议间互不兼容的通信问题仍在凸显。由于各协议自成体系、彼此割据,智能家居市场被迫催生出大量桥接器、集线器及兼容性软件以在不同生态的设备间构建通信桥梁,而这种现象不仅增加了智能家居厂商的研发成本与时间投入,还严重削减了终端用户的使用体验。为应对智能家居的生态割裂现象,家居厂商需为不同通信协议重复开发适配方案,而消费者则需面对设备入网流程繁琐、跨品牌功能阉割及兼容隐患等现实困境。在此背景
    华普微HOPERF 2025-04-17 17:53 121浏览
  • 一、行业背景与需求智能门锁作为智能家居的核心入口,正从单一安防工具向多场景交互终端演进。随着消费者对便捷性、安全性需求的提升,行业竞争已从基础功能转向成本优化与智能化整合。传统门锁后板方案依赖多颗独立芯片(如MCU、电机驱动、通信模块、语音模块等),导致硬件复杂、功耗高、开发周期长,且成本压力显著。如何通过高集成度方案降低成本、提升功能扩展性,成为厂商破局关键。WTVXXX-32N语音芯片通过“单芯片多任务”设计,将语音播报、电机驱动、通信协议解析、传感器检测等功能整合于一体,为智能门锁后板提供
    广州唯创电子 2025-04-18 09:04 185浏览
  •     CTI是Comparative Tracking Indices的缩写,在GB 4207中被译作“比较追踪指数”,我认为还是“漏电起痕指数”更容易理解。    CTI表述了材料的绝缘特性——阻止不希望出现的电流。CTI的单位是V,在绝缘物表面添加一定量的电解液并施加电场,观察在绝缘物表面既不会持续产生火焰,也不会因为热、介质击穿、湿气或者污染物产生电弧放电留下痕迹(起痕)的最高电压。CTI的测量方法见IEC 60112 (GB 4207)。&nbs
    电子知识打边炉 2025-04-19 21:20 54浏览
  •     爬电距离指的是两个带电体之间、带电体和大地之间,沿着绝缘材料表面的最短距离。与爬电距离有关的标准有IEC 61010-1。PCB设计方面,可以参照IPC-2221B。        (图源TI)    任何情况下,爬电距离不允许小于电气间隙。当绝缘材料是空气时,爬电距离可以和电气间隙相等。电气间隙的简介见协议标准第011篇。        一般情况下
    电子知识打边炉 2025-04-19 20:54 45浏览
  •   无人机蜂群电磁作战仿真系统全解析   一、系统概述   无人机蜂群电磁作战仿真系统是专业的仿真平台,用于模拟无人机蜂群在复杂电磁环境中的作战行为与性能。它构建虚拟电磁环境,模拟无人机蜂群执行任务时可能遇到的电磁干扰与攻击,评估作战效能和抗干扰能力,为其设计、优化及实战应用提供科学依据。   应用案例   目前,已有多个无人机蜂群电磁作战仿真系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润无人机蜂群电磁作战仿真系统。这些成功案例为无人机蜂群电磁作战仿真系统的推广和应用提
    华盛恒辉l58ll334744 2025-04-17 16:29 166浏览
  • 一、行业背景与需求随着智能化技术的快速发展和用户对便捷性需求的提升,电动车行业正经历从传统机械控制向智能交互的转型。传统电动车依赖物理钥匙、遥控器和独立防盗装置,存在操作繁琐、功能单一、交互性差等问题。用户期待通过手机等智能终端实现远程控制、实时数据监控及个性化交互体验。为此,将蓝牙语音芯片集成至电动车中控系统,成为推动智能化升级的关键技术路径。二、方案概述本方案通过在电动车中控系统中集成WT2605C蓝牙语音芯片,构建一套低成本、高兼容性的智能交互平台,实现以下核心功能:手机互联控制:支持蓝牙
    广州唯创电子 2025-04-18 08:33 196浏览
  •   无人机电磁环境效应仿真系统:深度剖析   一、系统概述   无人机电磁环境效应仿真系统,专为无人机在复杂电磁环境下的性能评估及抗干扰能力训练打造。借助高精度仿真技术,它模拟无人机在各类电磁干扰场景中的运行状态,为研发、测试与训练工作提供有力支撑。   应用案例   目前,已有多个无人机电磁环境效应仿真系统在实际应用中取得了显著成效。例如,北京华盛恒辉和北京五木恒润无人机电磁环境效应仿真系统。这些成功案例为无人机电磁环境效应仿真系统的推广和应用提供了有力支持。   二、系统功能  
    华盛恒辉l58ll334744 2025-04-17 15:51 150浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦