嵌入式linux驱动学习-用cdev代替register_chrdev()

news2025/7/19 11:21:55

​上回说到字符设备驱动程序的注册与销毁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");  } else    printf("can open\n");  return 0;}

makefile:

KERN_DIR = /usr/src/linux-headers-4.8.0-36-genericall:  make -C $(KERN_DIR) M=`pwd` modules  gcc -o led_test led_test.cclean:  make -C $(KERN_DIR) M=`pwd` modules clean  rm -rf modules.order  rm -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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/361323.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Mysql系列】Mysql之ACID实现原理

ACID 原子性 事务不可分割&#xff0c;要么全部执行&#xff0c;要么都不执行。原理是使用undo log。undo log&#xff0c;当事务对数据库进行修改的时候&#xff0c;会生成对应的undo log。 持久性 事务提交后&#xff0c;对于数据库的改变是永久性的。实现原理通过redo l…

leaflet 清除底图以外的所有图层(两种方法)

第084个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet项目中清除除了底图以外的其他图层,这里有两种方法,详情请参考源代码。 直接复制下面的 vue+leaflet源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共140行)相关API专栏目标…

C++调用Python脚本进行18次循环操作后,脚本不执行

C调用Python脚本进行18次循环操作后&#xff0c;脚本不执行 现象&#xff1a; 发送端接收端 从第二张图中可以看出&#xff0c;python脚本卡在’[parkin_debug] 6’与’[parkin_debug] 7’之间 该测试经过多次反复测试&#xff0c;均在第18次循环执行时&#xff0c;出现上述问…

java TCP/UDP、Socket、URL网络编程详解

文章目录网络通信协议通信双方地址端口号IP地址InetAddress类Socket 网路编程Socket类的常用构造器Socket类的常用方法UDP协议什么是UDP协议UDP网络编程DatagramSocket 构造方法DatagramSocket 常用方法DatagramPacket常用方法实现步骤单向数据发收的UDP程序双向数据发收的UDP程…

社团结构的划分及实现过程

社团结构的划分及实现过程 022036930019 张志龙 2022.11.18 题目 什么是网络社团结构&#xff0c;介绍给出社团结构划分几种常见算法&#xff0c;并且给出你实现的过程。同时对一些真实网络进行划分与真实情况进行比较&#xff0c;并且给出你的解释。 文章目录社团结构的划分…

整个寒假挑灯夜读用学习压抑悲伤之情(寒假总结)

目录 前言 一、回顾这一个多月&#xff08;学习阶段&#xff09; 二、意外经历——青训营 三、下学期规划 四、其他 前言 这几年过年越来越没有年味了&#xff0c;所以对过年并没有多大的期待&#xff0c;当别人都在朋友圈发新年快乐的时候&#xff0c;我应该在原神过海灯…

华为OD机试 - 组成最大数(Java) | 机试题算法思路 【2023】

使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查看地址:https://blog.csdn.net/hihell/category_12201821.html 华为OD详细说明:https://dream.blog.csdn.net/article/details/128980730 组成最大数 小组中…

maven-surefire-plugin,用于自动化测试和单元测试的

如果你执行过mvn test或者执行其他maven命令时跑了测试用例&#xff0c;你就已经用过maven-surefire-plugin了。 maven-surefire-plugin是maven里执行测试用例的插件&#xff0c;不显示配置就会用默认配置。这个插件的surefire:test命令会默认绑定maven执行的test阶段。 2.ma…

同城小程序应该怎么做?

同城小程序应该怎么做?同城小程序开发&#xff0c;微信同城小程序&#xff0c;同城生活小程序&#xff0c;同城信息发布小程序#同城小程序开发#微信同城小程序#同城生活小程序#同城信息发布小程序百收网 同城信息发布的小程序怎么做&#xff1f; 实际上跟 58 同城类似的&…

SpringBoot整合Dubbo和Zookeeper

安装Zookeeper 下载地址&#xff1a;https://zookeeper.apache.org/releases.html#download 解压&#xff0c;然后运行bin目录里的zkService.cmd 将conf文件夹的zoo_sample.cfg复制一份改名为zoo.cfg 修改zoo.cfg配置&#xff0c;dataDir临时数据存储的位置&#xff0c;client…

