【i.MX6ULL】驱动开发2--新字符设备开发模板
上篇文章介绍了字符设备的开发模板,但那是一种旧版本的驱动开发模式,设备驱动需要手动分配设备号再使用 register_chrdev进行注册,加载成功以后还需要手动使用mknod命令创建设备节点,比较麻烦。
目前Linux内核推荐的新字符设备驱动API函数,使得驱动的使用更加自动化,本篇就来一起研究下。
先看目录:
[TOC]
1 旧字符设备驱动的弊端
使用register_chrdev函数注册字符设备,需要指定一个设备号,这就造成:
- 需要事先确定好哪些主设备号没有使用
- 会将一个主设备号下的所有次设备号都使用掉,比如主设备号为200,那么 0~1048575(2^20-1)这个区间的次设备号就全部都被占用了
回顾上一篇的操作,先是加载驱动:
加载完,还有手动使用mknod指令来手动创建该设备节点,并且指定驱动程序中写死的设备号:
本篇,就要使用一种新的字符驱动编写方式,实现设备号的自动分配,省去mknod指令操作。
2 新字符设备驱动原理
2.1 分配和释放设备号
使用设备号的时候向Linux内核申请,需要几个就申请几个,由Linux内核分配设备可以使用的设备号。
使用如下函数来申请设备号(该函数在上篇提到过):
1 | /* |
如果给定了设备的主设备号和次设备号就使用如下所示函数来注册设备号即可:
1 | /* |
注销字符设备之后要释放设备号,不管是通过alloc_chrdev_region函数的动态分配还是register_chrdev_region函数手动指定的设备号,统一使用(和上篇使用的一样)的释放函数:
1 | /* |
新字符设备驱动下,设备号分配示例代码如下:
1 | int major; /*主设备号*/ |
2.2 字符设备注册
2.2.1 cdev字符设备结构
在Linux中使用cdev结构体表示一个字符设备,其定义在include/linux/cdev.h文件中:
1 | struct cdev { |
2.2.2 cdev_init 函数
定义好cdev变量以后就要使用cdev_init函数对其进行初始化:
1 | /* |
该函数的使用示例如下:
1 | /*要初始化的cdev结构体*/ |
2.2.3 cdev_add函数
该函数用于向Linux系统添加字符设备,即cdev结构体变量:
1 | /* |
2.2.4 cdev_del函数
卸载驱动的时候要使用cdev_del函数从Linux内核中删除字符设备:
1 | /* |
2.3 自动创建设备节点
上篇的Linux驱动实验中,在使用modprobe加载驱动程序以后还需要使用“mknod”命令手动创建设备节点,比较麻烦,这里就来研究一下如何实现自动创建设备节点。
2.3.1 mdev机制
在Linux下通过udev来实现设备文件的自动创建与删除。使用busybox构建根文件系统的时候,busybox会创建一个udev的简化版本mdev。
所以,在嵌入式开发中使用mdev来实现设备节点文件的自动创建与删除。Linux系统中的热插拔事件也由mdev 管理,在/etc/init.d/rcS 文件中如下语句:
1 | echo /sbin/mdev > /proc/sys/kernel/hotplug |
2.3.2 创建和删除类
自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在cdev_add函数后面添
加自动创建设备节点相关代码。
首先要创建一个class类,其实是个结构体,定义在include/linux/device.h里面。class_create是类创建函数(宏定义):
1 |
|
卸载驱动程序的时候需要使用函数为class_destroy删除掉类:
1 | /* |
2.3.3 创建设备
创建好类以后还不能实现自动创建设备节点,还需要在这个类下创建一个设备。使用device_create函数创建设备:
1 | /* |
参数最后的...
表示这在是一个可变参数的函数。
2.4 设置文件私有数据
每个硬件设备都有一些属性, 比如主设备号(dev_t),类(class)、设备(device)、开关状态(state)等等,在编写驱动的时候你可以将这些属性全部写成变量的形式:
1 | dev_t devid; /*设备号*/ |
可以将所有属性信封装到结构体中, 并在编写驱动open函数的时候将其作为私有数据添加到设备文件中:
1 | /*设备结构体*/ |
3 驱动程序编写
在上篇的基础上进行修改,因为只是更换的驱动程序的编写方式,与应用程序无关,因此只修改驱动程序即可。
3.1 添加一些定义
因为上篇文章的代码中使用的是chrdevbase这个名称,为了减少修改量,这里仅把结构体类型定义为带有new标志的newchr_dev,变量名仍使用chrdevbase这个名称。
1 |
|
3.2 修改open函数
在上篇程序的基础上增加了一条“设置私有数据”
1 | static int chrdevbase_open(struct inode *inode, struct file *filp) |
3.3 修改init函数
这个修改比较大,因为要在init函数中使用设备号的自动分配。
1 | static int __init chrdevbase_init(void) |
3.4 修改exit函数
因为init修改较大,对应的exit也要进行大的修改:
1 | static void __exit chrdevbase_exit(void) |
至此,修改完毕,其它的与之前的一样。
3.5 新旧驱动方式对比
通过一张图来对比新旧两种驱动编写方式的区别:
- 旧方式编写驱动的流程
- 新方式编写驱动的流程
可以看出主要区别在驱动的加载和卸载。
4 编译驱动
和上次编译驱动的方式一样,使用makefile,因为驱动的c文件名由chrdevbase.c改为了newchrdevbase.c,因此makefile文件中也要把名字改掉。
编译完之后,将编译出的ko文件先复制到ubuntu虚拟机的tftpboot目录中,为后面的测序做准备。
复制后,看一下tftpboot目录:
5 程序测试
5.1 文件发送到板子
和上篇一样,使用tftp传输,将ubuntu虚拟机编译出的ko文件发送到linux板子中。
再来看下tftp传输的硬件环境示意图:
然后是传输指令以及传输结果,可以看到newchrdevbase.ko已经从ubuntu虚拟机的tftpboot目录传输到了linux板子的/lib/modules/4.1.15目录中了。
5.2 测试
输入如下两条指令加载 newchrdevbase.ko 驱动模块:
1 | depmod //第一次加载驱动的时候需要运行此命令 |
驱动加载成功后,可以看到自动申请到的主设备号和次设备号,如下图,主设备号为249。
再输入ls /dev/chrdevbase -l
指令验证/dev/chrdevbase 这个设备节点文件是否存在,如下图,可以看到设备存在,注意和上篇旧驱动方式操作上的不同之处,旧的驱动方式需要额外使用mknod指令来手动创建该设备节点。
驱动已经加载成功,再来测试APP程序,理论上和上篇的效果一样,实测也是:
OK,测试完毕,测试完使用rmmod指令卸载驱动。
6 总结
此篇文章针对上篇文章使用旧字符驱动编写方式存在的不足,介绍了一种新的字符驱动编写方式,对比两种方式编写的主要区别,在上篇驱动代码的基础上进行修改,并测试通过,和上篇实现一样的效果,但驱动的加载更加方便,不再需要人为指定设备号。
进入公众号,回复加群,可加入学习交流群~