嵌入式驱动学习第三周——字符设备驱动关键结构体

前言

   linux内核将字符设备抽象成一个具体的数据结构,可以理解为字符设备对象,这篇博客就来讲解一下字符设备驱动的关键结构体。

   嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程,未来预计四个月将高强度更新本专栏,喜欢的可以关注本博主并订阅本专栏,一起讨论一起学习。现在关注就是老粉啦!

目录

  • 前言
  • 1. 字符设备介绍
  • 2. 虚拟文件系统(VFS)
  • 3. file_operation结构体
    • 3.1 介绍
    • 3.2 常用函数
    • 3.3 实现模板
  • 3. inode结构体
    • 3.1 介绍
    • 3.2 实际使用
  • 4. file结构体
    • 4.1 介绍
    • 4.2 实际使用
  • 参考资料

1. 字符设备介绍

   字符设备是Linux驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作,读写数据分先后顺序。常见的比如操作IO口输入输出,I2C,SPI,LCD等均为字符设备。

   Linux应用程序对驱动程序的调用如下图所示:

在这里插入图片描述
   应用程序在用户空间,而Linux驱动属于内核的一部分,运行于内核空间,如果想在用户空间对内核空间进行操作,那么就需要用系统调用实现从用户空间陷入到内核空间。例如调用open()函数,其本身是C库的一部分,调用流程如下:

在这里插入图片描述

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

2. 虚拟文件系统(VFS)

   众所周知,Linux有一个很厉害的思想叫:“一切皆文件”,即在Linux下,你不管是常规的txt,pdf等用open函数打开,对字符设备(LCD,IO口等)、块设备、网络设备等也当做文件,通过open函数打开,write函数写入指令或数据,read函数读取其中的数据等。

   在此基础上,虚拟文件系统(Virtuial File System,VFS)便应运而生了,它是Linux系统中的一个软件抽象层,是实现“一切皆文件”的关键。它为用户空间的程序提供了文件系统接口,同时定义了所有文件系统都支持的基本的、概念上的接口和数据结构。例如,读写普通的文本文件和读写I/O设备的具体实现方法必然是不同的,但VFS提供了统一的接口read和write,开发人员需要编写这些接口的具体的不同的实现,个人感觉有点像面向对象中的多态。因此,在VFS层和内核的其他部分看来,所有文件都只需调用read和write函数就可以完成读写功能,具体的实现过程它们并不关心。

3. file_operation结构体

3.1 介绍

   file_operation是将系统调用和驱动程序关联起来的关键数据结构,其每个成员都对应一个系统调用。读取file_operation中相应的函数指针,接着把控制权交给函数指针指向的函数,完成Linux设备驱动的工作,其在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_MMUunsigned (*mmap_capabilities)(struct file *);
#endifssize_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);
};

   以open函数为例,不同设备的打开方式不同,因此open函数的实现显然应该是不同的,但从VFS层面上看,都是只需要调用open函数就可以满足打开设备的需求。

3.2 常用函数

   常用的函数如下,不一定全部都要实现,但是像open、release、write、read等都是需要实现的:

owner 拥有该结构体的模块的指针,一般设置为 THIS_MODULE
read 函数用于读取设备文件
write 函数用于向设备文件写入(发送)数据
open 函数用于打开设备文件
release 函数用于释放(关闭)设备文件,与应用程序中的 close 函数对应
unlocked_ioctl 函数提供对于设备的控制功能,与应用程序中的 ioctl 函数对应
compat_ioctl 函数与 unlocked_ioctl 函数功能一样,区别在于在 64 位系统上,32 位的应用程序调用将会使用此函数。在 32 位的系统上运行 32 位的应用程序调用的是unlocked_ioctl。
mmap 函数用于将设备的内存映射到进程空间中(也就是用户空间),一般帧缓冲设备会使用此函数,比如 LCD 驱动的显存,将帧缓冲(LCD 显存)映射到用户空间中以后应用程序就可以直接操作显存了,这样就不用在用户空间和内核空间之间来回复制。
llseek 函数用于修改文件当前的读写位置
poll 是个轮询函数,用于查询设备是否可以进行非阻塞的读写
fasync 函数用于刷新待处理的数据,用于将缓冲区中的数据刷新到磁盘中
aio_fsync 函数与 fasync 函数的功能类似,只是 aio_fsync 是异步刷新待处理的数据

3.3 实现模板

   比如一个led的驱动,我们想对led进行控制,那么可以写一个write函数用来设置led对应io口的电平高低:

static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{// 设置对应gpio电平的代码return 0;
}

   写好后将file_operation结构体中的write函数变为led_write

static struct file_operations gpioled_fops = {.owner = THIS_MODULE,.write = led_write,
};

   不过要注意的是,由于内核空间不能操作用户空间的数据,因此需要用copy_from_user函数将用户空间的数据拷贝到内核空间中

3. inode结构体

