Linux设备驱动之多个同类设备共用一套驱动

news2024/11/17 7:24:22

1. 应用场景

比如我们的设备上有很多一样的usb接口,这些usb接口都需要有驱动才能工作,那么是每个usb都一套单独的驱动程序么?显然不是的,这些usb接口属于同一类设备,用户对他们的操作方法完全一致,只不过不是同一个设备,所以他们可以复用同一套驱动代码,在代码中去判断用户要操作哪个设备,然后去open/read/write这个设备。

2. 如何区分不同的设备

前面说过,每个设备都有一个唯一的标识符–设备号,那么对于同一类设备,它们的主设备号是一样的,次设备号是不一样的,用来区分它们,当用户想要操作哪个具体的设备,就会打开这个设备对应的设备文件(inode结构体),并自动在内核中创建对应的file结构体,这个file结构体中就保存了用户操作的所有信息,最终会传给我们的内核驱动,驱动再根据这个file结构体和inode结构体来判断用户具体要操作的哪个设备,然后去read/write这个具体的设备。

案例:

hanp@hanp:/dev/usb$ ls -lcrw------- 1 root root 180, 0  3月 11 17:29 hiddev0crw------- 1 root root 180, 1  3月 11 17:29 hiddev1

我的主机下面的两个usb设备,他们共用了一套usb驱动,但是他们的设备号是不一样的(180,0)和(180,1),主设备号都是180表示都属于同一类设备(usb设备),次设备号分别是0和1,表示这是两个不同的设备。

3. 代码实现

#include <linux/kernel.h>#include <linux/module.h>#include <linux/cdev.h>#include <linux/device.h>#include <linux/fs.h>#include <asm/uaccess.h>#define NUM_OF_DEVICES    2int major = 255;/* 两个设备,所以有两套结构体 *//* 设备0对应的设备结构体是hello_dev[0], 设备1对应的设备结构体是hello_dev[1] */struct hello_device {    dev_t devno;    struct cdev cdev;    char data[128];    char name[16];}hello_dev[NUM_OF_DEVICES];struct class * hello_class;const char DEVNAME[] = "hello_device";int hello_open(struct inode * ip, struct file * fp){    printk("%s : %d\n", __func__, __LINE__);        /* 获取用户打开的设备对应的设备结构体 hello_dev[0] 或者 hello_dev[1] */    struct hello_device * dev = container_of(ip->i_cdev, struct hello_device, cdev);        /* open的时候,通过container_of能够获取到用户要打开的那个设备的设备结构体,所有需要把这个结构体通过file指针的     * private_data参数传递给read/write */    fp->private_data = dev;        /* 一般用来做初始化设备的操作 */    /* ... */        return 0;}int hello_close(struct inode * ip, struct file * fp){    printk("%s : %d\n", __func__, __LINE__);        /* 一般用来做和open相反的操作,open申请资源,close释放资源 */    /* ... */        return 0;}ssize_t hello_read(struct file * fp, char __user * buf, size_t count, loff_t * loff){    int ret;        /* 通过file指针,获取到用户要操作的设备对应的设备结构体 */    struct hello_device * dev = fp->private_data;        /* 将用户需要的数据从内核空间copy到用户空间(buf) */    printk("%s : %d\n", __func__, __LINE__);    if (count <=0 || count > 128)        count = 128;    if ((ret = copy_to_user(buf, dev->data, count)))    {        printk("copy_to_user err\n");        return -1;    }        return count;}ssize_t hello_write(struct file * fp, const char __user * buf, size_t count, loff_t * loff){    int ret;    struct hello_device * dev = fp->private_data;        /* 将用户需要的数据从内核空间copy到用户空间(buf) */    printk("%s : %d\n", __func__, __LINE__);    if (count <=0 || count > 128)        count = 128;    if ((ret = copy_from_user(dev->data, buf, count)))    {        printk("copy_from_user err\n");        return -1;    }        return count;}/* 2. 分配file_operations结构体 */struct file_operations hello_fops = {    .owner = THIS_MODULE,    .open  = hello_open,    .release = hello_close,    .read = hello_read,    .write = hello_write};struct cdev cdev;static int hello_init(void){    int i;    printk("%s : %d\n", __func__, __LINE__);        /* 1. 生成并注册两个设备的设备号 */    /* 3. 分配、设置、注册两套cdev结构体 */    for (i = 0; i < NUM_OF_DEVICES; i++)    {        hello_dev[i].devno = MKDEV(major, i);        sprintf(hello_dev[i].name, "%s%d", DEVNAME, i);        register_chrdev_region(hello_dev[i].devno, 1, hello_dev[i].name);                hello_dev[i].cdev.owner = THIS_MODULE;        cdev_add(&hello_dev[i].cdev, hello_dev[i].devno, 1);        cdev_init(&hello_dev[i].cdev, &hello_fops);                /* 初始化两个设备各自的存储空间 */        sprintf(hello_dev[i].data, "Hi, I am hello device %d", i);    }        /* 在/sys/class目录下创建hello类,并在这个类下面创建hello_device0和hello_device1 */    hello_class = class_create(THIS_MODULE, DEVNAME);    for (i = 0; i < NUM_OF_DEVICES; i++)    {        device_create(hello_class, NULL, hello_dev[i].devno, NULL, "%s%d", DEVNAME, i);        printk("success!\n");    }    return 0;}static void hello_exit(void){    int i;    printk("%s : %d\n", __func__, __LINE__);        /* 释放资源 */    for (i = 0; i < NUM_OF_DEVICES; i++)    {        device_destroy(hello_class, hello_dev[i].devno);        cdev_del(&hello_dev[i].cdev);        unregister_chrdev_region(hello_dev[i].devno, 1);    }    class_destroy(hello_class);}MODULE_LICENSE("GPL");module_init(hello_init);module_exit(hello_exit);

