概述
在实际项目开发中,项目功能往往相对比较庞大,此时就需要我们对项目进行模块化设计,将项目分解成一个个独立的小模块单独实现,最后再使用类似搭积木的方式,将各种小模块搭建成我们实际需要的系统。
应用程序
应用程序实现多模块调用的方式:将子模块剥离出来,由单独的c文件实现,然后建立对应的头文件对其接口进行声明,主函数模块只需包含h文件,然后直接调用子模块中的公开接口即可。示例如下:
1、定义app_module.c子模块,用于实现out_str和add两个接口。
/** * @Filename : app_submod.c * @Revision : $Revision: 1.00 $ * @Author : Feng(更多编程相关的知识和源码见微信公众号:不只会拍照的程序猿,欢迎订阅) * @Description : 应用程序多模块编程示例,子程序部分 **/ #include <stdio.h> #include <stdlib.h> /** * @打印字符串 * @str:字符串 **/ void out_str(char *str) { printf("the string is <%s>\n", str); } /** * @加法运算 * @a、b:加数 * @返回和 **/ int add(int a, int b) { return (a + b); }
2、定义主函数模块app_module.c,调用子模块中的out_str和add方法。
注意:严格来说,应该将方法声明放在一个单独的头文件中,不过为了节约篇幅,这里直接在主函数模块里进行声明。
/** * @Filename : app_module.c * @Revision : $Revision: 1.00 $ * @Author : Feng(更多编程相关的知识和源码见微信公众号:不只会拍照的程序猿,欢迎订阅) * @Description : 应用程序多模块编程示例,主程序部分 **/ #include <stdio.h> #include <stdlib.h> /** * @打印字符串 * @str:字符串 **/ void out_str(char *str); /** * @加法运算 * @a、b:加数 * @返回和 **/ int add(int a, int b); int main(void) { int a = 10, b = 20; out_str("seven"); /* 调用out_str函数,打印输出seven */ printf("%d + %d = %d\n", a, b, add(a, b)); return 0; }
3、编译并运行程序。
feng:drv_module$ gcc -o app app_module.c app_submod.c feng:drv_module$ ./app the string is <seven> 10 + 20 = 30 feng:drv_module$
内核模块
与应用程序多模块调用类似,内核模块的多模块调用,同样是将子模块剥离出来,由单独的c文件实现,然后建立对应的头文件对其接口进行声明,主函数模块只需包含h文件,然后直接调用只模块中的公开接口即可。
与应用程序多模块调用的区别是,在子模块中需要调用宏EXPORT_SYMBOL和EXPORT_SYMBOL_GPL将函数接口显式导出一下。相关宏函数在头文件linux/export.h中定义。
#define EXPORT_SYMBOL(sym) \ __EXPORT_SYMBOL(sym, "") #define EXPORT_SYMBOL_GPL(sym) \ __EXPORT_SYMBOL(sym, "_gpl") #define EXPORT_SYMBOL_GPL_FUTURE(sym) \ __EXPORT_SYMBOL(sym, "_gpl_future") #ifdef CONFIG_UNUSED_SYMBOLS #define EXPORT_UNUSED_SYMBOL(sym) __EXPORT_SYMBOL(sym, "_unused") #define EXPORT_UNUSED_SYMBOL_GPL(sym) __EXPORT_SYMBOL(sym, "_unused_gpl") #else #define EXPORT_UNUSED_SYMBOL(sym) #define EXPORT_UNUSED_SYMBOL_GPL(sym) #endif
示例
★包含子模块源文件drv_submod.c、主模块源文件drv_module.c和Makefile文件(均已验证通过)。
drv_submod.c
/** * @Filename : drv_submod.c * @Revision : $Revision: 1.00 $ * @Author : Feng(更多编程相关的知识和源码见微信公众号:不只会拍照的程序猿,欢迎订阅) * @Description : 驱动多模块编程示例,子模块部分 **/ #include <linux/init.h> #include <linux/module.h> /** * @打印字符串 * @str:字符串 **/ void out_str(char *str) { printk("the string is <%s>\n", str); } EXPORT_SYMBOL(out_str); /*显示的将函数进行导出一下*/ /** * @加法运算 * @a、b:加数 * @返回和 **/ int add(int a, int b) { return (a + b); } EXPORT_SYMBOL(add); /*显示的将函数进行导出一下*/ MODULE_LICENSE("GPL"); /* 模块的许可证声明 */ /* 调用modinfo xx(模块名)查看 */ MODULE_AUTHOR("feng"); /* 模块的作者 */ MODULE_VERSION ("1.00"); /* 模块版本号 */ /* MODULE_DESCRIPTION("xxxxx"); 模块描述 */ /* MODULE_ALIAS("xxx"); 模块别名 */
drv_module.c
/** * @Filename : drv_module.c * @Revision : $Revision: 1.00 $ * @Author : Feng(更多编程相关的知识和源码见微信公众号:不只会拍照的程序猿,欢迎订阅) * @Description : 驱动多模块编程示例,主模块部分 **/ #include <linux/init.h> #include <linux/module.h> /** * @打印字符串 * @str:字符串 **/ void out_str(char *str); /** * @加法运算 * @a、b:加数 * @返回和 **/ int add(int a, int b); /* 驱动加载时执行,调用insmod或者modprobe加载驱动 */ static int __init drv_module_init(void) { int a = 10, b = 20; printk("hello : drv_module_init\n"); out_str("seven"); /* 调用out_str函数,打印输出seven */ printk("%d + %d = %d\n", a, b, add(a, b)); return 0; } /* 驱动卸载时执行,调用rmsmod或者modprobe -r卸载驱动 */ static void __exit drv_module_exit(void) { printk("bye : drv_module_exit\n"); } module_init(drv_module_init); /* 指定入口函数 */ module_exit(drv_module_exit); /* 指定出口函数 */ MODULE_LICENSE("GPL"); /* 模块的许可证声明 */ /* 调用modinfo xx(模块名)查看 */ MODULE_AUTHOR("feng"); /* 模块的作者 */ MODULE_VERSION ("1.00"); /* 模块版本号 */ /* MODULE_DESCRIPTION("xxxxx"); 模块描述 */ /* MODULE_ALIAS("xxx"); 模块别名 */
Makefile
#根文件所在目录 ROOTFS_DIR = /home/feng/atomic/rootfs #交叉编译工具链 CROSS_COMPILE = arm-linux-gnueabihf- CC = $(CROSS_COMPILE)gcc #驱动目录路径 DRV_DIR = $(ROOTFS_DIR)/home/drv DRV_DIR_LIB = $(ROOTFS_DIR)/lib/modules/4.1.15 #KERNELRELEASE由内核makefile赋值 ifeq ($(KERNELRELEASE), ) #内核路径 KERNEL_DIR =/home/feng/atomic/resource/linux-imx-rel_imx_4.1.15_2.1.0_ga #当前文件路径 CURR_DIR = $(shell pwd) all: #编译模块 make -C $(KERNEL_DIR) M=$(CURR_DIR) modules clean: #清除模块文件 make -C $(KERNEL_DIR) M=$(CURR_DIR) clean install: #拷贝模块文件 cp -raf *.ko $(DRV_DIR_LIB) else #指定编译什么文件 obj-m += drv_module.o drv_submod.o endif
结论
1、进入目录,执行make命令编译模块;然后执行make install命令,拷贝模块到目标机指定目录。
feng:drv_module$ make #编译模块 make -C /home/feng/atomic/resource/linux-imx-rel_imx_4.1.15_2.1.0_ga M=/mnt/hgfs/Share/linux/atomic/driver/drv_module modules make[1]: 进入目录“/home/feng/atomic/resource/linux-imx-rel_imx_4.1.15_2.1.0_ga” CC [M] /mnt/hgfs/Share/linux/atomic/driver/drv_module/drv_module.o CC [M] /mnt/hgfs/Share/linux/atomic/driver/drv_module/drv_submod.o Building modules, stage 2. MODPOST 2 modules CC /mnt/hgfs/Share/linux/atomic/driver/drv_module/drv_module.mod.o LD [M] /mnt/hgfs/Share/linux/atomic/driver/drv_module/drv_module.ko CC /mnt/hgfs/Share/linux/atomic/driver/drv_module/drv_submod.mod.o LD [M] /mnt/hgfs/Share/linux/atomic/driver/drv_module/drv_submod.ko make[1]: 离开目录“/home/feng/atomic/resource/linux-imx-rel_imx_4.1.15_2.1.0_ga” feng:drv_module$ make install #拷贝模块文件 cp -raf *.ko /home/feng/atomic/rootfs/lib/modules/4.1.15 feng:drv_module$
2、在目标机上执行modprobe命令加载模块。
注意:在模块加载之前,需要先调用depmod命令,生成模块依赖文件。
/ # depmod / # modprobe drv_module.ko hello : drv_module_init the string is <seven> 10 + 20 = 30 / #
3、使用modprobe命令加载模块,可以自动分析依赖模块并加载,使用lsmod命令,查看当前系统已加载的模块。
/ # lsmod Module Size Used by Tainted: G drv_module 708 0 drv_submod 949 1 drv_module / #
4、在目标机上执行modprobe -r命令卸载模块。
/ # modprobe -r drv_module bye : drv_module_exit / # lsmod Module Size Used by Tainted: G / #
5、综上、内核模块与应用程序加载多模块的方式基本一致,只是内核模块调用多模块需要在子模块中需要使用宏EXPORT_SYMBOL和EXPORT_SYMBOL_GPL将函数接口显式导出一下。
往期 · 推荐
帮你自动化办公的python-自动提取pdf指定页(文件处理篇)
帮你自动化办公的python-自动提取pdf指定页(项目概述)
也没想象中那么神秘的数据结构-一种通用化的双向链表设计(底层源码)
关注
更多精彩内容,请关注微信公众号:不只会拍照的程序猿,本人致力分享linux、设计模式、C语言、嵌入式、编程相关知识,也会抽空分享些摄影相关内容,同样也分享大量摄影、编程相关视频和源码,另外你若想要本文章源码请关注公众号:不只会拍照的程序猿,后台回复:linux驱动源码。