ARM_Linux驱动开发——字符设备驱动开发(上)

 

目录

一、Linux驱动开发思维

二、Linux驱动开发分类

三、“ ARM_Linux驱动开发——字符设备驱动开发 ”

字符设备驱动简介


前言

在分享Linux驱动开发之前,我想带大家首先回顾一下裸机驱动开发和Linux驱动开发的区别。

1、运行环境和操作系统:

裸机驱动开发:在裸机开发中,通常是针对没有操作系统支持的嵌入式系统或特定的硬件平台开发驱动程序。这些系统通常直接操作硬件资源,没有操作系统的抽象层。

Linux驱动开发:Linux驱动开发则是针对运行Linux操作系统的计算机或嵌入式系统开发。Linux作为一个开源操作系统,提供了广泛的设备支持和抽象接口,开发者可以利用Linux内核提供的接口来开发设备驱动。

2、开发流程和工具链:

裸机驱动开发:开发者需要了解硬件的具体细节,通常使用特定的开发工具链(如ARM Cortex-M系列开发可以使用Keil、IAR等),编写底层的驱动代码,直接操作硬件寄存器和控制器。

Linux驱动开发:需要熟悉Linux内核的架构和API,使用C语言或者特定的Linux设备驱动框架(如Platform Driver、USB Driver等)进行开发。Linux提供了丰富的设备驱动开发文档和示例代码,以及调试工具。

3、硬件抽象层和接口标准化:

裸机驱动开发:由于没有操作系统提供的标准接口,开发者需要自行处理硬件之间的通信和资源管理。每种硬件平台的驱动开发可能具有很高的定制性和特定性。

Linux驱动开发:Linux内核提供了一套标准的设备驱动开发框架和API,开发者可以利用这些框架来开发驱动,这样的驱动通常更具有通用性和可移植性。

4、调试和测试:

裸机驱动开发:调试通常需要通过硬件调试器或者仿真器进行,对于实际硬件的测试需要特定的硬件平台。

Linux驱动开发:Linux内核提供了丰富的调试工具和接口,如printk日志、sysfs接口等,同时也可以使用标准的Linux调试工具(如gdb、strace等)来进行驱动调试和性能分析。

总之,裸机驱动开发更加依赖于具体的硬件平台和细节,开发的驱动程序更加定制化和特定化;而Linux驱动开发则更多依赖于操作系统提供的标准接口和抽象层,开发的驱动程序具有更好的可移植性和通用性。 

一、Linux驱动开发思维

1、首先需要跳出曾经的裸机开发思维,因为Linux下的驱动开发直接操作寄存器已经不现实了;

2、开发者需要根据Linux下的各种驱动框架进行开发。即必须满足驱动开发的框架;

3、驱动最终的执行形式就是:/dev/xxx文件、open、close、write、read……

4、新的驱动支持设备树,这是一个.dts文件,此文件描述的是设备信息。


二、Linux驱动开发分类

Linux驱动开发可以按照不同的分类进行归类,主要包括以下几类:

1、字符设备驱动(Character Device Drivers):

这类驱动程序处理以字符流方式进行数据传输的设备,如串口、终端、声音卡等。通常通过文件操作接口(open, read, write, close)来与用户空间进行通信。

2、块设备驱动(Block Device Drivers):

这类驱动程序用于管理块设备,如硬盘、闪存存储器等,它们以固定大小的块为单位进行数据传输。块设备驱动通常需要实现块设备的缓存、IO调度等功能。

3、网络设备驱动(Network Device Drivers):

网络设备驱动用于控制网络接口卡(NIC),如以太网卡。这类驱动程序处理网络数据包的收发、协议栈的处理等。

4、USB设备驱动(USB Device Drivers):

USB设备驱动用于控制连接到Linux系统的USB设备,如USB存储设备、USB网卡等。这类驱动程序需要处理USB协议栈、设备的插拔事件等。

5、文件系统驱动(Filesystem Drivers):

文件系统驱动用于支持特定类型的文件系统,如ext4、NTFS等。这类驱动程序负责管理存储设备上的文件和目录结构。

6、总线设备驱动(Bus Drivers):

总线设备驱动用于管理系统总线,如PCI总线、SPI总线、I2C总线等。这类驱动程序负责枚举、初始化和管理连接到总线上的设备。

7、平台设备驱动(Platform Drivers):