3.1 介绍

   inode是一种存储文件元信息的区域,包括文件的创建者,文件创建日期,文件大小等(但不包含文件名)。此外,其通常还涉及blockblock就是存储文件数据的地方。

   拓展一下文件的存储:文件是存储在硬盘上的,硬盘的最小存储单位叫做扇区 sector,每个扇区存储512字节。操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个块block。这种由多个扇区组成的块,是文件存取的最小单位。块的大小,最常见的是4KB,即连续八个sector组成一个block。文件数据存储在块中,那么还必须找到一个地方存储文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等,这种存储文件元信息的区域就叫做inode。

   inode中主要存储以下这些元数据:

  • inode编号
  • 文件大小
  • 占用的块数目与块大小
  • 文件类型(普通文件、目录、管道,etc.)
  • 存储该文件的设备号
  • 链接数目
  • 读、写、执行权限
  • 拥有者的用户ID和组ID
  • 文件的最近访问、数据最近修改时间
  • inode最近修改时间

   inode编号就相当于你的身份证,是操作系统唯一标识一个文件的手段,通过stat指令可以查看数据信息:

在这里插入图片描述

3.2 实际使用

   在实际使用中,通常是作为open,release等函数的参数。因为打开和关闭文件的时候,也需要更新inode的信息,比如访问时间,文件大小等等信息:

static int led_open(struct inode *inode, struct file *filp) {}static int led_release(struct inode *inode, struct file *filp) {}

4. file结构体

4.1 介绍

   file结构体代表一个打开的文件,系统中的每个打开的文件在内核空间都有一个关联的 struct file。由内核在打开文件时创建,并传递给在文件上进行操作的任何函数。在文件的所有实例都关闭后,内核释放这个数据结构。其有两个很重要的字段:文件描述符和缓冲区

   file结构体依赖于底层的inode结构体。其于inode的区别在于,inode不跟踪文件的当前位置和当前模式,只是帮助操作系统找到底层文件结构的内容(管道,目录,常规磁盘文件,块/字符设备文件等);file结构体是一个基本结构,但也有一个指向inode的指针,代表打开的文件,并提供一组函数,它们与底层文件结构上执行的方法相关,这些方法包括open、write、read等。

   总结下来就是inode代表内核中的文件,file代表实际打开的文件。若一个文件打开多次,会有不同的file,但都指向同一个inode。

4.2 实际使用

   在实际使用中,通常是作为open,read,write,release等函数的参数,因为file结构体是代表打开的文件,因此进行操作的时候都需要加上它来修改信息:

static int led_open(struct inode *inode, struct file *filp) {}static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {}static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) {}static int led_release(struct inode *inode, struct file *filp) {}

参考资料

[1] 【正点原子】I.MX6U嵌入式Linux驱区动开发指南 第四十章
[2] Linux中的File_operations结构体
[3] linux驱动开发(二):Linux字符设备驱动程序(设备号、cdev、设备节点、file_operations)
[4] inode详解
[5] 问:说说inode到底是什么?
[6] Linux file结构体

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

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

相关文章

ArcGis Pro Python工具箱教程 02 工具箱工具集添加

ArcGis Pro Python工具箱教程 02 工具箱工具集添加 经过上一章的教程,pyt工具箱已将可以建立一个模板了,但是所建立的工具都是在一个列表,要进行查找会非常麻烦,所以要采用工具集的分类 官方文档中已经给出了添加工具集的方法&a…

以题为例浅谈SSRF

