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二进制"微信公众号
评论
  • 职场是人生的重要战场,既是谋生之地,也是实现个人价值的平台。然而,有些思维方式却会悄无声息地拖住你的后腿,让你原地踏步甚至退步。今天,我们就来聊聊职场中最忌讳的五种思维方式,看看自己有没有中招。1. 固步自封的思维在职场中,最可怕的事情莫过于自满于现状,拒绝学习和改变。世界在不断变化,行业的趋势、技术的革新都在要求我们与时俱进。如果你总觉得自己的方法最优,或者害怕尝试新事物,那就很容易被淘汰。与其等待机会找上门,不如主动出击,保持学习和探索的心态。加入优思学院,可以帮助你快速提升自己,与行业前沿
    优思学院 2025-01-09 15:48 53浏览
  • 「他明明跟我同梯进来,为什么就是升得比我快?」许多人都有这样的疑问:明明就战绩也不比隔壁同事差,升迁之路却比别人苦。其实,之间的差异就在于「领导力」。並非必须当管理者才需要「领导力」,而是散发领导力特质的人,才更容易被晓明。许多领导力和特质,都可以通过努力和学习获得,因此就算不是天生的领导者,也能成为一个具备领导魅力的人,进而被老板看见,向你伸出升迁的橘子枝。领导力是什么?领导力是一种能力或特质,甚至可以说是一种「影响力」。好的领导者通常具备影响和鼓励他人的能力,并导引他们朝着共同的目标和愿景前
    优思学院 2025-01-08 14:54 93浏览
  •  在全球能源结构加速向清洁、可再生方向转型的今天,风力发电作为一种绿色能源,已成为各国新能源发展的重要组成部分。然而,风力发电系统在复杂的环境中长时间运行,对系统的安全性、稳定性和抗干扰能力提出了极高要求。光耦(光电耦合器)作为一种电气隔离与信号传输器件,凭借其优秀的隔离保护性能和信号传输能力,已成为风力发电系统中不可或缺的关键组件。 风力发电系统对隔离与控制的需求风力发电系统中,包括发电机、变流器、变压器和控制系统等多个部分,通常工作在高压、大功率的环境中。光耦在这里扮演了
    晶台光耦 2025-01-08 16:03 84浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2025-01-09 09:58 43浏览
  • 在智能网联汽车中,各种通信技术如2G/3G/4G/5G、GNSS(全球导航卫星系统)、V2X(车联网通信)等在行业内被广泛使用。这些技术让汽车能够实现紧急呼叫、在线娱乐、导航等多种功能。EMC测试就是为了确保在复杂电磁环境下,汽车的通信系统仍然可以正常工作,保护驾乘者的安全。参考《QCT-基于LTE-V2X直连通信的车载信息交互系统技术要求及试验方法-1》标准10.5电磁兼容试验方法,下面将会从整车功能层面为大家解读V2X整车电磁兼容试验的过程。测试过程揭秘1. 设备准备为了进行电磁兼容试验,技
    北汇信息 2025-01-09 11:24 69浏览
  • 在当前人工智能(AI)与物联网(IoT)的快速发展趋势下,各行各业的数字转型与自动化进程正以惊人的速度持续进行。如今企业在设计与营运技术系统时所面临的挑战不仅是技术本身,更包含硬件设施、第三方软件及配件等复杂的外部因素。然而这些系统往往讲究更精密的设计与高稳定性,哪怕是任何一个小小的问题,都可能对整体业务运作造成严重影响。 POS应用环境与客户需求以本次分享的客户个案为例,该客户是一家全球领先的信息技术服务与数字解决方案提供商,遭遇到一个由他们所开发的POS机(Point of Sal
    百佳泰测试实验室 2025-01-09 17:35 59浏览
  • 根据环洋市场咨询(Global Info Research)项目团队最新调研,预计2030年全球中空长航时无人机产值达到9009百万美元,2024-2030年期间年复合增长率CAGR为8.0%。 环洋市场咨询机构出版了的【全球中空长航时无人机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球中空长航时无人机总体规模,包括产量、产值、消费量、主要生产地区、主要生产商及市场份额,同时分析中空长航时无人机市场主要驱动因素、阻碍因素、市场机遇、挑战、新产品发布等。报告从中空长航时
    GIRtina 2025-01-09 10:35 60浏览
  • 1月7日-10日,2025年国际消费电子产品展览会(CES 2025)盛大举行,广和通发布Fibocom AI Stack,赋智千行百业端侧应用。Fibocom AI Stack提供集高性能模组、AI工具链、高性能推理引擎、海量模型、支持与服务一体化的端侧AI解决方案,帮助智能设备快速实现AI能力商用。为适应不同端侧场景的应用,AI Stack具备海量端侧AI模型及行业端侧模型,基于不同等级算力的芯片平台或模组,Fibocom AI Stack可将TensorFlow、PyTorch、ONNX、
    物吾悟小通 2025-01-08 18:17 53浏览
  • HDMI 2.2 规格将至,开启视听新境界2025年1月6日,HDMI Forum, Inc. 宣布即将发布HDMI规范2.2版本。新HDMI规范为规模庞大的 HDMI 生态系统带来更多选择,为创建、分发和体验理想的终端用户效果提供更先进的解决方案。新技术为电视、电影和游戏工作室等内容制作商在当前和未来提供更高质量的选择,同时实现多种分发平台。96Gbps的更高带宽和新一代 HDMI 固定比率速率传输(Fixed Rate Link)技术为各种设备应用提供更优质的音频和视频。终端用户显示器能以最
    百佳泰测试实验室 2025-01-09 17:33 60浏览
  • 一个真正的质量工程师(QE)必须将一件产品设计的“意图”与系统的可制造性、可服务性以及资源在现实中实现设计和产品的能力结合起来。所以,可以说,这确实是一种工程学科。我们常开玩笑说,质量工程师是工程领域里的「侦探」、「警察」或「律师」,守护神是"墨菲”,信奉的哲学就是「墨菲定律」。(注:墨菲定律是一种启发性原则,常被表述为:任何可能出错的事情最终都会出错。)做质量工程师的,有时会不受欢迎,也会被忽视,甚至可能遭遇主动或被动的阻碍,而一旦出了问题,责任往往就落在质量工程师的头上。虽然质量工程师并不负
    优思学院 2025-01-09 11:48 82浏览
  • 在过去十年中,自动驾驶和高级驾驶辅助系统(AD/ADAS)软件与硬件的快速发展对多传感器数据采集的设计需求提出了更高的要求。然而,目前仍缺乏能够高质量集成多传感器数据采集的解决方案。康谋ADTF正是应运而生,它提供了一个广受认可和广泛引用的软件框架,包含模块化的标准化应用程序和工具,旨在为ADAS功能的开发提供一站式体验。一、ADTF的关键之处!无论是奥迪、大众、宝马还是梅赛德斯-奔驰:他们都依赖我们不断发展的ADTF来开发智能驾驶辅助解决方案,直至实现自动驾驶的目标。从新功能的最初构思到批量生
    康谋 2025-01-09 10:04 59浏览
  • 故障现象一辆2017款东风风神AX7车,搭载DFMA14T发动机,累计行驶里程约为13.7万km。该车冷起动后怠速运转正常,热机后怠速运转不稳,组合仪表上的发动机转速表指针上下轻微抖动。 故障诊断 用故障检测仪检测,发动机控制单元中无故障代码存储;读取发动机数据流,发现进气歧管绝对压力波动明显,有时能达到69 kPa,明显偏高,推断可能的原因有:进气系统漏气;进气歧管绝对压力传感器信号失真;发动机机械故障。首先从节气门处打烟雾,没有发现进气管周围有漏气的地方;接着拔下进气管上的两个真空
    虹科Pico汽车示波器 2025-01-08 16:51 108浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