平台设备驱动用于支持特定硬件平台上的设备,如嵌入式系统中的特定传感器、LED控制器等。

8、虚拟设备驱动(Virtual Device Drivers):

这类驱动程序不与物理硬件设备直接交互,而是创建虚拟设备,如虚拟网卡、虚拟磁盘等。

9、字符驱动和块驱动的子类:

这些驱动可能根据设备的特定需求进一步分类,例如音频设备驱动、输入设备驱动(键盘、鼠标)、显示设备驱动等。

Linux驱动开发涵盖了广泛的设备类型和功能,开发者根据具体的设备类型选择适合的驱动开发模块和接口。

三、“ ARM_Linux驱动开发——字符设备驱动开发 ”


字符设备驱动简介

字符设备就是一个一个字节,按照字节流来进行读写操作的设备,读写数据是分先后顺序的。常见的比如点灯、IIC、ISP……都是字符操作的设备,对于这些设备的驱动就叫做字符设备驱动。

了解字符设备驱动架构之前,我们可以先来看一下Linux下的应用程序是如何调用驱动程序的:

在Linux中,一切皆为文件。要实现对硬件的操作,需要应用程序对名为"/dev/xxx"的文件将进行操作。这个文件是由驱动加载成功后,会在"/dev"目录下生成相应的文件。

举一个简单的例子:如果你要操作点亮一个灯,你就可以使用open函数打开"/dev/Led"文件,使用完成以后用close函数关闭"/dev/Led"这个文件。需要点亮灯的话,则使用函数write函数来操作,即向此驱动写入数据,这个数据就是决定是否打开灯。如果要获取Led灯的状态,则使用read函数来从驱动中读取相应的状态。

应用程序是运行在用户空间,但是Linux驱动属于内核的一部分,所以驱动运行在内核空间。这样的话问题就来了,如果用户要直接对内核进行操作,直接通过用户空间来操作可行吗?

答案肯定是不行的!

用户空间是不能直接对内核进行操作的,他需要找一个中间人,这个中间人就是通过Linux驱动函数。即使用"系统调用"的方法来实现用户空间“侵入”到内核空间,这样才能实现对底层驱动的操作。

上面提到的open、close、write、read等这些驱动操作函数,都是由C库提供的。在Linux中,系统调用作为C库的一部分。

例如:调用open函数的时候流程如下:

(应用程序)step1:应用调用open()函数 ——> (C库)step2:C库中的open()函数 ——> (内核)step3:open()系统调用 ——> (具体驱动)step4: 驱动的open()函数

图片

那问题又来了,应用程序和具体的驱动又是如何联系起来的呢?

通过上面的讲解,我们能初步了解到应用程序使用到的函数在具体驱动程序中都有与之对应的函数, 比如应用程序中调用了 open 这个函数,那么在驱动程序中也得有一个名为 open 的函数。每一个系统调用,在驱动中都有与之对应的一个驱动函数,在 Linux 内核文件 include/linux/fs.h 中有个叫做 file_operations 的结构体,此结构体就是 Linux 内核驱动操作函数集合。

struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
	ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
	int (*iopoll)(struct kiocb *kiocb, bool spin);
	int (*iterate) (struct file *, struct dir_context *);
	int (*iterate_shared) (struct file *, struct dir_context *);
	__poll_t (*poll) (struct file *, struct poll_table_struct *);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	unsigned long mmap_supported_flags;
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, loff_t, loff_t, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **, void **);
	long (*fallocate)(struct file *file, int mode, loff_t offset,
			  loff_t len);
	void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
	unsigned (*mmap_capabilities)(struct file *);
#endif
	ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
			loff_t, size_t, unsigned int);
	loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
				   struct file *file_out, loff_t pos_out,
				   loff_t len, unsigned int remap_flags);
	int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;

