嵌入式Linux学习笔记之内核模块

2019-07-12 20:01发布

1、概述

Linux内核包含的组件非常多,如果把这些所有的组件编译进内核将会导致两个问题:一是编译出来的内核文件将会很大,而有的组件在内核运行过中只在很短的过程中起到作用,这样就会造成硬件的一种浪费;二是如果想要新增或者删除某些组件,就必须重新编译内核,而编译Linux内核这种工程无论如何也不是一件简单的事情。 针对以上问题,Linux提供了一种内核模块机制来解决。Linux内核在运行过程中可以根据要求动态加载或者删除模块,模块加载进入内核之后与编译进内核的组件有相同的权限。内核模块的的优势在于不需要重新编译内核就可以完成对某些组件的删除或者新增,而且整个过程不需要重新启动系统。 Linux内核模块与Linux驱动程序之间没有直接的联系。但是由于Linux内核模块的动态加载、卸载特性,大多数的Linux驱动都是以内核模块的形式实现。因此,想要学好Linux驱动程序开发,第一步就必须掌握好内核模块。 内核模块的学习相对简单,与传统C程序相比,只需注意几个点即可。

2.Linux内核模块模型

Linux内核模块包含以下几个部分: l  模块加载函数(必须) l  模块卸载函数(必须) l  许可证声明(必须) l  模块参数(可选) l  导出符号(可选) l  模块声明与描述(可选)

1、模块加载函数:

模块通过insmod或者modprobe被加载时的入口函数,由module_init()声明。 ①Linux内核模块与一般的C程序不同,一般C程序入口地址为main函数,而内核模块的入口地址为模块加载函数。一般在内核模块中不定义main函数; ②如果读Linux内核源码,会发现一般模块加载函数都是以__init标号定义,例如: static int __init sh_tmu_init(void)      //定义模块加载函数
{
    …….
    …….
    return 0;
}
module_init(sh_tmu_init);          //声明模块加载函数 该函数定义位于Linux源码Drivers/char/Clocksource/st_tmu.c ③模块加载函数声明为int型,用static关键字限制其作用于为当前文件; ④在驱动程序中,模块加载函数一般完成设备号的申请,设备的注册,硬件的初始化等工作。

2、模块卸载函数

模块通过rmmod命令被卸载时的出口函数,由module_exit()声明。 ①与模块加载函数相对应,模块卸载函数一般以__exit标号定义,例如: static void __exit sh_tmu_exit(void) //定义模块卸载函数
{ ….. ….. } module_exit(sh_tmu_exit);        //声明模块卸载函数 该函数定义位于Linux源码Drivers/char/Clocksource/st_tmu.c ②模块卸载函数声明为void型,以static关键字声明将其作用域限制在当前文件中; ③模块卸载函数应该完成与模块加载函数相反的工作,在加载函数中申请了设备号硬改在卸载函数中释放,注册了的结构应该在此注销,申请的硬件资源应该在此释放。

3、许可证声明

许可证声明是必须选项,用MODULE_LICENSE()声明,如果没有,在加载内核模块的时候将会提示内核被污染。 MODULE_LICENSE("GPL v2");   //许可声明为GPL v2

4、模块参数

模块参数用module_param()来声明。 module_param(参数名,参数类型,参数读写权限) 在装载内核模块时,用户可以向模块传递参数,形式为: “insmode(或 modprobe)模块名 参数名=参数值” 如果不传递,参数将使用模块内定义的默认值。 这里的参数类型需要注意,参数类型有byte、short、ushort、int、uint、long、ulong、charp(字符指针)、bool 或 invbool(布尔的反) module_param(irq, uint, 0); module_param(mem, ulong, 0); 以上两个模块参数的使用示例在内核源码的Drivers/Char/Applicom.c中定义。

5、导出符号

模块参数是在加载过程中传入模块的参数,与之对应的模块导出符号可以理解为内核模块传出给内核的符号。内核模块导出的符号记录在“/proc/kallsyms”文件中。 内核模块可以使用如下宏导出符号到内核符号表: EXPORT_SYMBOL(符号名); EXPORT_SYMBOL_GPL(符号名); EXPORT_SYMBOL_FEATURE(符号名); 示例: EXPORT_SYMBOL_GPL(amd64_get_dram_hole_info); 上述示例用法定义于Linux内核源码Drivers/Char/Edac/Amd64_edac.c中。

6、模块声明与描述

MODULE_AUTHOR(author);
MODULE_DESCRIPTION(description);
MODULE_VERSION(version_string);
MODULE_DEVICE_TABLE(table_info);
MODULE_ALIAS(alternate_name); 以上宏分别用于声明内核模块的作者,描述,版本,设备表信息和别名。 需要注意的是MODULE_DEVICE_TABLE这个宏,MODULE_DEVICE_TABLE的第一个参数是设备的类型,后面一个参数是设备表,这个设备表的最后一个元素是空的,用于标识结束。

3、内核模块的编译

在Linux内核源码Documentationkbuild中给出了Linux内核Makefile的编写规则,如果想对linux内核有深入了解可以去参考。下面提供一个Makefile的编写示例: obj-m += xxx.o all:          make -C /home/s4/lesson2/linux-smart210M=$(PWD) modules clear:          rm *.o *.ko *.mod.c *.mod.o  这里的/home/s4/lesson2/linux-smart210是内核源码的路径,M后面跟的是要编译的文件的路径。 编译过程分析: 先进入内核源码编译生成xxx.o文件,然后会生成xxx.mod.c文件并根据这个文件生成xxx.mod.o,最后将xxx.o和xxx.mod.o链接为xxx.ko文件。