内核技术中文网»首页 论坛 资料 查看内容

0 评论

0 收藏

分享

浅谈Linux内核中的多模块调用

概述

在实际项目开发中,项目功能往往相对比较庞大,此时就需要我们对项目进行模块化设计,将项目分解成一个个独立的小模块单独实现,最后再使用类似搭积木的方式,将各种小模块搭建成我们实际需要的系统。

应用程序

应用程序实现多模块调用的方式:将子模块剥离出来,由单独的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将函数接口显式导出一下。

回复

举报 使用道具

全部回复
暂无回帖,快来参与回复吧
主题 278
回复 0
粉丝 0
扫码获取每晚技术直播链接
快速回复 返回顶部 返回列表