解释:

container_of:/** * container_of - cast a member of a structure out to the containing structure * @ptr:        the pointer to the member. * @type:       the type of the container struct this is embedded in. * @member:     the name of the member within the struct. * */#define container_of(ptr, type, member)

功能:根据结构体中某个成员的地址,从而获取到整个结构体的首地址

@ptr: 已知结构体成员的地址

@type: 要获取的结构体的类型

@member: 已知结构体成员的名字

我们用到的实例解析:

struct hello_device * dev = container_of(ip->i_cdev, struct hello_device, cdev);

文章最后的图解和上篇文章中我讲到file结构体和inode结构体的关系,其中inode结构体和文件系统下的文件是一一对应的关系,里面保存了这个字符设备对应的cdev结构体:

struct cdev * i_cdev,而这个cdev结构体又包含在设备结构体hello_device中,其中hello_dev[0]中包含的是设备0的cdev,hello_dev[1]中包含的是设备1的cdev,那么

container_of函数就可以根据这个cdev来判断用户打开的是hello_dev[0]还是hello_dev[1]并获取到地址。

流程图解析

编译安装驱动:

sudo insmod hello.ko

hanp@hanp:/dev$ ls hello_device*hello_device0  hello_device1
hanp@hanp:/dev$ cat /proc/devices | grep hello255 hello_device0255 hello_device1

可以看到在/proc/devices下注册了两个设备hello_device0和hello_device1,这两个设备的主设备一样都是255,但是次设备号不一样(cat /dev/hello_deviceX可以查看次设备号)。

4. 写应用程序进行测试 app.c

#include <stdio.h>#include <string.h>#include <unistd.h>#include <sys/stat.h>#include <sys/types.h>#include <fcntl.h>int main(char argc, char * argv[]){    int fd;    int ret;    char buf[64];        if (argc != 2)    {        printf("Usage: %s <filename>\n", argv[0]);        return -1;    }        fd = open(argv[1], O_RDWR);    if (fd < 0)    {        perror("fail to open file");        return -1;    }        /* read data */    ret = read(fd, buf, sizeof(buf));    if (ret < 0)    {        printf("read err!");        return -1;    }    printf("buf = %s\n", buf);        /* write data */    strcpy(buf, "write data from app!");    ret = write(fd, buf, sizeof(buf));    if (ret < 0)    {        printf("write err!");        return -1;    }    read(fd, buf, sizeof(buf));    printf("buf = %s\n", buf);        close(fd);    return 0;}