什么是ssrf SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。 一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。(正是因为它是由服务端发起的,所以它能够请求到与它相连…

HTML案例-2.标签综合练习

目录 效果 知识点 1.图像标签 2.链接标签 3.锚点定位 4.base标签 源码 页面1 页面2 效果 知识点 1.图像标签 <img src="图像URL" /> 单标签 属性 属性值 描述 src URL 图像的路径 alt 文本

【linux线程(二)】线程互斥与线程同步

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:Linux从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学更多操作系统知识   &#x1f51d;&#x1f51d; Linux线程 1. 前言2. 多线程互…

基于大模型和向量数据库的 RAG 示例

1 RAG 介绍 RAG是一种先进的自然语言处理方法&#xff0c;它结合了信息检索和文本生成技术&#xff0c;用于提高问答系统、聊天机器人等应用的性能。 2 RAG 的工作流程 文档加载&#xff08;Document Loading&#xff09; 从各种来源加载大量文档数据。这些文档…

Redis 除了做缓存,还能做什么?

分布式锁&#xff1a;通过 Redis 来做分布式锁是一种比较常见的方式。通常情况下&#xff0c;我们都是基于 Redisson 来实现分布式锁。关于 Redis 实现分布式锁的详细介绍&#xff0c;可以看这篇文章&#xff1a;分布式锁详解open in new window 。限流&#xff1a;一般是通过 …

json-server 安装成功,查看版本直接报错。安装默认版本埋下的一个坑,和node版本不匹配

文章目录 一、作者的错误二、作者安装的过程三、版本问题的解决方式四、安装成功&#xff0c;显示命令不存在的解决思路五、安装失败的解决思路六、json-server运行命令参考文档 一、作者的错误 安装成功 错误原文 file:///C:/Users/ljj/AppData/Roaming/nvm/v14.18.1/node_g…

go语言基础笔记

1.基本类型 1.1. 基本类型 bool int: int8, int16, int32(rune), int64 uint: uint8(byte), uint16, uint32, uint64 float32, float64 string 复数&#xff1a;complex64, complex128 复数有实部和虚部&#xff0c;complex64的实部和虚部为32位&#xff0c;complex128的实部…

Vue首屏优化方案

在Vue项目中&#xff0c;引入到工程中的所有js、css文件&#xff0c;编译时都会被打包进vendor.js&#xff0c;浏览器在加载该文件之后才能开始显示首屏。若是引入的库众多&#xff0c;那么vendor.js文件体积将会相当的大&#xff0c;影响首屏的体验。可以看个例子&#xff1a;…

Unload-labs

function checkFile() {var file document.getElementsByName(upload_file)[0].value;if (file null || file "") {alert("请选择要上传的文件!");return false;}//定义允许上传的文件类型var allow_ext ".jpg|.png|.gif";//提取上传文件的类…

初见Dynamo2.13 for Revit2023~

Hello大家好&#xff01;我是九哥~ 今天我们来聊聊Dynamo2.13 for Revit有哪些新功能&#xff08;后台回复"Revit2013"获取&#xff09;~ 首先&#xff0c;Dynamo2.13版本其实早就发布了&#xff0c;官方博客更是花了三篇文章的篇幅来详细介绍&#xff0c;小伙伴…

Hack The Box-Monitored

目录 信息收集 rustscan dirsearch WEB web信息收集 snmpwalk curl POST身份验证 漏洞探索 漏洞挖掘 sqlmap 登录后台 提权 get user get root 信息收集 rustscan ┌──(root㉿ru)-[~/kali/hackthebox] └─# rustscan -b 2250 10.10.11.248 --range0-65535 --…

今天我们来学习一下关于MySQL数据库

目录 前言: 1.MySQL定义&#xff1a; 1.1基础概念&#xff1a; 1.1.1数据库&#xff08;Database&#xff09;&#xff1a; 1.1.2表&#xff08;Table&#xff09;&#xff1a; 1.1.3记录&#xff08;Record&#xff09;与字段&#xff08;Field&#xff09;&#xff1a; …

C#,入门教程(27)——应用程序(Application)的基础知识

上一篇: C#,入门教程(26)——数据的基本概念与使用方法https://blog.csdn.net/beijinghorn/article/details/124952589 一、什么是应用程序 Application? 应用程序是编程的结果。一般把代码经过编译(等)过程,最终形成的可执行 或 可再用 的文件称为应用程序。可执行文…

GaussDB数据库的索引管理

目录 一、引言 二、GaussDB数据库中的索引基本概念 1. 什么是GaussDB索引&#xff1f; 2. GaussDB索引的作用 三、GaussDB支持的索引类型 1. B-Tree索引 2. GIN索引 3. GiST索引 4. SP-GiST索引 四、创建和管理GaussDB索引 1. 创建索引 2. 删除索引 3. 索引的优化…

【AI论文阅读笔记】ResNet残差网络

论文地址&#xff1a;https://arxiv.org/abs/1512.03385 摘要 重新定义了网络的学习方式 让网络直接学习输入信息与输出信息的差异(即残差) 比赛第一名1 介绍 不同级别的特征可以通过网络堆叠的方式来进行丰富 梯度爆炸、梯度消失解决办法&#xff1a;1.网络参数的初始标准化…

RabbitMQ详解与常见问题解决方案

文章目录 什么是 RabbitMQ&#xff1f;RabbitMQ 和 AMQP 是什么关系&#xff1f;RabbitMQ 的核心组件有哪些&#xff1f;RabbitMQ 中有哪几种交换机类型&#xff1f;Direct Exchange(直连交换机)Topic Exchange(主题交换机)Headers Exchange(头部交换机)Fanout Exchange(广播交…

安装linux_centos7虚拟机_开启网络_ssh_防火墙

文章目录 安装linux_centos7虚拟机_开启网络_ssh_防火墙安装centos7虚拟机1. 进入VMware --> 点击文件 --> 新建虚拟机2. 选择典型 --> 选择下一步3. 选择--> 稍后安装操作系统4. 选择--> Linux --> CentOS 7 64位5. 在虚拟机名称输入(虚拟机名) --> 选择…

李三清研究引领力学定律新篇章,光子模型图揭秘

一周期内&#xff0c;垂直&#xff0c;曲率不变&#xff0c;方向转向互变&#xff0c;正向反向互变&#xff0c;左旋右旋互变。变无限粗或变无限厚才发生质变&#xff0c;且属于由内向外变换&#xff0c;所以对应变换就是由内点向外点变换。 由于方向转向不能分割&#xff0c;…

【Vue2】组件通信

父子通信 父 -> 子 子 -> 父 props 校验 props: {校验的属性名: {type: 类型, // Number String Boolean ...required: true, // 是否必填default: 默认值, // 默认值validator (value) {// 自定义校验逻辑return 是否通过校验}} },data 的数据是自己的 → 随便改pr…