嵌入式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 1
static 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 1
static 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");
} else
printf("can open\n");
return 0;
}
makefile:
KERN_DIR = /usr/src/linux-headers-4.8.0-36-generic
all:
make -C $(KERN_DIR) M=`pwd` modules
gcc -o led_test led_test.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
rm -f led_nothing
obj-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 2
static 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个,题…...

《爆肝整理》保姆级系列教程python接口自动化(二十)--token登录(详解)
简介 为了验证用户登录情况以及减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。有些登录不是用 cookie 来验证的,是用 token 参数来判断是否登录。token 传参有两种一种是放在请求头里,本质上是跟 cookie 是一样的&…...

k8s种的kubectl命令
一.kubectl基本命令1.1 称述式资源管理的方法kubernetes集群管理集群资源的唯一入口是通过相应的方法调用apiserver的接口kubectl是官方的CLI命令行工具,用于与apiserver进行通信,将用户在命令行输入的命令,组织并转化为apiserver能识别的信息…...

数组(一)-- LeetCode[26][80] 删除有序数组中的重复元素
1 删除有序数组中的重复项 1.1 题目描述 给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。 由于在某些语言中不能改变数组的长度,…...

GEE学习笔记 六十三:新的地图图层ui.Map.CloudStorageLayer
在GEE中导出数据有一种方式是直接导出地图到Google Cloud Storage中,也就是Export.map.toCloudStorage(xxx),这种方式是将我们计算生成影像导出成为静态瓦片的格式存放在Google Cloud Storage中。我们可以在其他的前端程序比如OpenLayer、Mapbox GL JS等…...
ClickHouse 语法详解
ClickHouse有2类解析器:完整SQL解析器(递归式解析器),以及数据格式解析器(快速流式解析器) 除了 INSERT 查询,其它情况下仅使用完整SQL解析器。 INSERT查询会同时使用2种解析器:INSE…...

手把手教你将微信小程序放到git上
背景 首先,要创建一个自己的git仓库,这里默认大家都能够自己创建了git仓库了。如果不会创建仓库的话,百度一下,很容易就能够创建了!(后续,如有不知道在哪里,怎么创建仓库的话&#…...
功能测试3年,回顾一路走来的艰辛
不论你是什么时候开始接触测试这个行业的,你首先听说的应该是功能测试。通过一些测试手段来验证开发做出的代码是否符合产品的需求?当然你也有自己对功能测试的理解,但是最近两年感觉功能测试好像不太受欢迎,同时不少同学真的是功…...

作为Linux C/C++程序员必备的工具
Linux系统 可以选择centOS或者ubautu server(不建议选择桌面版本的)。不建议裸机安装,玩坏了就特别麻烦。不建议使用有桌面版本的ubautu,在一定程度有桌面的版本的会消耗性能。 如果经济实力允许,可以购买云服务器。 参考文章: Ubuntu server…...
docker Alpine一个只有5M小而美的Docker镜像
docker Alpine一个只有5M小而美的Docker镜像 参考链接: Alpine 一个只有5M的Docker镜像 http://www.infoq.com/cn/news/2016/01/Alpine-Linux-5M-Docker?utm_sourcetuicool&utm_mediumreferral 使用alpinelinux 构建 golang http 启动了才15mb http://blog.csdn.net/fre…...

Springboot扩展点之InstantiationAwareBeanPostProcessor
Springboot扩展点系列实现方式、工作原理集合:Springboot扩展点之ApplicationContextInitializerSpringboot扩展点之BeanFactoryPostProcessorSpringboot扩展点之BeanDefinitionRegistryPostProcessorSpringboot扩展点之BeanPostProcessorSpringboot扩展点之Instant…...