构建第一个驱动程序
驱动模块的组成
一个驱动模块主要由以下部分组成:、
组成 | 选项 |
---|---|
头文件 | 必选 |
模块参数 | 可选 |
模块功能函数 | 可选 |
其他 | 可选 |
模块加载函数 | 必须 |
模块卸载函数 | 必须 |
模块许可声明 | 必须 |
1.头文件(必选)
#include <linux/module.h>
#include <linux/init.h>
2.模块参数(可选 )
模块参数是驱动模块加载时,需要传递给驱动模块的参数。
3.模块加载函数(必须 )
模块加载函数是模块加载时,需要执行的函数。
4.模块卸载函数(必须)
模块卸载函数是模块卸载时,需要执行的函数。
5.模块许可声明(必须)
模块许可声明表示模块受内核支持的程度,有许可权的模块会更受到开发人员的重视。需要使用MODULE_LICENSE表示该模块哦的许可权限。内核可识别的许可权限如下:
MODULE_LICENSE("GPL"); //任一版本的GNU公共许可权
MODULE_LICENSE("GPL v2"); //GPL版本2许可权
MODULE_LICENSE("GPL and additional rights"); //GPL及其附加许可权
MODULE_LICENSE("Dual BSD/GPL"); //BSD/GPL双重许可权
MODULE_LICENSE("Dual MPL/GPL"); //MPL/GPL双重许可权
MODULE_LICENSE("Proprietary"); //专有许可权
第一个驱动模块
开始编写一个最简单的驱动模块
#include <linux/module.h>
#include <linux/init.h> //导入需要的头文件
static int hello_init(void)
{
printk(KERN_ALERT "Hello World\n"); //加载函数打印 Hello World 信息
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye, World\n"); //卸载函数
}
module_init(hello_init); //指定模块加载函数
module_exit(hello_exit); //指定模块卸载函数
MODULE_LICENSE("Dual BSD/GPL"); // 指定许可权为 Dual BSD/GPL
编译Hello World模块
正确编译内核模块,需要满足以下条件:
编译内核模块的条件
- 使用正确版本的编译工具、模块工具和其他必要的工具。
- 应该有一份内核源码,该源码的版本应该和系统目前 使用的内核版本一致。
- 内核源码应该至少编译一次,也就是执行过make命令。
Makefile文件
一个完整的Makefile文件如下:
ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /linux-2.6.29.4/linux-2.6.29.4
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
else
obj-m := hello.o
endif
编译模块
使用make命令就可以生成模块文件hello.ko了
模块的操作
命令 | 操作 |
---|---|
insmod | 加载模块 |
rmmod | 卸载模块 |
modprobe | 比较高级的加载和卸载模块命令 |
lsmod | 查看已经加载的模块和信息 |
modinfo | 查询模块的相关信息 |
加载Hello World模块后系统发生的变化
在路径/proc/modules下发生变化,modules文件中会增加如下一行:
cat modules | grep he*
lsmod | grep hello
/proc/devices 文件没有变化,因为hello.ko模块不是一个设备模块
/sys/module/目录会增加hello这个模块的基本信息。
在/sys/module/下会增加一个hello目录。 使用tree -a hello 可以查看以层次结构为组织的内核模块信息属性
模块参数和模块之间通信
可以使用module_param(参数名,参数数据类型,参数读写权限)来为模块定义个参数。
static long a = 1;
static int b = 1;
module_param(a,long,S_IRUGO);
module_param(b,int,S_IRUGO);
参数数据类型可以是byte、short、ushort、int、uint、long、ulong、bool、charp(字符指针类型)。不支持浮点类型,printk()也不支持浮点类型。
导出函数
使用EXPORT_SYMBOL使函数变为导出函数。
将模块加入内核
向内核添加模块
向liunx内核中添加驱动模块,需要完成3个工作:
- 编写驱动程序文件
- 将驱动程序文件放到linux内核源码的相应目录中,如果没有合适的目录,可以自己建立一个目录存放驱动程序文件。
- 在目录的Kconfig文件中添加新驱动程序对于的项目编译选择。
- 在目录的Makefile文件中添加新驱动程序的编译语句。
Kconfig
内核源码树的目录下都有两个文件Kconfig和Makefile。分布到各目录的Kconfig文件构成了一个分布式的内核配置数据库,每个Kconfig文件分别描述了所属目录源文档相关的内核配置菜单。在内核配置make menuconfig时,从Kconfig中读出菜单,用户选择后保存到.config这个内核配置文件中,在内核编译时,主目录中的Makefile调用这个.config文件,就知道了用户的选择。
Kconfig语法
主要关键字
config
menuconfig
choice/endchoice
comment
menu/endmenu //以上五项为菜单选项
if/endif //是一个条件选项
菜单入口
config 关键字定义一个新的配置选项,之后定义该配置选项的属性。属性可以有类型、输入提示,依赖关系,帮助信息和默认值等。
每个配置选项都必须指定一种类型,包括bool、tristate、string、hex和int,其中tristate和string是两种基本类型。
bool "set version information on all modules symbols" #定义bool类型菜单提示
依赖关系
depends on <expr>
如果定义了多个依赖关系,那么可以用&&来连接,表示与的关系。
bool "foo" if BAR #如果定义了BAR选项,那么使能foo选项
default y if BAR #如果定义BAR选项,那么foo的默认值为y,表示编译入内核
等价于
depends on BAR
bool "foo"
default y
菜单结构
菜单结构一般作为菜单入口的父菜单。
选择菜单(choice)
选择菜单定义一组选项。此选项的类型只能是boolean 和 tristate型。
"choice"
<choice options>
<choice block>
"endchoice"
在一个硬件有多个驱动的情况下可以使用choice菜单,使用choice 菜单可以实现最终只有一个驱动被编译进内核中,choice菜单可以接受的另一个选项是optional,这样选项就被设置被N,表示没有被选中。
注释菜单(comment)
comment <prompt>
<comment options>