测试:

$ gcc app.c$ sudo ./a.out /dev/hello_device0buf = Hi, I am hello device 0buf = write data from app!$ sudo ./a.out /dev/hello_device1buf = Hi, I am hello device 1buf = write data from app!

 

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

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

相关文章

连接器信号完整性仿真教程 七

本将介绍微带线及差分微带线仿真。做连接器信号完整性仿真时&#xff0c;有时后没法将激励端口直接设置到连接器端子上&#xff0c;这就需画出连接器PCB PAD&#xff0c;将激励端口设置在PAD的端面上&#xff0c;或者用引线连接PAD&#xff0c;将引线引出到适当的位置&#xff…

Window基础命令

文章目录 查看哪些端口被禁用TCP协议删除开机启动项方案1方案2 查看哪些端口被禁用TCP协议 netsh interface ipv4 show excludedportrange protocoltcp删除开机启动项 方案1 列出所有启动项 bcdedit /enum仔细看你要删除的是哪一项&#xff08;看description&#xff09;&a…

2. 两数相加(中等系列)

给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外&#xff0c;这两个数都不会以 0 …

Django基础6——数据模型关系

文章目录 一、基本了解二、一对一关系三、一对多关系3.1 增删改查3.2 案例&#xff1a;应用详情页3.2 案例&#xff1a;新建应用页 四、多对多关系4.1 增删改查4.2 案例&#xff1a;应用详情页4.3 案例&#xff1a;部署应用页 一、基本了解 常见数据模型关系&#xff1a; 一对一…

RabbitMQ+springboot用延迟插件实现延迟消息的发送

延迟队列&#xff1a;其实就是死信队列中消息过期的特殊情况 延迟队列应用场景&#xff1a; 可以用死信队列来实现&#xff0c;不过死信队列要等上一个消息消费成功&#xff0c;才会进行下一个消息的消费&#xff0c;这时候就需要用到延迟插件了&#xff0c;不过要线在docker上…

HSP高度敏感的人应该了解的内容(附图书资源)

推荐图书:《高敏感是种天赋》 在爆炸的信息海中希望你慢慢看&#xff0c;细细品~ 什么是高敏感人群&#xff1f; &#xff08;Highly Sensitive Person&#xff0c;简称HSP&#xff09;是指那些对外界刺激和情绪变化非常敏感&#xff0c;容易受到情绪的影响&#xff0c;并且需…

在window上配置NASM

NASM是支持x86、x64架构CPU的汇编器(汇编软件)&#xff1b;NASM也支持大量的文件格式&#xff0c;包括Linux&#xff0c;*BSD&#xff0c;a.out&#xff0c;ELF&#xff0c;COFF&#xff0c;Mach−O&#xff0c;Microsoft 16−bit OBJ&#xff0c;Win32以及Win64&#xff0c;同…

今天去看看俺姐(老婆)新开的超市

首发博客地址 https://blog.zysicyj.top/ 1 昨晚写博客到12点多&#xff0c;今天困死了&#xff0c;比较意外的是&#xff0c;早上老爸没有叫我&#xff0c;今天早上是老爸和小舅送的葡萄。 所以呢&#xff0c;今早睡得很晚&#xff0c;然后6点多才醒&#xff0c;睡得真舒服&am…

通过参数化可变形曲线直接从 X 射线投影数据计算分割研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

机器学习——KNN算法

1、&#xff1a;前提知识 KNN算法是机器学习算法中用于分类或者回归的算法&#xff0c;KNN全称为K nearest neighbour&#xff08;又称为K-近邻算法&#xff09; 原理&#xff1a;K-近邻算法采用测量不同特征值之间的距离的方法进行分类。 优点&#xff1a;精度高 缺点&…

