【Linux】驱动_2_字符驱动

news2024/11/24 17:57:01

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)有没有被占用,如果未被占用的话,就使用这块区域。

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

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

相关文章

【程序分享1】LAMMPS + OVITO + 晶体缺陷识别 + 点缺陷 + 分子动力学模拟

分享2个分子动力学模拟相关的程序。 1. 一种识别体心立方晶体缺陷的新方法。 2. 无后处理的分子动力学模拟中的并行点缺陷识别: lammps的计算和转储方式 。 感谢论文的原作者&#xff01; 第1个程序 关键词&#xff1a; 1. Atomistic simulations, 2. Molecular dynamics…

让客服工作开挂的8个客服办公高效率神器

做客服工作&#xff0c;经常需要写文案&#xff0c;做图片做视频&#xff0c;还要能快捷回复客户&#xff0c;都需要有靠谱的客服办公软件支持&#xff0c;本文介绍了8个高效神器&#xff0c;希望能帮到做客服的亲 前言 做客服工作&#xff0c;在回答客户咨询的同时&#xff0…

2024.4.28 机器学习周报

目录 引言 Abstract 文献阅读 1、题目 2、引言 3、创新点 4、总体流程 5、网络结构 5.1、损失函数 5.2、Confidence Maps 5.3、Part Affinity Fields(PAFs) 5.4、多人的PAFs 6、实验 7、结论 深度学习 yolov8实现目标检测和人体姿态估计 Yolov8网络结构 yaml…

【亲测可用】配置镜像源

文章目录 配置镜像源1. 手动添加镜像源2. 永久配置&#xff08;推荐&#xff09;方法1&#xff1a;方法2 &#xff1a; 小结 配置镜像源 配置镜像源会让资源下载的更快一些 我实验了一下&#xff0c;都成功了的方法&#xff0c;推荐给你们 1.手动添加 2.永久配置 前提是你的…

好看到爆炸的弹窗公告源码

源码介绍 好看到爆炸的弹窗公告源码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c; 源码截图 源码下载 好看到爆炸的弹窗公告源码

新标准日本语初下 课后练习作业

新版标准日本语初下 第二十五課 これは明日会議で使う資料です 第二十五課 これは明日会議で使う資料です &#xff12;&#xff14;&#xff0d;&#xff10;&#xff14;&#xff0d;&#xff12;&#xff16; 練習&#xff12;&#xff15;&#xff0d;1&#xff0d;1 例…

Vuforia AR篇(四)— AR虚拟按钮

目录 前言一、创建虚拟按钮二、创建脚本三、效果 前言 在当今互联网和移动设备普及的背景下&#xff0c;**增强现实&#xff08;AR&#xff09;**技术正迅速成为连接现实世界与数字信息的重要桥梁。AR虚拟按钮作为这一技术的创新应用&#xff0c;不仅提供了一种全新的用户交互…

[极客大挑战 2019]Upload、[ACTF2020 新生赛]Upload、[MRCTF2020]你传你呢

[极客大挑战 2019]Upload 打开环境&#xff0c;是上传一句话木马的题 先上传1.php试试&#xff0c;发现不可以 试试改后缀为phtml&#xff0c;提示语句中不能包含<?&#xff0c;只能改木马&#xff1a; <script language"php">eval($_POST[line]);</sc…

ListView、RecycleView、动画、单位、ViewPager

