本文主要是内核模块——内核模块符号导出的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
PC端开发环境 Windows Windows11 Ubuntu Ubuntu20.04.2的64位版本 VMware® Workstation 17 Pro 17.6.0 build-24238078 终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license)) Win32DiskImager Win32DiskImager v1.0 Linux开发板环境 Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板 uboot NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04 linux内核 linux-4.19.71(NXP官方提供)
点击查看本文参考资料
点击查看相关文件下载
内核模块编译生成的 ko 文件是相互独立的, 即模块之间变量或者函数在正常情况下无法进行互相访问。 而一些复杂的驱动模块需要分层进行设计, 这时候就需要用到内核模块符号导出(也可以叫符号共享)。
一、内核符号导出 1. 内核符号 内核符号指的就是在内核模块中导出函数和变量, 在加载模块时被记录在公共内核符号表中,以供其他模块调用。 这个机制,允许我们使用分层的思想解决一些复杂的模块设计。我们在编写一个驱动的时候, 可以把驱动按照功能分成几个内核模块,借助符号共享去实现模块与模块之间的接口调用,变量共享。
2. 符号导出使用的宏 符号导出所使用的宏为 EXPORT_SYMBOL(sym)和 EXPORT_SYMBOL_GPL(sym)。
2.1 EXPORT_SYMBOL(sym) EXPORT_SYMBOL(sym)定义在export.h - include/linux/export.h - EXPORT_SYMBOL :
1 2 #define EXPORT_SYMBOL(sym) \ __EXPORT_SYMBOL(sym, "" )
sym 表示要导出的函数或变量名称。
2.2 EXPORT_SYMBOL_GPL(sym) EXPORT_SYMBOL_GPL(sym)定义在export.h - include/linux/export.h - EXPORT_SYMBOL_GPL :
1 2 #define EXPORT_SYMBOL_GPL(sym) \ __EXPORT_SYMBOL(sym, "_gpl" )
EXPORT_SYMBOL(sym)和 EXPORT_SYMBOL_GPL(sym)两个宏使用方法相同, 而 EXPORT_SYMBOL_GPL(sym)导出的模块只能被 GPL 许可的模块使用, 所以绝大多数的情况都使用 EXPORT_SYMBOL(sym)进行符号导出。 sym表示要导出的函数或变量名称。
二、符号导出实例 1. 代码实例 1.1 module_sym_math_demo.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 #include <linux/kernel.h> #include <linux/init.h> #include <linux/module.h> #include "./timestamp_autogenerated.h" #include "./version_autogenerated.h" int itype = 0 ; static bool btype = 0 ; static char ctype = 0 ; static char *stype = 0 ; module_param(itype, int , 0 ); module_param(btype, bool , S_IRUGO); module_param(ctype, byte, 0 ); module_param(stype, charp, S_IRUGO); static int __init module_sym_math_demo_init (void ) { printk("*** [%s:%d]Build Time: %s %s, git version:%s ***\n" , __FUNCTION__, __LINE__, KERNEL_KO_DATE, KERNEL_KO_TIME, KERNEL_KO_VERSION); printk("module_sym_math_demo module init!\n" ); printk(KERN_ALERT "itype=%d\n" , itype); printk(KERN_ALERT "btype=%d\n" , btype); printk(KERN_ALERT "ctype=%d\n" , ctype); printk(KERN_ALERT "stype=%s\n" , stype); return 0 ; } static void __exit module_sym_math_demo_exit (void ) { printk("module_sym_math_demo exit!\n" ); } int sym_math_add (int a, int b) { return a + b; } int sym_math_sub (int a, int b) { return a - b; } EXPORT_SYMBOL(itype); EXPORT_SYMBOL(sym_math_add); EXPORT_SYMBOL(sym_math_sub); module_init(module_sym_math_demo_init); module_exit(module_sym_math_demo_exit); MODULE_LICENSE("GPL V2" ); MODULE_AUTHOR("sumu" ); MODULE_DESCRIPTION("Description" ); MODULE_ALIAS("module's other name" );
在这个内核模块中,我们导出三个符号:itype、sym_math_add和sym_math_sub,第一个为变量,后面两个为函数。另外该模块可以传入四个参数。
1.2 module_sym_demo.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <linux/kernel.h> #include <linux/init.h> #include <linux/module.h> #include "./timestamp_autogenerated.h" #include "./version_autogenerated.h" extern int itype;int sym_math_add (int a, int b) ;int sym_math_sub (int a, int b) ;static int __init module_sym_demo_init (void ) { printk("*** [%s:%d]Build Time: %s %s, git version:%s ***\n" , __FUNCTION__, __LINE__, KERNEL_KO_DATE, KERNEL_KO_TIME, KERNEL_KO_VERSION); printk("module_sym_demo module init!\n" ); printk(KERN_ALERT "itype+1=%d, itype-1=%d\n" , sym_math_add(itype, 1 ), sym_math_sub(itype, 1 )); return 0 ; } static void __exit module_sym_demo_exit (void ) { printk("module_sym_demo exit!\n" ); } module_init(module_sym_demo_init); module_exit(module_sym_demo_exit); MODULE_LICENSE("GPL V2" ); MODULE_AUTHOR("sumu" ); MODULE_DESCRIPTION("Description" ); MODULE_ALIAS("module's other name" );
该内核模块使用module_sym_math_demo.ko中导出的变量和符号进行运算。
1.3 Makefile 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 MODULE_NAME1 := module_sym_demo MODULE_NAME2 := module_sym_math_demo ARCH ?= arm MAKE_PARAM := CURRENT_PATH := $(shell pwd) KERNEL_KO_RELEASE = $(shell git rev-parse --verify --short HEAD) ifeq ("$(origin V)" , "command line" ) KBUILD_VERBOSE = $(V) endif ifndef KBUILD_VERBOSE KBUILD_VERBOSE = 0 endif ifeq ($(KBUILD_VERBOSE) ,1) quiet = Q = else quiet=quiet_ Q = @ endif INCDIRS := ./ SRCDIRS := ./ INCLUDE := $(patsubst %, -I %, $(INCDIRS) ) TFTP_SERVER ?= ~/3tftp NFS_SERVER ?= ~/4nfs TFTP_DIR ?= $(TFTP_SERVER) ROOTFS_ROOT_DIR ?= $(NFS_SERVER) /imx6ull_rootfs ROOTFS_MODULE_DIR ?= $(ROOTFS_ROOT_DIR) /drivers_demo ifeq ($(KERNELRELEASE) ,)ifeq ($(ARCH) , arm)KERNELDIR ?= ~/7Linux/imx6ull-kernel MAKE_PARAM += ARCH=arm MAKE_PARAM += CROSS_COMPILE=arm-linux-gnueabihf- CROSS_COMPILE_PREFIX ?= arm-linux-gnueabihf- else KERNELDIR ?= /lib/modules/$(shell uname -r) /build CROSS_COMPILE_PREFIX ?= endif CC := $(CROSS_COMPILE_PREFIX) gcc LD := $(CROSS_COMPILE_PREFIX) ld include Kbuild.include srctree := . timestamp_h := timestamp_autogenerated.h version_h := version_autogenerated.h export CC LD srctreeall: $(timestamp_h) $(version_h) modules modules: $(MAKE) $(MAKE_PARAM) -C $(KERNELDIR) M=$(CURRENT_PATH) modules $(INCLUDE) modules_install: $(MAKE) $(MAKE_PARAM) -C $(KERNELDIR) M=$(CURRENT_PATH) modules INSTALL_MOD_PATH=$(ROOTFS_MODULE_DIR) modules_install $(timestamp_h) : $(srctree) /Makefile FORCE $(call filechk,timestamp_autogenerated.h) $(version_h) : $(srctree) /Makefile FORCE $(call filechk,version_autogenerated.h) install: $(Q) sudo cp -v *.ko $(ROOTFS_MODULE_DIR) uninstall: $(Q) sudo rm -vf $(ROOTFS_MODULE_DIR) /$(MODULE_NAME1) .ko $(Q) sudo rm -vf $(ROOTFS_MODULE_DIR) /$(MODULE_NAME2) .ko PHONY += FORCE FORCE: PHONY += clean clean: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean $(Q) rm -rf $(timestamp_h) $(Q) rm -rf $(version_h) .PHONY : $(PHONY)help: @echo "\033[1;32m================================ Help ================================\033[0m" @echo "Ubuntu may need to add sudo:" @echo "insmod <module_name>.ko # Load module" @echo "rmmod <module_name> # Uninstall the module" @echo "dmesg -C # Clear the kernel print information" @echo "lsmod # Check the kernel modules that have been inserted" @echo "dmesg # View information printed by the kernel" @echo "file <module_name>.ko # View \".Ko\" file information" @echo "" @echo "make ARCH=x86_64 # x86_64 platform" @echo "make # arm platform" @echo "\033[1;32m======================================================================\033[0m" print: @echo "KERNELDIR = $(KERNELDIR) " @echo "INCLUDE = $(INCLUDE) " else CONFIG_MODULE_SIG = n obj-m += $(MODULE_NAME1) .o obj-m += $(MODULE_NAME2) .o endif
这里我们需要编译两个驱动,所以这里需要这样写:
1 2 obj-m += $(MODULE_NAME1) .o obj-m += $(MODULE_NAME2) .o
1.4 Kbuild.include 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 define filechk_timestamp_autogenerated.h (if test -n "$${SOURCE_DATE_EPOCH}" ; then \ SOURCE_DATE="@$${SOURCE_DATE_EPOCH}" ; \ DATE="" ; \ for date in gdate date.gnu date; do \ $${date} -u -d "$${SOURCE_DATE}" >/dev/null 2>&1 && DATE="$${date}" ; \ done; \ if test -n "$${DATE}" ; then \ LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +' LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +' LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +' LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +' LC_ALL=C $${DATE} -u -d "$${SOURCE_DATE}" +' else \ return 42; \ fi; \ else \ LC_ALL=C date +' LC_ALL=C date +' LC_ALL=C date +' LC_ALL=C date +' LC_ALL=C date +' fi) endef define filechk_version_autogenerated.h (echo \ echo \ echo \ echo \ endef define filechk $(Q) set -e; \ : ' CHK $@ '; \ mkdir -p $(dir $@ ) ; \ $(filechk_$(1)) < $< > $@ .tmp; \ if [ -r $@ ] && cmp -s $@ $@ .tmp; then \ rm -f $@ .tmp; \ else \ : ' UPD $@ '; \ mv -f $@ .tmp $@ ; \ fi endef
这个文件用于生成编译时间和git版本号。
2. 开发板测试 我们编译完毕后将其放到开发板的根文件系统中。然后就可以加载了,由于module_sym_demo.ko要使用module_sym_math_demo.ko中的符号,我们需要先加载module_sym_math_demo.ko。
我们执行以下命令:
1 2 insmod module_sym_math_demo.ko itype=123 btype=1 ctype=200 stype=abc insmod module_sym_demo.ko
可以看到如下输出信息:
若是反过来的话就会报如下错误:
卸载驱动的时候,我们要先卸载使用符号的module_sym_demo.ko,再卸载提供导出符号的module_sym_math_demo.ko:
1 2 rmmod module_sym_demo.ko rmmod module_sym_math_demo.ko
我们会看到以下打印信息:
若是反过来,则会报以下错误:
3. 内核导出符号表 我们在驱动中导出的符号是可以在根文件系统中直接搜索到的,内核导出的符号表在这个/proc/kallsyms
文件中,我们可以使用cat命令查看:
但是这个文件含有大量的符号,我们的根文件系统支持grep命令的话我们可以配合grep命令使用:
1 2 3 cat /proc/kallsyms | grep itype cat /proc/kallsyms | grep sym_math_add cat /proc/kallsyms | grep sym_math_sub
我们会看到如下打印信息: