【Linux】驱动_2_字符驱动

1. Linux设备分类

  • 字符设备: 指应用程序按字节/字符来读写数据的设备。通常为传真、虚拟终端和串口调制解调器、键盘之类设备提供流通信服务,通常不支持随机存取数据。字符设备在实现时大多不使用缓存器。系统直接从设备读/写每一个字符。
  • 块设备: 通常支持随机存取和寻址,并使用缓存器。操作系统为输入输出分配了缓存以存储一块数据。当程序向设备发送读或写数据的请求时,系统把数据中的每一个字符存储在适当的缓存中。当缓存填满时,会采取适当的操作(把数据传走),而后系统清空缓存。与字符设备不同的是,是否支持随机存储。字符型是流形式,逐一存储。典型的块设备有硬盘、 SD卡、闪存等,应用程序可以寻址磁盘上的任何位置,并由此读取数据。此外,数据的读写只能以块的倍数进行。
  • 网络设备: 是一种特殊设备,它并不存在于/dev 下面,主要用于网络数据的收发。

Linux 内核中处处体现面向对象的设计思想,为了统一形形色色的设备, Linux 系统将设备抽象为 struct cdev, struct block_device,struct net_devce 三个对象,具体的设备都可以包含着三种对象从而继承和三种对象属性和操作,并通过各自的对象添加到相应的驱动模型中,从而进行统一的管理和操作

2. 字符设备抽象

Linux 内核将字符设备抽象成一个数据结构 (struct cdev), 字符设备对象cdev 记录了字符设备的相关信息(设备号、内核对象),字符设备的打开、读写、关闭等操作接口(file_operations),在我们想要添加一个字符设备时,就是将这个对象注册到内核中,通过创建一个文件(设备节点)绑定对象的 cdev,当我们对这个文件进行读写操作时,就可以通过虚拟文件系统,在内核中找到这个对象及其操作接口,从而控制设备。
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 硬件层,通过查看硬件的原理图、芯片的数据手册,确定底层需要配置的寄存器,这类似于裸机开发。将对底层寄存器的配置,读写操作放在文件操作接口里面,也就是实现file_operations 结构体。
  • 驱动层,将文件操作接口注册到内核,内核通过内部散列表来登记记录主次设备号。在文件系统层,新建一个文件绑定该文件操作接口,应用程序通过操作指定文件的文件操作接口来设置底层寄存器

3. 相关概念及数据结构

linux 中使用设备编号来表示设备,主设备号区分设备类别,次设备号标识具体的设备。cdev 结构体被内核用来记录设备号,在使用设备时通常会打开设备节点,通过设备节点的 inode 结构体、 file 结构体最终找到 file_operations 结构体,并从 file_operations 结构体中得到操作设备的具体方法

3.1 设备号