ListView列表 老版本 public View oldGetView(int position, View convertView, ViewGroup parent) {//返回每一个item//拿到布局if (convertViewnull)convertView LayoutInflater.from(context).inflate(R.layout.my_list_view, parent, false);//find会耗时需要优化TextVi…

YOLOv8核心原理深度解析

YOLOv8源码地址: https://github.com/ultralytics/ultralytics 一、简介: 根据官方描述,Yolov8是一个SOTA模型,它建立在Yolo系列历史版本的基础上,并引入了新的功能和改进点,以进一步提升性能和灵活性,使其成为实现目标检测、图像分割、姿态估计等任务的最佳选择。其具体…

Rancher-Longhorn-新增磁盘以及卷创建原理和卷副本调度规则

一、添加磁盘-官网指引 重点在于&#xff1a; 1、比如你新增了一块盘&#xff0c;你需要做一下事情&#xff1a; 1、执行 lsblk 能找到你的盘。 2、然后执行 fdisk /dev/sdxx 分区你的盘。 3、然后对于分区部署文件系统&#xff0c; mkfs.xfs 4、然后执行 mount /dev/sdxxx 你…

项目管理中常用的三个工具:甘特图、看板、燃尽图

在日常项目管理的实践中&#xff0c;为了更有效地追踪项目进度、优化资源配置和提高团队协作效率&#xff0c;管理者常常会借助一些工具来辅助工作。这些工具的本质在于将抽象复杂的项目管理任务具象化、简单化&#xff0c;以更直观、方便的方式呈现出来。 以下介绍项目管理中…

Git--基础学习--面向企业--持续更新

一、基础学习 1.1基本命令 //查询基础信息 git config --global --list //选取合适位置创建 mkdir 文件名 //创建文件夹 //全局配置 git config --global user.email "****e***i" git config --global user.name "*** K****"//--------------------进入…

Linux系统编程---线程同步

一、同步概念 同步即协同步调&#xff0c;按预定的先后次序运行。 协同步调&#xff0c;对公共区域数据【按序】访问&#xff0c;防止数据混乱&#xff0c;产生与时间有关的错误。 数据混乱的原因&#xff1a; 资源共享(独享资源则不会)调度随机(意味着数据访问会出现竞争)线…

新建云仓库

1.GitHub新建云仓库&#xff1a; LICENSE:开源许可证&#xff1b;README.md:仓库说明文件&#xff1b;开源项目&#xff1b;cocoaPodsName.podspec: CocoaPods项目的属性描述文件。 2.Coding新建云仓库&#xff1a; 备注&#xff1a; Coding新建项目&#xff1a;

自动化机器学习流水线:基于Spring Boot与AI机器学习技术的融合探索

&#x1f9d1; 作者简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

训练营第三十五天动态规划(基础题part1)

训练营第三十五天动态规划&#xff08;part1&#xff09; 需要用到之前的数据 确定dp数组&#xff08;dp table&#xff09;以及下标的含义确定递推公式dp数组如何初始化确定遍历顺序举例推导dp数组 509. 斐波那契数 力扣题目链接 题目 斐波那契数 &#xff08;通常用 F(n…

动态规划——斐波那契数列模型:面试题08.01.三步问题

文章目录 题目描述算法原理1.状态表示2.状态转移方程3.初始化4.填表顺序5.返回值 代码实现CJava 题目描述 题目链接&#xff1a;面试题08.01.三步问题 如果n是0走法可能是1也可能是0&#xff0c;所以本题范围并不需要考虑直接从1开始即可 因为以3为结尾有直接从0到3的方式&a…

深度学习500问——Chapter08:目标检测(4)

文章目录 8.3 One Stage 目标检测算法 8.3.1 SSD 8.3.2 DSSD 8.3.3 YOLOv1 8.3 One Stage 目标检测算法 我们将对单次目标检测器&#xff08;包括SSD系列和YOLO系列等算法&#xff09;进行综述。我们将分析FPN以理解多尺度特征图如何提高准确率&#xff0c;特别是小目标的检测…

无人机+集群组网:机载自组网电台技术详解

无人机与集群组网的结合为现代通信带来了独特的优势。在集群组网中&#xff0c;每个节点&#xff08;例如无人机&#xff09;都兼具路由器和主机的功能&#xff0c;它们不仅可以运行各种面向用户的应用程序&#xff0c;还可以执行路由协议&#xff0c;根据路由策略和路由表完成…