Facechain使用教程:3张照片就能生成个人写真,还完全免费

1.效果展示 下面4张图片&#xff0c;小伙伴们有没有看出来哪些是原图&#xff0c;哪些是AI生成的呢&#xff1f; 上面的图片第1张是原图&#xff0c;其他的都是AI生成的哦~ 今天来教大家怎么用facechain训练自己的人物写真模型&#xff0c;然后就可以尝试各种风格的照片了。 …

Modbus转Profinet网关连接三菱变频器博图快速配置

本案例将分享如何使用兴达易控的modbus转profinet网关&#xff08;XD-MDPN100&#xff09;来连接西门子1200系列plc&#xff0c;并实现三菱变频器的485通讯兼容转modbusTCP通信。通过在博图中进行配置&#xff0c;我们可以实现设备之间的连接和通信。 首先&#xff0c;我们需要…

8.6.tensorRT高级(3)封装系列-终极封装形态,以及考虑的问题

目录 前言1. 终极封装总结 前言 杜老师推出的 tensorRT从零起步高性能部署 课程&#xff0c;之前有看过一遍&#xff0c;但是没有做笔记&#xff0c;很多东西也忘了。这次重新撸一遍&#xff0c;顺便记记笔记。 本次课程学习 tensorRT 高级-终极封装形态&#xff0c;以及考虑的…

Python序列类型

序列&#xff08;Sequence&#xff09;是有顺序的数据列&#xff0c;Python 有三种基本序列类型&#xff1a;list, tuple 和 range 对象&#xff0c;序列&#xff08;Sequence&#xff09;是有顺序的数据列&#xff0c;二进制数据&#xff08;bytes&#xff09; 和 文本字符串&…

webassembly009 transformers.js 网页端侧推理

之前试用过两个网页端的神经网络框架&#xff0c;一个是 Tensorflow PlayGround&#xff0c;它相当与实现了一个网页端的简单的训练框架&#xff0c;有关节点的数据结构可看这篇。另一个是onnx的网页端(nodejs绿色免安装try onnx on web(chrome))&#xff0c;需要自己转换onnx模…

XSS攻击是怎么回事?记录一下

title: XSS攻击 date: 2023-08-27 19:15:57 tags: [XSS, 网络安全] categories: 网络安全 今天学习了一个网络攻击的手段&#xff0c;XSS攻击技术&#xff0c;大家自建网站的朋友&#xff0c;记得看看是否有此漏洞。 &#x1f388; XSS 攻击 全称跨站脚本攻击 Cross Site Sc…

Spring Boot中通过maven进行多环境配置

上文 java Spring Boot将不同配置拆分入不同文件管理 中 我们说到了&#xff0c;多环境的多文件区分管理 说到多环境 其实不止我们 Spring Boot有 很多的东西都有 那么 这就有一个问题 如果 spring 和 maven 都配置了环境 而且他们配的不一样 那么 会用谁的呢&#xff1f; 此…

2023年最新版IDEA安装(超详细)

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【JavaSE_primary】 写在前面&#xff0c;IDEA的安装是建立在JDK安装好了的前提下&#xff0c;否则IDEA是无法使用的&#xff0c;具体JDK…

教你如何美化自己的Typora

美化你的Typora 前提 很多朋友习惯使用Typora打字或电子笔记&#xff0c;虽然市面上有很多Markdown工具&#xff0c;但是我尤爱Typora。 虽然它没有云存储不方便多设备同步&#xff0c;本地管理也不是很强大&#xff1b;可它简约的md语法和窗口界面&#xff0c;让我能够沉浸在…

Vue3(开发h5适配)

在开发移动端的时候需要适配各种机型&#xff0c;有大的&#xff0c;有小的&#xff0c;我们需要一套代码&#xff0c;在不同的分辨率适应各种机型。 因此我们需要设置meta标签 <meta name"viewport" content"widthdevice-width, initial-scale1.0">…