对于字符的访问是通过文件系统的名称进行的,这些名称被称为特殊文件、设备文件, Linux 根目录下有/dev 这个文件夹,专门用来存放设备中的驱动程序,使用 ls -l /dev以列表的形式列出系统中的所有设备。其中,每一行表示一个设备,每一行的第一个字符表示设备的类型。
如图:’ c’用来标识字符设备,’ b’用来标识块设备。如 autofs 是一个字符设备 c, 它的主设备号是 10,次设备号是 235; loop0 是一个块设备,它的主设备号是 7,次所备案为 0,同时可以看到 loop0-loop3 共用一个主设备号,次设备号由 0 开始递增 ,一般来说,主设备号指向设备的驱动程序,次设备号指向某个具体的设备。如图, I2C-0, I2C-1属于不同设备但是共用一套驱动程序

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 内核中设备编号的含义

    typedef u32 __kernel_dev_t;
    typedef __kernel_dev_t dev_t;		/* 表示设备号 */
    
    #define MINORBITS 20
    #define MINORMASK ((1U << MINORBITS) - 1)
    #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
    #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
    #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))/* 将主设备号和次设备号合成一个设备号 */
    

    dev_t 是一个 32 位的数表示设备编号,高12位表示主设备号,低20位表示次设备号。理论上主设备号取值范围: 0-2^12,次设备号 0-2^20。实际上在内核源码中 __register_chrdev_region(… ) 函数中, major 被限定在 0-CHRDEV_MAJOR_MAX,CHRDEV_MAJOR_MAX 是一个宏值是 512。在 kdev_t 中,设备编号通过移位操作最终得到主/次设备号码,同样主/次设备号也可以通过位运算变成 dev_t 类型的设备编号,具体实现参看上面代码 MAJOR(dev)、 MINOR(dev) 和 MKDEV(ma,mi)。

  • cdev 结构体

    struct cdev {
        struct kobject kobj;	/* 内嵌的内核对象,将设备统一加入到“Linux 设备驱动模型”中管理 */
        struct module *owner;	/* 字符设备驱动程序所在的内核模块对象的指针。 */
        const struct file_operations *ops;	/* 文件操作,ops在应用程序通过文件系统(VFS)呼叫到设备设备驱动程序中实现的文件操作类函数过程中起桥梁纽带作用, VFS 与文件系统及设备文件之间的接口是 file_operations 结构体成员函数,这个结构体包含了对文件进行打开、关闭、读写、控制等一系列成员函数。 */
        struct list_head list;	/* 用于将系统中的字符设备形成链表 */
        dev_t dev;				/* 字符设备的设备号,有主设备和次设备号构成 */
        unsigned int count;		/* 属于同一主设备号的次设备号的个数,表示设备驱动程序控制的实际同类设备的数量。 */
    };
    

    内核通过一个散列表 (哈希表) 记录设备编号。哈希表由数组和链表组成。以主设备号为 cdev_map 编号,使用哈希函数 f(major)=major%255 来计算组数下标 (使用哈希函数是为了链表节点尽量平均分布在各个数组元素中,提高查询效率);主设备号冲突, 则以次设备号为比较值来排序链表节点。如下图所示,内核用 struct cdev 结构体来描述一个字符设备,并通过struct kobj_map 类型的散列表 cdev_map 来管理当前系统中的所有字符设备。

    内核中确实有一个 chrdevs 数组 :

    static struct char_device_struct {
        struct char_device_struct *next;
        unsigned int major;
        unsigned int baseminor;
        int minorct;
        char name[64];
        struct cdev *cdev; /* will die */
    } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
    

    访问它的时候,并不是直接使用主设备号 major 来确定数组项,而是使用如下函数来确定数组项:

    /* index in the above */
    static inline int major_to_index(unsigned major)
    {
    	return major % CHRDEV_MAJOR_HASH_SIZE;
    }
    

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • file_operations 结构体

    file_operation是关联系统调用和驱动程序的关键数据结构。结构的每一个成员都对应一个系统调用。读取 file_operation 中相应的函数指针,接着把控制权转交给函数指针指向的函数,从而完成了 Linux 设备驱动程序的工作。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    提到 read 和 write 函数时,需要使用 opy_to_user以及copy_from_user来进行数据访问,写入/读取成功函数返回 0,失败则会返回未被拷贝的字节数。

    static inline long copy_from_user(void *to, const void __user *from,unsigned long n)
    static inline long copy_to_user(void __user *to, const void *from, unsigned long n)
    
  • file结构体

    内核中用 file 结构体来表示每个打开的文件,每打开一个文件,内核会创建一个结构体,并将对该文件上的操作函数传递给该结构体的成员变量 f_op,当文件所有实例被关闭后,内核会释放这个结构体。

    struct file {
    	const struct file_operations *f_op;
        /* needed for tty driver, and maybe others */
    	void *private_data;
    };
    

4. 框架

  1. 确定主设备号,也可以让内核分配
  2. 定义自己的 file_operations 结构体
  3. 实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体
  4. 把 file_operations 结构体告诉内核: register_chrdev
  5. 得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
  6. 有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev
  7. 其他完善:提供设备信息,自动创建设备节点: class_create,device_create

5. open 函数到底做了什么

使用设备前通需要调用 open 函数,用于设备专有数据的初始化,申请相关资源及进行设备的初始化等工作,对简单的设备而言,open函数可不做具体的工作,在应用层通过系统调用 open 打开设备时,打开正常会得到该设备的文件描述符,之后可以通过该描述符对设备进行 read 和 write 等操作;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用 open() 系统调用函数打开一个字符设备时 (int fd = open(“dev/xxx” , O_RDWR)) 大致有以下过程:

  • 在虚拟文件系统 VFS 中的查找对应与字符设备对应 struct inode 节点
  • 遍历散列表 cdev_map,根据 inod 节点中的 cdev_t 设备号找到 cdev 对象
  • 创建struct file对象(系统采用一个数组管理一个进程中多个被打开的设备,每个文件描述符作为数组下标标识了一个设备对象)
    • 初始化 struct file 对象,将 struct file 对象中的 file_operations 成员指向 struct cdev 对象中的
    file_operations 成员(file->fops = cdev->fops)
    • 回调 file->fops->open 函数

6. 补充知识

6.1 module_init/module_exit 的实现

驱动刚需的入口函数,出口函数如下

module_init(hello_init);
module_exit(hello_exit);

驱动程序可被编进内核,也可被编译为ko文件后手工加载。 对于两种形式,“ module_init/module_exit” 宏是不一样的。 在内核文件“ include\linux\module.h”中可以看到这 2 个宏:

#ifndef MODULE
    #define module_init(x) __initcall(x);
    #define module_exit(x) __exitcall(x);
