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二进制"微信公众号
评论
  • 戴上XR眼镜去“追龙”是种什么体验?2024年11月30日,由上海自然博物馆(上海科技馆分馆)与三湘印象联合出品、三湘印象旗下观印象艺术发展有限公司(下简称“观印象”)承制的《又见恐龙》XR嘉年华在上海自然博物馆重磅开幕。该体验项目将于12月1日正式对公众开放,持续至2025年3月30日。双向奔赴,恐龙IP撞上元宇宙不久前,上海市经济和信息化委员会等部门联合印发了《上海市超高清视听产业发展行动方案》,特别提到“支持博物馆、主题乐园等场所推动超高清视听技术应用,丰富线下文旅消费体验”。作为上海自然
    电子与消费 2024-11-30 22:03 75浏览
  • RDDI-DAP错误通常与调试接口相关,特别是在使用CMSIS-DAP协议进行嵌入式系统开发时。以下是一些可能的原因和解决方法: 1. 硬件连接问题:     检查调试器(如ST-Link)与目标板之间的连接是否牢固。     确保所有必要的引脚都已正确连接,没有松动或短路。 2. 电源问题:     确保目标板和调试器都有足够的电源供应。     检查电源电压是否符合目标板的规格要求。 3. 固件问题: &n
    丙丁先生 2024-12-01 17:37 57浏览
  • 国产光耦合器正以其创新性和多样性引领行业发展。凭借强大的研发能力,国内制造商推出了适应汽车、电信等领域独特需求的专业化光耦合器,为各行业的技术进步提供了重要支持。本文将重点探讨国产光耦合器的技术创新与产品多样性,以及它们在推动产业升级中的重要作用。国产光耦合器创新的作用满足现代需求的创新模式新设计正在满足不断变化的市场需求。例如,高速光耦合器满足了电信和数据处理系统中快速信号传输的需求。同时,栅极驱动光耦合器支持电动汽车(EV)和工业电机驱动器等大功率应用中的精确高效控制。先进材料和设计将碳化硅
    克里雅半导体科技 2024-11-29 16:18 161浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2024-12-02 10:40 63浏览
  • 学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&
    youyeye 2024-11-30 14:30 65浏览
  • 在电子技术快速发展的今天,KLV15002光耦固态继电器以高性能和强可靠性完美解决行业需求。该光继电器旨在提供无与伦比的电气隔离和无缝切换,是现代系统的终极选择。无论是在电信、工业自动化还是测试环境中,KLV15002光耦合器固态继电器都完美融合了效率和耐用性,可满足当今苛刻的应用需求。为什么选择KLV15002光耦合器固态继电器?不妥协的电压隔离从本质上讲,KLV15002优先考虑安全性。输入到输出隔离达到3750Vrms(后缀为V的型号为5000Vrms),确保即使在高压情况下,敏感的低功耗
    克里雅半导体科技 2024-11-29 16:15 119浏览
  • 《高速PCB设计经验规则应用实践》+PCB绘制学习与验证读书首先看目录,我感兴趣的是这一节;作者在书中列举了一条经典规则,然后进行详细分析,通过公式推导图表列举说明了传统的这一规则是受到电容加工特点影响的,在使用了MLCC陶瓷电容后这一条规则已经不再实用了。图书还列举了高速PCB设计需要的专业工具和仿真软件,当然由于篇幅所限,只是介绍了一点点设计步骤;我最感兴趣的部分还是元件布局的经验规则,在这里列举如下:在这里,演示一下,我根据书本知识进行电机驱动的布局:这也算知行合一吧。对于布局书中有一句:
    wuyu2009 2024-11-30 20:30 88浏览
  • 光耦合器作为关键技术组件,在确保安全性、可靠性和效率方面发挥着不可或缺的作用。无论是混合动力和电动汽车(HEV),还是军事和航空航天系统,它们都以卓越的性能支持高要求的应用环境,成为现代复杂系统中的隐形功臣。在迈向更环保技术和先进系统的过程中,光耦合器的重要性愈加凸显。1.混合动力和电动汽车中的光耦合器电池管理:保护动力源在电动汽车中,电池管理系统(BMS)是最佳充电、放电和性能监控背后的大脑。光耦合器在这里充当守门人,将高压电池组与敏感的低压电路隔离开来。这不仅可以防止潜在的损坏,还可以提高乘
    腾恩科技-彭工 2024-11-29 16:12 119浏览
  • 最近几年,新能源汽车愈发受到消费者的青睐,其销量也是一路走高。据中汽协公布的数据显示,2024年10月,新能源汽车产销分别完成146.3万辆和143万辆,同比分别增长48%和49.6%。而结合各家新能源车企所公布的销量数据来看,比亚迪再度夺得了销冠宝座,其10月新能源汽车销量达到了502657辆,同比增长66.53%。众所周知,比亚迪是新能源汽车领域的重要参与者,其一举一动向来为外界所关注。日前,比亚迪汽车旗下品牌方程豹汽车推出了新车方程豹豹8,该款车型一上市就迅速吸引了消费者的目光,成为SUV
    刘旷 2024-12-02 09:32 62浏览
  • 艾迈斯欧司朗全新“样片申请”小程序,逾160种LED、传感器、多芯片组合等产品样片一触即达。轻松3步完成申请,境内免费包邮到家!本期热荐性能显著提升的OSLON® Optimal,GF CSSRML.24ams OSRAM 基于最新芯片技术推出全新LED产品OSLON® Optimal系列,实现了显著的性能升级。该系列提供五种不同颜色的光源选项,包括Hyper Red(660 nm,PDN)、Red(640 nm)、Deep Blue(450 nm,PDN)、Far Red(730 nm)及Ho
    艾迈斯欧司朗 2024-11-29 16:55 157浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