先给自己打个广告,本人的微信公众号正式上线了,搜索:张笑生的地盘,主要关注嵌入式软件开发,足球等等,希望大家多多关注,有问题可以直接留言给我,一定尽心尽力回答大家的问题
一 why
前一篇博客《linux内核模块编程(三)----字符驱动设备开发》,我们初步实现了一个linux内核字符设备驱动,主要是为了大家参照该驱动程序源码,学会如何进行字符设备驱动开发,不知道大家是否成功在自己的本地运行起来这个驱动。
今天这篇文章,我们来谈谈字符设备驱动开发的框架套路,几个重要的概念和重要内核函数。
二 what
- 字符设备驱动框架
首先我们看一下字符设备驱动的软件框架,如下。字符设备驱动运行在kernel space,用户空间通过常规的open,write,read函数调用驱动中对应的xxx_open,xxx_write,xxx_read函数以实现对某个硬件设备的控制
1. 必须要有一个设备号,用于在众多设备驱动中进行区分
2. 用户必须知道设备驱动对应的设备节点(设备文件名)
3. 在linux中一切皆文件,因此对设备的操作被linux封装成了文件操作,应用空间的open,wrie,read的时候,实际在内核驱动代码中有对应的xx_open,xx_read,xx_write
- 字符设备驱动相关的内核函数
2.1 申请设备号
// 创建设备号
int register_chrdev(unsigned int major, const char * name, const struct file_operations * fops)
参数1:主设备号
设备号(32bit dev_t) = 主设备号(高12bit) + 次设备号(低20bit)
主设备号 : 表示一类设备 --- 比如:手机上camera
次设备号 : 表示一类设备中的某一个:前置camera,后置camera
给定的方式有两种:
1. 动态--参数1直接填0
2. 静态--指定的一个整数,250
参数2:描述设备信息,可以自定义
/proc/devices 列举出所有的已经注册的设备
参数3:文件操作对象 --- 提供open,wrie,read
返回值:正确返回0,错误返回负数
有创建就有注销
static inline void unregister_chrdev(unsigned int major, const char *name)
2.2 创建设备节点
创建设备节点的时候,可以选择手动创建,也可以选择自动创建。手动创建一般适合在debug的情况,在一般的驱动中建议采用自动创建设备节点的方式
1. 手动创建---缺点: /dev/目录中文件都是在内存中,断电后 /dev/目录中文件都会消失
mknod /dev/设备名 类型 主设备号 次设备号
比如:
mknod /dev/char0 c 240 0
2. 自动创建(通过 udev/mdev机制)
class_create
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
struct class *__class_create(struct module *owner, const char *name,
struct lock_class_key *key)
参数1: THIS_MODULE
参数2:字符串名字,自定义
返回一个struct class指针
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
参数1: class_create返回的struct class指针
参数2:表示父亲,一般直接填 NULL
参数3:表示设备号类型, MKDEV(major, 0)
参数4:表示私有数据,一般也是直接填NULL
参数5,6:可变参数,字符串,表示设备节点的名字
返回一个struct device指针
- 总结
字符设备驱动开发的一般步骤如下,再次强调一下是在linux内核4.15版本下的总结,可能有些函数接口不一样。
初始化注册步骤
1. 申请字符设备的资源---申请设备号,通过register_chrdev()函数实现
2. 创建设备节点,通过class_create()和device_create()两个函数实现
当我们卸载某个字符设备驱动时,对应的注销和注册是成对出现的,但是记住遵循一个原则,先注册的后注销,后注册的先注销,步骤如下
1. 先调用device_destroy()---对应函数device_create()
2. 再调用class_destroy()---对应函数class_create()
3. 最后调用unregister_chrdev()---对应函数register_chrdev()
关于一个完整的字符设备驱动程序示例,还请参考《linux内核模块编程(三)----字符驱动设备开发》,该博客中有示例源码。后期有可能还是会参照这样的思路来写博文,先给出一个示例程序,编译运行,然后再出一篇博文分析它,或者有可能在一片博文中先给出示例,然后再分析,这样可能会造成一篇博文较长,不知道大家是否能看完。
三 to be continued
因为字符设备驱动的目的,是方便用户空间程序能够驱动硬件设备工作的,所以下一篇博文会给出如何在用户空间通过调用字符设备驱动,来控制我们的硬件。