嵌入式linux驱动学习-用cdev代替register_chrdev()
上回说到字符设备驱动程序的注册与销毁register_chrdev()和unregister_chrdev()这是有缺陷的。
嵌入式lnux驱动学习-2.一个驱动程序的流程
现在用另外一个更好的方法代替,我们先来看看register_chrdev()实际上是调用了
__register_chrdev(major, 0, 256, name, fops);
static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops){return __register_chrdev(major, 0, 256, name, fops);}
这个256其实就是申请的次设备号个数。
还记得怎么创建设备节点吗,无论是手动创建还是自动创建都是用主设备号加上次设备号,一个驱动程序可以有很多不同的设备节点。
一个驱动程序有自己对应的file_operations结构体,A驱动对应A_fop结构体,用register_chrdev()后,A驱动的256个设备节点都对应A_fop结构体。
用另外一种方法,可以指定次设备号个数,举例:
用register_chrdev()后,A驱动主设备号254,B驱动主设备号就不能用254,不然就冲突了。
而另外一种方法,A驱动主设备号254,次设备号申请0-2,3个,B驱动主设备号仍然可以用254,次设备号只要不用0-3就行,主设备号相同也不会产生冲突。
实际上另外一种方法就是把register_chrdev()展开:
1.分配主次设备号
#define LED_MAJOR 0#define DEVICE_NUM 1static int major = LED_MAJOR;static int __init myled_init(void){int ret;dev_t devno = MKDEV (major,0);if (major)ret = register_chrdev_region(devno, DEVICE_NUM, "myled");else {ret = alloc_chrdev_region(&devno, 0, DEVICE_NUM, "myled") ;major = MAJOR(devno);}
dev_t devno定义了完整设备号, 为 32 位, 其中 12 位为主设备号, 20 位为次设备号。
使用如下宏可以从 dev_t 获得主设备号和次设备号:
MAJOR (dev_t dev)
MINOR (dev_t dev)
使用如下宏从主、次设备号获得完整的设备号
MKDEV (major,minor)
register_chrdev_region()函数用于已知起始设备的设备号的情况, 而alloc_chrdev_region() 用于设备号未知, 向系统动态申请未被占用的设备号的情况,可以自动避开设备号重复的冲突。
DEVICE_NUM为我们要申请的次设备号个数,这里设置了1个。
2.初始化 cdev 结构体
在 Linux 内核中, 使用 cdev 结构体描述一个字符设备, cdev 结构体的定义如下:
struct cdev {struct kobject kobj; /* 内嵌的 kobject */struct module *owner; /* 所属模块 */struct file_operations *ops; /* 文件操作结构体 */struct list_head list;dev_t dev; /* 设备号 */unsigned int count;};
cdev 结构体里有一个重要成员 file_operations 定义了字符设备驱动提供给虚拟文件系统的接口函数。
绑定file_operations结构体在cdev结构体初始化中完成
为了精简就写个什么都没有的open函数。
static int led_open (struct inode *node, struct file *filp){return 0;}static struct file_operations myled_oprs = {.owner = THIS_MODULE,.open = led_open,};static int __init myled_init(void){int ret;dev_t devno = MKDEV (major,0);if (major)ret = register_chrdev_region(devno, DEVICE_NUM, "myled");else {ret = alloc_chrdev_region(&devno, 0, DEVICE_NUM, "myled") ;major = MAJOR(devno);}cdev_init(&cdev_myled, &myled_oprs);//初始化......}
3.添加驱动
很简单,就是在初始化后加一句
cdev_add (&cdev_myled, devno, DEVICE_NUM);
4.删除驱动和设备号
在驱动出口使用
cdev_del(&cdev_myled);unregister_chrdev_region(MKDEV (major, 0), DEVICE_NUM);
5.完整测试
自动创建设备节点的方式是一样的,我们只申请一个次设备号0,但是用次设备号0和1,创建两个设备节点myled0,myled1。
然后写一个简单的应用程序,功能只是打开设备节点,如果是一个myled0能打开,myled1打不开即正常。
驱动:
#include <linux/module.h>#include <linux/kernel.h>#include <linux/fs.h>#include <linux/init.h>#include <linux/delay.h>#include <linux/uaccess.h>#include <asm/irq.h>#include <asm/io.h>#include <linux/cdev.h>#include <linux/device.h>#define LED_MAJOR 0#define DEVICE_NUM 1static int major = LED_MAJOR;static struct class *led_class;static struct cdev cdev_myled;static int led_open (struct inode *node, struct file *filp){return 0;}static struct file_operations myled_oprs = {.owner = THIS_MODULE,.open = led_open,};static int __init myled_init(void){int ret;dev_t devno = MKDEV (major,0);if (major)ret = register_chrdev_region(devno, DEVICE_NUM, "myled");else {ret = alloc_chrdev_region(&devno, 0, DEVICE_NUM, "myled") ;major = MAJOR(devno);}cdev_init(&cdev_myled, &myled_oprs);cdev_add (&cdev_myled, devno, DEVICE_NUM);led_class = class_create(THIS_MODULE, "myled");device_create(led_class, NULL, MKDEV(major, 0),NULL,"myled0");device_create(led_class, NULL, MKDEV(major, 1),NULL,"myled1");return 0;}static void __exit myled_exit(void){cdev_del(&cdev_myled);unregister_chrdev_region(MKDEV (major, 0), DEVICE_NUM);device_destroy(led_class, MKDEV(major, 0));device_destroy(led_class, MKDEV(major, 1));class_destroy(led_class);}module_init(myled_init);module_exit(myled_exit);MODULE_LICENSE("GPL");
应用:
#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <stdio.h>int main(int argc,char **argv){int fd;if(argc != 2) {printf("usage:%s num",argv[0]);return 0;}fd = open(argv[1],O_RDWR);if(fd < 0) {printf("can't open!\n");} elseprintf("can open\n");return 0;}
makefile:
KERN_DIR = /usr/src/linux-headers-4.8.0-36-genericall:make -C $(KERN_DIR) M=`pwd` modulesgcc -o led_test led_test.cclean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderrm -f led_nothingobj-m += led_nothing.o

文件传入linux系统,make命令编译,insmod命令加载驱动:

结果:

测试成功
6.测试2
两个驱动用同一个主设备号,看看系统能不能识别
如下代码,第一个主设备号由系统自动分配后,第二个主设备号就用和第一个一样的,两个驱动对应不同的open函数,分别生成两个设备,对应设备成功打开时,用printk输出信息。
如果打开不同节点时,输出信息不同,说明成功。
应用程序和上文完全相同
#include <linux/module.h>#include <linux/kernel.h>#include <linux/fs.h>#include <linux/init.h>#include <linux/delay.h>#include <linux/uaccess.h>#include <asm/irq.h>#include <asm/io.h>#include <linux/cdev.h>#include <linux/device.h>#define LED_MAJOR 0#define DEVICE_NUM 2static int major = LED_MAJOR;static struct class *led_class;static struct cdev cdev_myled;static struct cdev cdev_myled2;static int led_open (struct inode *node, struct file *filp){printk("myled0/1 open\n");return 0;}static struct file_operations myled_oprs = {.owner = THIS_MODULE,.open = led_open,};static int led_open2 (struct inode *node, struct file *filp){printk("myled2/3 open\n");return 0;}static struct file_operations myled_oprs2 = {.owner = THIS_MODULE,.open = led_open2,};static int __init myled_init(void){int ret;dev_t devno = MKDEV (major,0);if (major)ret = register_chrdev_region(devno, DEVICE_NUM, "myled");else {ret = alloc_chrdev_region(&devno, 0, DEVICE_NUM, "myled") ;major = MAJOR(devno);}cdev_init(&cdev_myled, &myled_oprs);cdev_add (&cdev_myled, devno, DEVICE_NUM);register_chrdev_region(MKDEV (major,2), DEVICE_NUM, "myled2");cdev_init(&cdev_myled2, &myled_oprs2);cdev_add (&cdev_myled2, MKDEV (major,2), DEVICE_NUM);led_class = class_create(THIS_MODULE, "myled");device_create(led_class, NULL, MKDEV(major, 0),NULL,"myled0");device_create(led_class, NULL, MKDEV(major, 1),NULL,"myled1");device_create(led_class, NULL, MKDEV(major, 2),NULL,"myled2");device_create(led_class, NULL, MKDEV(major, 3),NULL,"myled3");return 0;}static void __exit myled_exit(void){cdev_del(&cdev_myled);unregister_chrdev_region(MKDEV (major, 0), DEVICE_NUM);device_destroy(led_class, MKDEV(major, 0));device_destroy(led_class, MKDEV(major, 1));cdev_del(&cdev_myled2);unregister_chrdev_region(MKDEV (major, 2), DEVICE_NUM);device_destroy(led_class, MKDEV(major, 2));device_destroy(led_class, MKDEV(major, 3));class_destroy(led_class);}module_init(myled_init);module_exit(myled_exit);MODULE_LICENSE("GPL");
编译后insmod装载驱动,cat /proc/devices查看一下

两个主设备号相同的驱动程序出现了

四个设备节点也都打开成功,用dmseg命令查看内核打印信息

成功。可见cdev方法虽然多了几步,但是更加灵活。在今后的讲解中为了精简代码,还是用register_chrdev()。
更多内容与参考资料:大叔的嵌入式小站:
嵌入式linux驱动学习-3.用cdev代替register_chrrdev
相关文章:
嵌入式linux驱动学习-用cdev代替register_chrdev()
上回说到字符设备驱动程序的注册与销毁register_chrdev()和unregister_chrdev()这是有缺陷的。 嵌入式lnux驱动学习-2.一个驱动程序的流程 现在用另外一个更好的方法代替,我们先来看看register_chrdev()实际上是调用了 __register_chrdev(major, 0, 256, name,…...
技术更新!10个MySQL性能调优技巧
MySQL是世界上使用最广泛的开源数据库,它在业界的受欢迎程度让其他数据库望尘莫及。它是一个关系型数据库管理系统,多年来依然是应用程序的核心。在过去几年里,MySQL有一些重要发展。因此,整理更新10个MySQL性能调优技巧。 模式设…...
ICLR 2023|VLDet:从图像-文本对中学习区域-词语对齐的开放词汇式目标检测
原文链接:https://www.techbeat.net/article-info?id4614&isPreview1 作者:林闯 目标检测任务在AI工业界具有非常广泛的应用,但由于数据获取和标注的昂贵,检测的目标一直被限制在预先设定好的有限类别上。而在学术界…...
如何效率搭建企业流程系统?试试低代码平台吧
编者按:本文介绍了一款可私有化部署的低代码平台,可用于搭建团队流程管理体系,并详细介绍了该平台可实现的流程管理功能。关键词:可视化设计,集成能力,流程审批,流程调试天翎是国内最早从事快速开发平台研发…...
嵌入式开发:C++在深度嵌入式系统中的应用
深度嵌入式系统通常在C语言中实现。为什么会这样?这样的系统是否也能从C中获益?嵌入式开发人员在将广泛、高效的深度嵌入式代码库从C转换为C方面的实践经验的贡献。嵌入式和深度嵌入式系统通常用C而不是C实现。软件开发人员必须放弃C作为强类型系统、模板元编程(TMP)和面向对…...
快鲸scrm发布快递行业私域运营解决方案
现如今,快递行业竞争格局日益激烈,前有“四通一达”等传统快递企业,后有自带互联网基因、绑定电商流量新贵快递企业,如菜鸟、京东等。在这一背景下,很多快递企业开启了增长破局之旅,他们纷纷搭建起私域运营…...
【蓝桥杯集训·每日一题】AcWing 1497. 树的遍历
文章目录一、题目1、原题链接2、题目描述二、解题报告1、思路分析2、时间复杂度3、代码详解三、知识风暴递归一、题目 1、原题链接 1497. 树的遍历 2、题目描述 一个二叉树,树中每个节点的权值互不相同。 现在给出它的后序遍历和中序遍历,请你输出它的 …...
详解matplotlib的color配置
详解matplotlib的color配置 Matplotlib可识别的color格式 格式举例RGB或RGBA,由[0, 1]之间的浮点数组成的元组,分别代表红色、绿色、蓝色和透明度(0.1, 0.2, 0.5), (0.1, 0.2, 0.5, 0.3不区分大小写的十六进制RGB或RGBA字符串。‘#0f0f0f’, ‘#0f0f0f…...
Oracle删除表数据的三种方式
简介 oracle数据库mysql数据库都是如此 drop命令>truncate命令>delete命令,它们的执行方式、效率和结果各有不同。还是万年的student 学生表 自己可以建个尝试这玩一下。 drop命令 语句: drop table 表名; 理由:1、用drop删除表数据&…...
第 16 章_多版本并发控制
第 16 章_多版本并发控制 1. 什么是MVCC MVCC (Multiversion Concurrency Control),多版本并发控制。顾名思义,MVCC 是通过数据行的多个版本管理来实现数据库的并发控制。这项技术使得在InnoDB的事务隔离级别下执行一致性读操作…...
五种 IO 模型
文章目录操作系统和内存内核空间和用户空间应用程序的内核态和用户态网络 IO 和磁盘 IO简易的网络通信流程阻塞和非阻塞阻塞 IO 模型非阻塞 IO 模型IO 复用模型SelectPollEpoll小结信号驱动 IO 模型异步 IO 模型五种 IO 模型的对比IO 模型里的同步和异步5种 IO 模型分别是&…...
34-Golang中的结构体!!!
Golang中的结构体结构体和结构体变量(实例)的区别和联系结构体变量(实例)在内存中的布局如何声明结构体字段/属性注意事项和细节说明创建结构体实例的四种方式结构体使用细节结构体和结构体变量(实例)的区别和联系 1.结构体是自定义的数据类型,代表一类事物2.结构体…...
这6个视频剪辑素材库,你一定要知道~
推荐5个免费商用视频素材网站,建议收藏哦! 1、菜鸟图库 视频素材下载_mp4视频大全 - 菜鸟图库 网站素材量很大,有设计、图片、音频、视频等超多素材,大部分都能免费下载。视频素材都很高清,有自然、人物、科技、农业…...
RocketMQ WIN11 搭建
去官方下载 https://rocketmq.apache.org/zh/download/ 下载,博主下载的是 4.6.0 的版本,选择Binary版本 拓展 Source 下载:需要编译 Binary 下载:不需要编译 解压缩,运行 先解压缩环境变量中添加rocketMQ文件夹路…...
iPhone更换电池和屏幕后提醒非原厂配件的操作办法
---开局一张图,内容全靠编系列! 【图】 自从在iPhone系统iOS13开始支持原厂配件检测后,可以说苹果也动起了维修站商家利益的这块蛋糕。道理自然简单,卷嘛!全球汽车行业也不是靠卖新车才赚钱的,各种交通事故…...
chatGPT发布记录
发行说明(2 月 13 日)我们对 ChatGPT 进行了多项更新!这是新功能:我们更新了免费计划中 ChatGPT 模型的性能,以便为更多用户提供服务。根据用户反馈,我们现在默认让 Plus 用户使用更快的 ChatGPT 版本&…...
DataX及DataX-Web
大数据Hadoop之——数据同步工具DataX数据采集工具-DataX datax详细介绍及使用 一、概述 DataX 是阿里云DataWorks数据集成的开源版本,在阿里巴巴集团内被广泛使用的离线数据同步工具/平台。DataX 实现了包括 MySQL、Oracle、OceanBase、SqlServer、Postgre、HDFS、…...
数据结构与算法系列之kmp算法
什么是kmp算法 1.kmp算法是一种改进的字符串算法,其核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数已达到快速匹配的目的。 它主要实现作用的是 在 (主串)中找到 (匹配)字符串。 例 BF算法与k…...
算法分析详解
自古老的公元前1世纪开始,《周髀算经》就作为中国最古老的天文学和数学著作。 《周髀算经》采用最简便可行的方法确定天文历法,揭示日月星辰的运行规律,包括四季更替,气候变化,南北有极,昼夜相推的道理。为…...
东南大学自然辩证法概论期末总结
写在前面 作者:夏日 博客地址:https://blog.csdn.net/zss192 本文为2022年东南大学自然辩证法概论期末总结,内容为根据老师所发题纲综合多个资料总结得来 考试形式:从老师所发题纲,10个题目中选出4个,题…...
保姆级教程:用iSYSTEM winIDEA和iC5000给S32K148烧录程序,附完整配置流程
从零掌握iSYSTEM工具链:S32K148开发板烧录与调试全流程实战第一次接触iSYSTEM的winIDEA和iC5000仿真器时,很多嵌入式开发者都会感到无从下手。不同于常见的开源工具链,这套专业级开发环境在汽车电子和工业控制领域有着广泛应用,尤…...
抖音数字资产管理方法论:构建个人内容沉淀系统的技术实践
抖音数字资产管理方法论:构建个人内容沉淀系统的技术实践 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback su…...
别再乱用分支了!Flowable四种网关(排他/并行/包容/事件)实战选型指南
Flowable四大网关实战选型:从混乱到精准的决策艺术当你在设计一个请假审批流程时,是否遇到过这样的困惑:部门经理审批后需要同时通知HR和财务,但某些特殊情况下又需要跳过财务直接归档?这种看似简单的业务需求…...
DeepSeek系统设计辅助效能断崖式下降的3个信号,第2个90%工程师至今未察觉!
更多请点击: https://kaifayun.com 第一章:DeepSeek系统设计辅助效能断崖式下降的3个信号,第2个90%工程师至今未察觉! 当 DeepSeek 的系统设计辅助能力突然变“笨”——接口建议频繁失准、上下文感知错乱、生成代码无法通过基础编…...
告别鼠标手!5分钟上手开源鼠标连点器MouseClick,轻松实现自动化点击
告别鼠标手!5分钟上手开源鼠标连点器MouseClick,轻松实现自动化点击 【免费下载链接】MouseClick 🖱️ MouseClick 🖱️ 是一款功能强大的鼠标连点器和管理工具,采用 QT Widget 开发 ,具备跨平台兼容性 。软…...
上线前最后一道防线,DeepSeek代码审查如何帮你拦截87%的CVE类缺陷?
更多请点击: https://intelliparadigm.com 第一章:上线前最后一道防线,DeepSeek代码审查如何帮你拦截87%的CVE类缺陷? 在软件交付生命周期末期,传统人工代码审计与通用SAST工具常因误报率高、上下文理解弱而漏检高危漏…...
CANoe诊断测试没CDD文件怎么办?手把手教你用Fault Memory窗口和CAPL脚本读取解析DTC故障码
CANoe诊断测试无CDD文件的实战解决方案:从Fault Memory到CAPL脚本全解析当CDD文件缺失或定义不清晰时,诊断测试工程师常常陷入困境。本文将深入探讨如何利用Fault Memory窗口的基础功能,并通过CAPL脚本实现更灵活、更强大的故障码读取与解析方…...
基于Arduino与nRF24L01+的无线传感器平台设计与部署指南
1. 项目概述与设计思路如果你和我一样,喜欢在阳台或者小院子里种点蔬菜瓜果,那你肯定也遇到过这样的烦恼:出门几天,心里总惦记着家里的番茄苗是不是缺水了,小温室里的温度会不会太高。传统的温湿度计只能让你在现场读数…...
WebSocket实时通信架构进阶:Room、命名空间与集群部署
WebSocket实时通信架构进阶:Room、命名空间与集群部署 作者:Crown_22 | AI Agent & Hermes Agent 桌面程序开发者 前言 WebSocket已经成为实时应用的标准技术,但大多数教程只停留在"建立连接、发送消息"的基础阶段。在生产环境中,你需要处理Room管理、命名空…...
Unity事件系统实战:用事件驱动重构你的金币拾取逻辑(告别硬编码)
Unity事件系统实战:用事件驱动重构你的金币拾取逻辑(告别硬编码)在游戏开发中,我们经常会遇到这样的场景:玩家拾取金币后,需要更新UI、播放音效、解锁成就、保存数据……如果把这些逻辑全部写在金币拾取的代…...
