嵌入式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个,题…...
FastDDS XML配置实战:从HelloWorld到可配置QoS的完整迁移指南
FastDDS XML配置实战:从硬编码到灵活部署的工程化演进 在分布式系统开发中,数据分发服务(DDS)因其高效的实时通信能力被广泛应用于工业物联网、自动驾驶等领域。作为DDS规范的实现之一,FastDDS凭借其出色的性能和灵活性赢得了开发者青睐。本…...
Qwen3.5-9B图文对话实战:工业设备铭牌识别+参数查询+维保周期提醒
Qwen3.5-9B图文对话实战:工业设备铭牌识别参数查询维保周期提醒 1. 项目概述 Qwen3.5-9B是一款拥有90亿参数的开源大语言模型,特别适合工业场景下的图文对话应用。这个项目展示了如何利用其多模态能力,实现工业设备铭牌识别、参数查询和维保…...
GME-Qwen2-VL-2B-Instruct代码实例:自定义指令前缀‘Find an image that matches...’注入方法
GME-Qwen2-VL-2B-Instruct代码实例:自定义指令前缀‘Find an image that matches...’注入方法 1. 项目背景与价值 在实际的图文匹配场景中,我们经常需要判断一张图片与多个文本描述之间的匹配程度。GME-Qwen2-VL-2B-Instruct作为一个强大的多模态模型…...
Llama-3.2V-11B-cot实战:基于SpringBoot构建企业级智能客服原型
Llama-3.2V-11B-cot实战:基于SpringBoot构建企业级智能客服原型 最近在帮一个朋友的公司做技术选型,他们想快速搭建一个智能客服原型,既要成本可控,又要能快速集成到现有的Java技术栈里。聊了一圈,发现很多团队都卡在…...
SEO_SEO优化常见误区及正确操作指南
SEO优化常见误区 在互联网时代,SEO(搜索引擎优化)已成为网站运营中不可或缺的一部分。很多人在实际操作中却常常犯下一些常见的SEO优化误区,这不仅影响了网站的流量,也可能导致搜索引擎的惩罚。下面我们将详细分析这些…...
从智能门铃到工业质检:拆解5个嵌入式AI落地案例,看模型压缩和硬件选型怎么选
从智能门铃到工业质检:5个嵌入式AI实战案例与选型策略 智能门铃的摄像头突然捕捉到一张陌生面孔,300毫秒内完成本地人脸比对并推送到主人手机——这背后是嵌入式AI在消费电子领域的典型应用。当算法工程师面对瑞芯微RK3588和地平线旭日X3两颗芯片的选型表…...
VISA 标准深度剖析:寄存器基控制规范与函数接口研究
VISA 标准深度剖析:寄存器基控制规范与函数接口研究 VISA(Virtual Instrument Software Architecture)是仪器控制领域的标准 API,它为不同总线(GPIB、USB、LAN、PXI 等)提供了统一的编程接口。本文将 VISA 函数按功能分为 8 大类,并逐一解析其作用、核心函数及使用场景…...
3PEAK思瑞浦 TPT1051V-SO1R SOP8 CAN收发器
特性 符合IS011898标准支持CAN FD和最高达5 Mbps的数据速率典型环路延迟:110纳秒5V电源供应,3.0V~5.5VI0接口接收器共模输入电压:士30V总线故障保护:42VCAN网络最多支持110个节点结温范围从-40C到150C闩锁性能超过500mA总线引脚ESD保护:-8kV人体模型 -1.5kV充电设备…...
Phi-3-mini-4k-instruct-gguf实操手册:中文短文本生成场景下的温度调优策略
Phi-3-mini-4k-instruct-gguf实操手册:中文短文本生成场景下的温度调优策略 1. 模型概述与使用场景 Phi-3-mini-4k-instruct-gguf 是微软推出的轻量级文本生成模型,特别适合处理中文短文本任务。这个经过优化的GGUF版本模型,在问答、文本改…...
忍者像素绘卷参数详解:描绘步数/幻化精度/画幅比例三维度效果对照表
忍者像素绘卷参数详解:描绘步数/幻化精度/画幅比例三维度效果对照表 1. 像素艺术创作新纪元 忍者像素绘卷是基于Z-Image-Turbo深度优化的图像生成工作站,它将传统漫画创作与16-Bit复古游戏美学完美融合。这款工具采用明亮的"云端"视觉设计&a…...