下面我们对内核驱动操作函数进行初步解释:

  1. llseek:

    • 定义:文件定位函数,用于改变文件的读写位置。

    • 参数:struct file * 文件指针,loff_t 偏移量,int 偏移基准。

    • 返回值:loff_t 新的文件位置。

  2. read:

    • 定义:读取文件内容到用户空间。

    • 参数:struct file * 文件指针,char __user * 用户空间缓冲区,size_t 请求读取的字节数,loff_t * 读取位置的指针。

    • 返回值:ssize_t 实际读取的字节数,错误时返回负值。

  3. write:

    • 定义:将用户空间的数据写入文件。

    • 参数:struct file * 文件指针,const char __user * 用户空间缓冲区,size_t 请求写入的字节数,loff_t * 写入位置的指针。

    • 返回值:ssize_t 实际写入的字节数,错误时返回负值。

  4. read_iter:

    • 定义:迭代式地从文件读取数据到用户空间。

    • 参数:struct kiocb * 异步IO控制块,struct iov_iter * 数据迭代器。

    • 返回值:ssize_t 实际读取的字节数。

  5. write_iter:

    • 定义:迭代式地将用户空间的数据写入文件。

    • 参数:struct kiocb * 异步IO控制块,struct iov_iter * 数据迭代器。

    • 返回值:ssize_t 实际写入的字节数。

  6. iopoll:

    • 定义:用于异步IO中的轮询操作。

    • 参数:struct kiocb * 异步IO控制块,bool 是否自旋。

    • 返回值:int 操作结果。

  7. iterate:

    • 定义:在目录中迭代项。

    • 参数:struct file * 目录文件指针,struct dir_context * 目录上下文。

    • 返回值:int 操作结果。

  8. iterate_shared:

    • 定义:在共享目录中迭代项。

    • 参数:struct file * 目录文件指针,struct dir_context * 目录上下文。

    • 返回值:int 操作结果。

  9. poll:

    • 定义:注册文件的poll/epoll事件。

    • 参数:struct file * 文件指针,struct poll_table_struct * poll表。

    • 返回值:__poll_t 事件掩码。

关于字符设备驱动开发步骤,敬请关注下文更新——《ARM_Linux驱动开发——字符设备驱动开发(下)》!

 

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

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

相关文章

【无需公网IP】在树莓派上搭建Web站点

目录 1.概述 2.使用 Raspberry Pi Imager 安装 Raspberry Pi OS 3.设置 Apache Web 服务器 3.1测试 web 站点 3.2安装静态样例站点 3.3将web站点发布到公网 3.4安装 Cpolar 3.5cpolar进行token认证 3.6生成cpolar随机域名网址 3.7生成cpolar二级子域名 3.8将参数保存…

边框插画:成都亚恒丰创教育科技有限公司

边框插画:艺术与生活的精致边界 在视觉艺术的广阔天地里,边框插画以其独特的魅力和细腻的表达方式,成为连接艺术与生活的一道精致边界。成都亚恒丰创教育科技有限公司它不仅仅是图像的外框装饰,更是情感、故事与创意的延伸&#…

基于Matlab和Python泰勒图的绘制

一、泰勒图介绍 泰勒图:泰勒图1常用于评价模型的精度,常用的精度指标有相关系数,标准差以及均方根误差(RMSE)。一般而言,泰勒图中的散点代表模型,辐射线代表相关系数,横纵轴代表标准差,而虚线代表均方根误差。泰勒图一改以往用散点图这种只能呈现两个指标来表示模型精度…

《基于 LatentFactor + Redis + ES 实现动态药房分配方法》

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗 🌻 近期刚转战 CSDN,会严格把控文章质量,绝不滥竽充数,欢迎多多交流。&am…

echarts——横坐标轴文字过长如何换行

