Linux新字符设备驱动开发方式

linux字符设备驱动开发模板中介绍了旧版本的驱动开发模板,其需要手动分配设备号后,再进行注册,驱动加载成功后还需要手动创建设备节点,比较麻烦。 目前linux内核推荐的新字符设备驱动api函数,可以自动分配设备号、创建设备节点,使得驱动的使用更加方便
1. 新字符设备驱动原理
1.1 分配和释放设备号
旧字符设备驱动开发中使用register_chrdev函数注册字符设备时,需要事先确定好主设备号,并且注册成功后,会将该设备号下的所有次设备号都使用掉
而新字符设备驱动api函数很好的解决了这个问题,使用设备号时再向内核申请,需要几个就申请几个,由内核分配设备可以使用的设备号
设备号申请函数:没有指定设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) //dev:保存申请到的设备号//baseminor:次设备号起始地址,一般为0//count:要申请的设备号数量//name:设备名字设备号申请函数:指定了主次设备号
int register_chrdev_region(dev_t from, unsigned count, const char *name) //from:要申请的起始设备号//count:要申请的设备号数量//name:设备名字设备号释放函数:统一使用下面函数释放
void unregister_chrdev_region(dev_t from, unsigned count) //from:要释放的设备号//count:表示从from开始,要释放的设备号数量因此新字符设备驱动中,设备号分配代码通常按如下示例编写:
int major; /* 主设备号 */int minor; /* 次设备号 */dev_t devid; /* 设备号 */if (major) { /* 定义了主设备号 */ devid = mkdev(major, 0); /*大部分驱动次设备号都选择0*/ register_chrdev_region(devid, 1, test);} else { /* 没有定义设备号 */ alloc_chrdev_region(&devid, 0, 1, test); /*申请设备号*/ major = major(devid); /* 获取分配号的主设备号 */ minor = minor(devid); /* 获取分配号的次设备号 */}1.2 注册字符设备
linux中使用cdev表示字符设备,在include/linux/cdev.h中定义:
struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; //文件操作函数集合 struct list_head list; dev_t dev; //设备号 unsigned int count;};//编写字符设备驱动之前需要定义一个cdev结构体变量cdev_init函数:定义好cdev变量后,用该函数进行初始化
cdev_add函数:向系统添加字符设备(cdev结构体变量)
struct cdev testcdev;/* 设备操作函数 */static struct file_operations test_fops = { .owner = this_module, /* 其他具体的初始项 */};testcdev.owner = this_module;cdev_init(&testcdev, &test_fops); /* 初始化cdev结构体变量 */cdev_add(&testcdev, devid, 1); /* 添加字符设备 */cdev_del函数:卸载驱动时从内核中删除相应的字符设备
void cdev_del(struct cdev *p)//p:要删除的字符设备1.3 自动创建设备节点
旧字符设备驱动开发中,驱动程序加载成功后还需要使用mknod命令手动创建设备节点,十分麻烦。
而新字符设备驱动开发中,linux通过udev用户程序来实现设备文件的自动创建与删除。 udev会检测系统中硬件设备状态,并根据硬件设备状态来创建或者删除设备文件。
使用busybox构建根文件系统时,busybox会创建一个udev的简化版本mdev。 因此,在嵌入式开发中使用mdev来实现设备节点文件的自动创建与删除。 linux系统中的热插拔事件也由mdev管理,在/etc/init.d/rcs文件中有如下语句:
echo /sbin/mdev > /proc/sys/kernel/hotplug创建类:自动创建设备节点的工作是在驱动程序入口函数中完成的,一般在cdev_add之后添加相关代码
struct class *class_create (struct module *owner, const char *name)//owner 一般为 this_module//name 是类名字//返回值是个指向结构体class的指针,也就是创建的类删除类:卸载驱动程序时需要删除类
void class_destroy(struct class *cls)//cls 就是要删除的类创建设备:类创建好后还不能实现自动创建设备节点,还需要在该类下创建一个设备
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...) //class 设备创建在哪个类下//parent 父设备,一般为null//devt 设备号//drvdata 设备可能会使用的数据,一般null//fmt 设备名字//返回值是创建好的设备删除设备:卸载驱动时需要删除创建的设备
void device_destroy(struct class *class, dev_t devt)//class 是要删除的设备所处的类//devt 是要删除的设备号1.4 设置文件私有数据
每个硬件设备都有一些属性,比如主设备号、类、设备、开关状态等等,在编写驱动时可将这些属性封装成一个结构体。 并在编写驱动open函数时将设备结构体作为私有数据添加到设备文件中:
/*设备结构体*/ struct test_dev{ dev_t devid; /*设备号*/ struct cdev cdev; /*cdev*/ struct class *class; /*类*/ struct device *device; /*设备*/ int major; /*主设备号*/ int minor; /*次设备号*/ }; struct test_dev testdev; /*open函数*/ static int test_open(struct inode *inode, struct file *filp) { filp->private_data = &testdev; /*设置私有数据*/ return 0; }综上所述,新字符设备驱动开发流程如下图所示:
2. 新字符设备驱动开发实验
新字符设备驱动开发实验是在linux字符设备驱动开发模板一文的基础上进行修改,只更改了驱动的编写方式,与应用程序无关,因此只修改驱动程序即可
2.1 驱动程序编写
添加定义:宏及字符设备定义
#define chrdevbase_cnt 1 //设备号个数#define chrdevbase_name chrdevbase //名字/*newchr设备结构体 */struct newchr_dev{ dev_t devid; //设备号 struct cdev cdev; //cdev struct class *class; //类 struct device *device; //设备 int major; //主设备号 int minor; //次设备号};struct newchr_dev chrdevbase; //自定义字符设备修改open函数:设置私有数据
static int chrdevbase_open(struct inode *inode, struct file *filp){ printk(chrdevbase open!\\r\\n); filp->private_data = &chrdevbase; //设置私有数据 return 0;}修改init函数
static int __init chrdevbase_init(void){ /* 注册字符设备驱动 */ //1、创建设备号 if (chrdevbase.major) /* 定义了设备号 */ { chrdevbase.devid = mkdev(chrdevbase.major, 0); register_chrdev_region(chrdevbase.devid, chrdevbase_cnt, chrdevbase_name); } else /* 没有定义设备号 */ { alloc_chrdev_region(&chrdevbase.devid, 0, chrdevbase_cnt, chrdevbase_name); /* 申请设备号 */ chrdevbase.major = major(chrdevbase.devid); /* 获取分配号的主设备号 */ chrdevbase.minor = minor(chrdevbase.devid); /* 获取分配号的次设备号 */ } printk(chrdevbase major=%d,minor=%d\\r\\n,chrdevbase.major, chrdevbase.minor); //2、初始化cdev chrdevbase.cdev.owner = this_module; cdev_init(&chrdevbase.cdev, &chrdevbase_fops); //3、添加一个cdev cdev_add(&chrdevbase.cdev, chrdevbase.devid, chrdevbase_cnt); //4、创建类 chrdevbase.class = class_create(this_module, chrdevbase_name); if (is_err(chrdevbase.class)) { return ptr_err(chrdevbase.class); } //5、创建设备 chrdevbase.device = device_create(chrdevbase.class, null, chrdevbase.devid, null, chrdevbase_name); if (is_err(chrdevbase.device)) { return ptr_err(chrdevbase.device); } printk(chrdevbase init done!\\r\\n); return 0;}修改exit函数
static void __exit chrdevbase_exit(void){ /* 注销字符设备驱动 */ cdev_del(&chrdevbase.cdev);/* 删除cdev */ unregister_chrdev_region(chrdevbase.devid, chrdevbase_cnt); /* 注销设备号 */ device_destroy(chrdevbase.class, chrdevbase.devid); class_destroy(chrdevbase.class); printk(chrdevbase exit done!\\r\\n);}2.2 程序编译
程序编译包括驱动程序和应用程序编译两个部分:
驱动程序编译:创建makefile文件,使用make命令,编译驱动程序
kerneldir := /home/andyxi/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_andyxicurrent_path := $(shell pwd)obj-m := newchrdev.obuild: kernel_moduleskernel_modules:$(make) -c $(kerneldir) m=$(current_path) modulesclean:$(make) -c $(kerneldir) m=$(current_path) clean应用程序编译:无需内核参与,直接编译即可
arm-linux-gnueabihf-gcc newchrdevapp.c -o newchrdevapp2.3 运行测试
为了方便,选择通过tftp从网络启动,并使用nfs挂载网络根文件系统。 确保开发板能正常启动,在ubuntu中将驱动和测试文件复制到modules/4.1.15目录中
在开发板中输入如下指令加载驱动模块
depmod //第一次加载驱动的时候需要运行此命令modprobe newchrdev.ko //加载驱动驱动加载成功后,可以看到自动申请到的主设备号和次设备号
使用ls /dev/chrdevbase -l命令验证该设备节点文件是否存在,而旧驱动方式需要额外使用mknod指令来手动创建该设备节点文件
驱动加载成功后,测试app程序,如下
测试完使用rmmod指令卸载驱动
以上可见linux新字符设备驱动开发方式可以自动分配设备号、创建设备节点,使得驱动的使用更加方便、便捷。

纳微半导体宣布开设全新电动汽车设计中心
隔离开关常见故障及处理
关于FPGA上的FMC接口知识点的介绍
全自动电能表冲击电流试验装置的原理及设计
连接器中FPC与FFC的区别是什么
Linux新字符设备驱动开发方式
STM32单片机最小系统的工作原理和结构组成
ABAT系列蓄电池在线监测系统
学校有引进了哪些vr校园安全教育系统呢
华为发布行业运维服务的解决方案
“NoSQL”的定义、作用和使用方法详细说明
STM32单片机外部晶振配置时钟设置
TCL华星攻势迅猛,或将年底在LCD领域击败京东方
传统机器人已是百年之躯,企业该如何另寻新路
全球智能硬件“独角兽”企业发展现状与趋势
智能仓储之RFID仓库管理解决方案
苹果iPhone 12 mini销量远低于12/Pro/Max仅占6%,iPhone 12最畅销
智能手机的未来:计算是精彩体验的推动力
航空发动机螺纹怎么测量?SJ5780轮廓扫描测量仪全自动在线检测
python代码:利用几十行python书写微信机器人