概述
在实际项目开发中,项目功能往往相对比较庞大,此时就需要我们对项目进行模块化设计,将项目分解成一个个独立的小模块单独实现,最后再使用类似搭积木的方式,将各种小模块搭建成我们实际需要的系统。
应用程序
应用程序实现多模块调用的方式:将子模块剥离出来,由单独的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将函数接口显式导出一下。