#else /* MODULE */
    #define module_init(initfn) \
        /* Each module must use one module_init(). */
        static inline initcall_t __inittest(void) \
        { return initfn; } \
        int init_module(void) __attribute__((alias(#initfn)));
    /* This is only required if you want to be unloadable. */
    #define module_exit(exitfn) \
        static inline exitcall_t __exittest(void) \
        { return exitfn; } \
        void cleanup_module(void) __attribute__((alias(#exitfn)));
#endif

编译驱动程序时,我们执行“make modules”这样的命令,它在编译c文件时会定义宏 MODULE,在编译内核时,并不会定义宏 MODULE。所以, “ module_init/module_exit”这 2 个宏在驱动程序被编进内核时,如上面代码中第 3、 4 行那样定义;在驱动程序被编译为 ko 文件时,如上面代码中第 11~19 行那样定义。

6.2 register_chrdev 的内部实现
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
{
	return __register_chrdev(major, 0, 256, name, fops);
}
int __register_chrdev(unsigned int major, unsigned int baseminor, unsigned int count, const char *name, const struct file_operations *fops)
{
    struct char_device_struct *cd;
    struct cdev *cdev;
    int err = -ENOMEM;
    cd = __register_chrdev_region(major, baseminor, count, name);
    cdev = cdev_alloc();
    cdev->owner = fops->owner;
    cdev->ops = fops;
    kobject_set_name(&cdev->kobj, "%s", name);
    err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
}

调用__register_chrdev_region 函数来“注册字符设备的区域”,它仅仅是查看设备号(major, baseminor)到(major, baseminor+count-1)有没有被占用,如果未被占用的话,就使用这块区域。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Jenkins 打包报错记录 error: index-pack died of signal 15

问题背景&#xff0c;打包每次到92%时就会报错&#xff0c;试了好几次都是同样的错误 14:56:53 fatal: index-pack failed 14:56:53 14:56:53 at org.jenkinsci.plugins.gitclient.CliGitAPIImpl.launchCommandIn(CliGitAPIImpl.java:2734) 14:56:53 at org.jenkinsci.plugi…

GoLand远程开发IDE:使用SSH远程连接服务器进行云端编程

目录 ⛳️推荐 1. 安装配置GoLand 2. 服务器开启SSH服务 3. GoLand本地服务器远程连接测试 4. 安装cpolar内网穿透远程访问服务器端 4.1 服务器端安装cpolar 4.2 创建远程连接公网地址 5. 使用固定TCP地址远程开发 ⛳️推荐 前些天发现了一个巨牛的人工智能学习网站&am…

【复现】金和OA-jc6 RCE漏洞_74

目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一&#xff1a; 四.修复建议&#xff1a; 五. 搜索语法&#xff1a; 六.免责声明 一.概述 金和C6协同管理平台包括协同办公管理,人力资源管理,项目管理,客户关系管理,企业目标管理,费用管理,移动办公,微信办公等多个业务范…

Java——内存溢出如何排查

1、模拟内存移除场景 public class OOMTest {public static void main(String[] args) {List<byte[]> memoryLeakArray new ArrayList<>();for (int i 0; i<1024; i){byte[] bytes new byte[1024 * 1024];memoryLeakArray.add(bytes);}} }初始化启动参数最大…

小心!那个走了的员工可能带走了公司的秘密

数据泄露是企业安全的一大隐患&#xff0c;尤其是离职员工带走公司数据的问题&#xff0c;这是一种常被忽视的内部威胁。离职员工可能因为种种原因&#xff0c;带走了他们曾经可以访问的公司数据。而这些数据如果落入了不当的地方&#xff0c;可能会给企业带来严重的损害。那么…

力扣数据库题库学习(4.24日)

1068. 产品销售分析 I 问题链接 思路分析 编写解决方案&#xff0c;以获取 Sales 表中所有 sale_id 对应的 product_name 以及该产品的所有 year 和 price 。返回结果表 无顺序要求 。 这个问题很简单&#xff0c;查询两张表内的指定字段。这个考的其实就是数据库的连接&am…

23种设计模式(Java版,超详细!)

文章目录 一、什么是设计模式二、设计模式的分类三、设计模式的基本要素四、23种设计模式概览五、设计模式间的关系六、设计模式详解6.1. 工厂方法模式&#xff08;Factory Method&#xff09;6.2. 抽象工厂模式&#xff08;Abstract Factory&#xff09;6.3. 建造者模式&#…

屏幕状态自动检测+鼠标自动操作

目录 一、写在前面 1.1适用场景 1.2涉及到的库 二、函数库 2.1pyautogui-屏幕截图&鼠标操作 2.1.1屏幕截图screenshot函数 2.1.2鼠标移动及单击 2.2Opencv-模板匹配 2.2.1matchTemplate函数 2.2.2minMaxLoc函数 2.2.3相关代码 2.3base64-图片转base64 2.3.1在线…

【行为型模式】模板方法模式

一、模板方法模式概述 模板方法模式定义&#xff1a;在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。(类对象型模式) 模板方法中的基本方法是实现算法的各个步骤&#xff0c;是模板方法的…

谷歌搜索SEO优化需要做什么?

最基本的要求&#xff0c;网站基础要优化好&#xff0c;让你的网站更加友好地服务于用户和搜索引擎&#xff0c;首先你要保证你的网站也适配手机端&#xff0c;现在手机端&#xff0c;如果你的网站在手机上打开慢&#xff0c;或者没有适配手机端&#xff0c;让用户用手机看着电…

Echarts X轴类目名太长时隐藏显示全部

echarts图表X轴 在柱状图中,X轴类目名如果数据太长; echarts会默认进行隐藏部分字段; 如果我们想让每一个类目名都显示出来,需要进行额外的处理X轴类目名太长时,默认只显示一部分类目名 <!DOCTYPE html> <html lang="en"> <head><meta ch…

硬实力!神工坊团队在首届开放原子开源大赛中斩获一二等奖

日前&#xff0c;首届开放原子开源大赛苏州站在苏州工业园区顺利开赛&#xff0c;神工坊团队在“大规模非对称不定带宽线性代数方程组求解算法赛”中表现非凡&#xff0c;斩获一二等奖&#xff01; “大规模非对称不定带宽线性代数方程组求解算法赛”是“开放原子开源大赛”工业…

画机柜布置图就这么简单,你学会了吗?

你还在使用excel画机柜布置图&#xff1f; 你还在使用CAD画机柜布置图&#xff1f; 你还在使用Visio画机柜布置图&#xff1f; 我们今天都在使用nVisual画机柜布置图&#xff01; 第一步&#xff1a;登录注册cloud.nVisual.com云平台&#xff0c;免费使用Visual&#xff1b; 第…

C语言趣味代码(三)

这一篇主要围绕写一个程序---寻找数字 来写&#xff0c;在这篇我会详细和大家介绍基本实现以及它的改良版&#xff0c;还有相关知识的拓展&#xff0c;干货绝对满满。 1. 寻找数字 在这一主题下&#xff0c;我们会编写一些代码&#xff0c;来锻炼玩家的反应力&#xff0c;同时…

第6章 Mybatis高级查询(详解篇)

@[TOC](第6章 Mybatis高级查询(详解篇)) 1. 一对一映射 1.1 自动映射(关联的嵌套结果映射) <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd…

【总结】hbase master重启恢复失败问题修复

问题现象 最近hbase master 莫名其妙宕机了&#xff0c;查看最后输出日志&#xff0c;也没有发现有效信息。 于是想着先重启一把&#xff0c;在hbase master 选主成active状态的过程中&#xff0c;发现重启多次都很漫长&#xff0c;且最终因重启时间过长&#xff0c;被hbase-…

【NMPA-国家药品监督管理局】

NMPA-国家药品监督管理局 ■ NMPA简介■ (1) 监管逻辑■ 1.1&#xff09;注册检验■ 1.2&#xff09;临床试验■ 1.3&#xff09;体系考核■ 1.4&#xff09;专家评审■ 1.5&#xff09;飞行检查 ■ (2) 上市流程■ 2.1&#xff09;注册申请&#xff1a;■ 2.2&#xff09;注册…

水滴式粉碎机:高效、精细破碎利器

水滴式粉碎机是一种适用于多种物料的粉碎设备。它能够处理硬质物料如石头、陶瓷、玻璃等&#xff0c;也能粉碎食品和饲料原料如大米、小麦、玉米等。此外&#xff0c;水滴式粉碎机还适用于秸秆、木材等物料的粉碎。部分水滴式粉碎机还具备粗粉碎和细粉碎两种功能&#xff0c;可…

重发布实验:

要求&#xff1a; 配置&#xff1a; 配置IP地址&#xff1a; Ar1&#xff1a; [a1]int g 0/0/0 [a1-GigabitEthernet0/0/0]ip add 100.1.1.1 24 [a1-GigabitEthernet0/0/0]int l 0 [a1-LoopBack0]ip add 192.168.0.1 32 [a1-LoopBack0]int l1 [a1-LoopBack1]ip add 192…

k8s 报错:x509: certificate has expired or is not yet valid

程序员的公众号:源1024,获取更多资料,无加密无套路! 最近整理了一份大厂面试资料《史上最全大厂面试题》,Springboot、微服务、算法、数据结构、Zookeeper、Mybatis、Dubbo、linux、Kafka、Elasticsearch、数据库等等 获取方式: 关注公众号并回复 666 领取,更多内容持续奉…