5.11 BGP属性-Preferred-Value

5.4.5配置Preferred-Value属性控制选路 1. 实验目的 熟悉Preferred-Value属性控制选路的应用场景掌握Preferred-Value属性控制选路的配置方法2. 实验拓扑 实验拓扑如图5-11所示: 图5-11:配置Preferred-Value属性控制选路 3. 实验步骤…

Python|每日一练|数组|回溯|栈|树|双指针|单选记录:N 皇后|二叉树的前序遍历|四数之和

1、N 皇后&#xff08;数组&#xff0c;回溯&#xff09; n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#xff0c;返回所有不同的 n 皇后问题 的解决方案。 每一种解法包含一个不同的 n 皇后问题 …

如何选择靠谱的插画培训课程

如何选择靠谱的插画培训课程&#xff0c;今天教你3个维度选择一个靠谱的插画培训班&#xff01; 插画培训机构课程&#xff1a; 1.选择插画培训班时&#xff0c;要先考察课程&#xff0c;看看课程内容是否符合自己的需求&#xff0c;是否有助于提高插画技术。课程设置应该灵活…

GPT系列详解:初代GPT

本文详细解读了OpenAI公司在2018年6月发布的论文《Improving Language Understanding by Generative Pre-Training》&#xff0c;它其中介绍的算法也就是后来人们说的GPT。本文借鉴了李沐的这个视频&#xff0c;感兴趣的同学可以移步观看大神的讲解。 目录引言GPT方法无监督预训…

拒绝背锅:测试项目中的风险管理一定要知道

测试经理除了要管理产品线的质量保障和日常部门事务工作外&#xff0c;另一项比较重要的就是测试项目全流程的管理。 今天不聊整体的测试项目流程如何开展&#xff0c;而是想聊一聊在同行中比较高频出现的一个字眼&#xff1a;风险管理。 什么是风险管理 引用百度上的解释&a…

OSS上传(Java和Js)

OSS上传&#xff08;Java和Js&#xff09;准备工作创建RAM用户创建角色创建权限策略给角色授予权限策略获取临时访问凭证Java普通上传OSSJava分片上传OSSJS普通上传OSSJS分片上传OSS使用RAM用户或STS方式访问 由于阿里云账号AccessKey拥有所有API访问权限&#xff0c;建议遵循阿…

短视频配音的秘诀!你不会还在傻傻自己人声配音吧?

我们在刷短视频的时候&#xff0c;常常发现这些视频的声音很相似&#xff0c;难道都是同一个作者创作的吗&#xff1f; 其实&#xff0c;这都是他们通过AI智能配音技术&#xff0c;把文本转换为人工智能的声音&#xff0c;而非创作者本人的配音效果。 毕竟真人配音的效率太低…

Elasticsearch_分词器、搜索文档以及原生JAVA操作

文章目录一、ES分词器1、默认分词器2、IK分词器2.1 IK分词器安装及测试2.2 IK分词器词典3、拼音分词器4、自定义分词器二、搜索文档1、添加文档数据2、搜索方式3、ES搜索文档的过滤处理3.1 结果排序3.2 分页查询3.3 高亮查询3.4 SQL查询三、原生JAVA操作ES1、搭建项目2、索引操…

CIMCAl photo container detect is free, container damage detect

飞瞳引擎™小程序直接拍照识别集装箱/API接口二次开发集装箱信息识别/铅封号识别API免费顶尖AI集装箱箱号识别率99.98%以上&#xff0c;高泛化CIMCAIl ENGINE™ photo container detection is free&#xff0c;support API further development or WeChat applet,the recogniti…

Color correction for tone mapping

Abstract色调映射算法提供了复杂的方法&#xff0c;将真实世界的亮度范围映射到输出介质的亮度范围&#xff0c;但它们经常导致颜色外观的变化。在本研究中&#xff0c;我们进行了一系列的主观外观匹配实验&#xff0c;以测量对比度压缩和增强后图像色彩的变化。结果表明&#…