横坐标轴文字过长,想要换行 实现如下效果 具体实现代码如下: axisLabel: {show: true,interval: 0,formatter: function (value) {var ret "";//拼接加\n返回的类目项 var maxLength 4;//每项显示文字个数 var valLength value.length;//X轴类目项…

MP | 基于kmer的泛基因组分析方法及应用

2024年5月24日,中国农业大学分子设计育种前沿科学中心作物杂种优势与利用教育部重点实验室郭伟龙与姚颖垠团队在《Molecular Plant》发表了题为《A k-mer-based pangenome approach for cataloging seed-storage-protein genes in wheat to facilitate genotype-to-…

一道笔试题 - 反转列表

文章目录 描述预期结果代码 描述 给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1),长度为n,反转该链表后,返回新链表的表头。 如当输入链表{1,2,3}时, 经反转后,原链表变…

【RHCE】NFS 实验

主服务器 下载nfs-utils软件包: 1.如果停⽌该服务,启动并启⽤该服务: systemctl enable - now rpcbind 2.要启动 NFS 服务器,并使其在引导时⾃动启动:systemctl enable - now nfs- server 3.配置防火墙,开…

基于swagger插件的方式推送接口文档至torna

目录 一、前言二、登录torna三、创建/选择空间四、创建/选择项目五、创建/选择应用六、获取应用的token七、服务推送7.1 引入maven依赖7.2 test下面按照如下方式新建文件 一、前言 Torna作为一款企业级文档管理系统,支持了很多种接口文档的推送方式。官方比较推荐的…

【深度学习】PyTorch深度学习笔记02-线性模型

1. 监督学习 2. 数据集的划分 3. 平均平方误差MSE 4. 线性模型Linear Model 用穷举法确定线性模型的参数 import numpy as np import matplotlib.pyplot as pltx_data [1.0, 2.0, 3.0] y_data [2.0, 4.0, 6.0]def forward(x):return x * w# loss function 是 均方根误差 lo…

YoloV8改进策略:卷积篇Kan行天下之JacobiKAN,KAN遇见Jacobi多项式

摘要 将Kolmogorov-Arnold Networks (KAN) 中的B-spline替换为Jacobi多项式是一个很有创意的想法,因为Jacobi多项式在函数逼近方面表现出色,并且具有递归计算的特性。经过测试,Jacobi多项式的KAN在YoloV8中,取得了非常不错的涨点效果。下面我将概述如何构建基于Jacobi多项…

【库架一体立体库】与【传统立体库】对比

导语 大家好,我是社长,老K。专注分享智能制造和智能仓储物流等内容。 随着冷链物流行业的快速发展,对于冷藏设施的要求也在不断提高。库架一体式智能立体冷藏库以其高效、节能、智能化的特点,正逐渐成为行业发展的新趋势。 分享一…

大模型应用中什么是SFT(监督微调)?

大模型应用中什么是SFT(监督微调)? 一、SFT的基本概念 监督微调(Supervised Fine-Tuning, SFT)是对已经预训练的模型进行特定任务的训练,以提高其在该任务上的表现。预训练模型通常在大量通用数据上进行训…

算法:字符串相关

目录 题目一:最长公共前缀 题目二:最长回文子串 题目三:二进制求和 题目四:字符串相乘 题目一:最长公共前缀 编写一个函数来查找字符串数组中的最长公共前缀 如果不存在公共前缀,返回空字符串 "…

mysql判断时间段是否重合

mysql判断时间段是否重合 SELECT CASE WHEN t1.start_time < t2.end_time AND t1.end_time > t2.start_time THEN ‘重合’ ELSE ‘不重合’ END AS result FROM table_name t1, table_name t2 WHERE t1.id <> t2.id;

产品经理-交互设计动手实践(11)

业内有很多画交互的工具&#xff0c;这里不过多介绍&#xff0c;互联网公司最常用的工具是Axure,墨刀,蓝湖,小瀑 它是一个专业的快速原型设计工具&#xff0c;使用它能够快速创建线框图、流程图、原型和规格说明文档。 它能快速、高效地创建原型&#xff0c;同时支持多人协作设…

不想成为失业大军,就要学习六西格玛?

最近&#xff0c;优思学院收到一封邮件&#xff0c;这封邮件的发送者是一位完成了我们六西格玛绿带课程的学生。 他的公司裡有20%的工程师被裁员&#xff0c;但值得注意的是&#xff0c;留下来的工程师中有70%人竟然都持有六西格玛绿带或黑带证书。 他的公司不仅希望利用这些…

科普文:深入理解Mybatis

概叙 (1) JDBC JDBC(Java Data Base Connection,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成.JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。 优点…

React文档内网搭建

React文档内网搭建流程 官网地址 官网中文地址 通过官网我们可以找到React的github存储库 ReactGitHub 在介绍中可以找到对应的文档存储库 React文档存储库 此存储库是英文文档地址,我们通过中文文档地址以及该存储库作者目录下找到中文存储库 React文档中文存储库 下载…

JavaSE语法 | 初识Java!!!

初识Java 一、Java开发环境二、初步认识Java的main方法2.1 main方法的实现2.2 运行Java程序 三、注释四、标识符五、关键字 一、Java开发环境 IDEA版本&#xff1a;IntelliJ IDEA Community Edition 2022.3.3 JDK17 Windows 11 二、初步认识Java的main方法 2.1 main方法的实…