当前位置: 首页 > news >正文

read()调用

版权声明:此随笔是参考CSDN博主「wdfk_prog」的原创文章mm/filemap.c,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_39665253/article/details/150440385
1. 用户态层
应用进程调用 read(fd, buffer, len)→ glibc 库封装为 syscall(SYS_read, fd, buffer, len),触发系统调用陷入
2.进入内核态
软中断进入内核态,触发 sys_read() 系统调用,解析fd
3. VFS 虚拟文件系统层
sys_read() 调用 vfs_read()
解析 fd → 找到进程文件描述符表 → 拿到 struct file 和 struct inode(fd 是文件打开之后返回的文件ID号)
打开 /dev/my_device(字符设备) → 走 my_read
打开 /home/1.txt(ext4 磁盘文件) → 走 generic_file_read_iter
3.1 识别该文件是/dev/my_device 字符设备(自己写的驱动)
匹配该设备绑定的 file_operations 驱动操作集

// `file_operations` 结构体(自己写的)
static struct file_operations fops = {.owner   = THIS_MODULE,.read    = my_read,
};

设备驱动层(Driver Layer)
VFS 调用自定义驱动提供的读接口:my_read()

  • 驱动从硬件寄存器 / FIFO / 内核缓冲区读取数据;
  • 通过 copy_to_user() 将内核数据拷贝到用户态 buffer;
  • 逐层返回读取字节数,切回用户态,read() 调用返回

read() 设备文件的完整流程解析

  1. 用户调用 read(fd, buffer, len)
  2. glibc 通过 syscall(SYS_read, fd, buffer, len) 进入内核
  3. 内核中的 sys_read() 解析 fd
  4. VFS 发现 fd 关联 /dev/my_device
  5. 内核调用 file_operations.read,即 my_read()
  6. copy_to_user() 把数据拷贝到用户空间
  7. read() 返回读取的字节数,数据已存入 buffer

3.2 调用文件操作集的迭代读方法(磁盘文件)
回到刚刚那部分,若调用vfs_read()解析出来的fd对应的是磁盘文件,就要到Pagecache中去读数据了
sys_read()→ vfs_read() → file->f_op->read_iter() → ext4_read()到 Ext4 的文件操作集
Ext4 普通文件的 f_op 是这么注册的:

const struct file_operations ext4_fops = {.read_iter = generic_file_read_iter,  // 内核通用函数!
};

file->f_op->read_iter 直接就是 generic_file_read_iter,简单来说,VFS 直接调用到了 vfs_read() → generic_file_read_iter()
generic_file_read_iter()循环处理用户请求的字节范围,处理一页的数据
Linux 内核通用文件读迭代器,所有磁盘文件系统共用,只管页缓存逻辑,不关心具体文件系统、不直接操作磁盘。

步骤 1:解析迭代器 iov_iter
把用户传的 buffer、len、偏移pos 封装成 iov_iter,知道要把数据读到用户态哪个地址、读多少。
步骤 2:优先走 PageCache 页缓存命中
generic_file_read_iter()→ pagecache_get_page(inode->i_mapping,页偏移)确定缓存页
(1)若找到缓存页
1.uptodate=1,物理页已经缓存有效数据,直接调用copy_to_read()将数据拷贝到用户态
2.uptidate=0

  • 调用 inode->i_mapping->a_ops->readpage(file, page)或者是readpages(),
  • ext4 等文件系统收到请求,调用ext4_readpage(),根据inode和页索引(页偏移)计算出文件 offset 0 对应的磁盘逻辑块地址,
  • 然后构建 bio IO 提交给块设备层,发起磁盘读操作。通过DMA将数据从磁盘读到物理页(存在虚拟空间映射,因为进程访问的是虚拟地址空间,后边会讲到物理地址和虚拟地址是如何映射的),同时更新uptodate=1,
  • 当 DMA 操作(中断结束)完成,数据从磁盘读入到 page 对应的物理内存后,块设备层通过回调函数通知上层(应用层)。
  • 唤醒等待IO的应用进程,generic_file_read_iter 确认页面已 uptodate,然后调用 copy_to_user() 将 page 中的数据拷贝到应用程序的缓冲区

在这里可以引入零拷贝的概念
以上述读磁盘文件,再通过套接字把数据发出去为例(普通 4 次拷贝 + 2 次态切换)

  • 磁盘 → 内核 PageCache(DMA 硬件拷贝,无 CPU 参与,必要)
  • 内核 PageCache → 用户态 buffer(CPU 拷贝,多余拷贝 1)
  • 用户态 buffer → 内核 Socket 缓冲区(CPU 拷贝,多余拷贝 2)
  • 内核 Socket 缓冲区 → 网卡(DMA 硬件拷贝,必要)
    中间两次都是在用户态和内核态之间来回复制数据,白白浪费 CPU;还伴随两次状态切换,开销大、效率低,这就是普通 IO 的性能瓶颈
    所以就引入了零拷贝的概念,不让数据踏进用户态缓冲区,数据全程只待在内核 PageCache里,CPU只发指令,不搬数据。常见的两种零拷贝
    1. sendfile() 零拷贝
    全程数据不进用户态,完全在内核里流转,应用只告诉内核,从哪个文件读、发到哪个套接字,不碰数据本身
  • 磁盘 DMA → 内核 PageCache(必要)
  • 内核直接把 PageCache 数据 DMA / 直接流转 → 内核 Socket 缓冲区(无 CPU memcpy,只有 DMA 硬件搬运)
  • 内核 Socket 缓冲区 DMA → 套接字发送出去
    全程只有一次内核态到用户态的转换,三次DMA硬件的数据搬运,不用CPU进行拷贝,节省效率和资源
    2. mmap() 零拷贝
    把内核 PageCache 直接映射到用户态虚拟地址,应用不用再把内核数据拷贝到用户缓冲区,直接读写内核那页内存。
  • 磁盘 DMA → 内核 PageCache(必要)
  • 内核 PageCache 内存映射(mmap)到用户态,无 CPU 拷贝(memcpy),用物理地址到虚拟地址的映射,避免将数据拷贝到用户buffer
  • 用户直接操作映射地址,调用 send/write
  • 映射的页数据 CPU 拷贝 → 内核 Socket 缓冲区(CPU拷贝)
  • 内核 Socket 缓冲区 DMA → 套接字发送出去(必要)
    还是两次内核态到数据态的转换,两次DMA硬件的数据搬运,一次CPU的拷贝
    二者对比:
对比维度 mmap sendfile
数据是否进用户态 映射到用户态地址,可直接访问 完全不进用户态
CPU 多余拷贝 省 1 次,还剩 1 次(映射的页数据 → 内核 Socket 缓冲区) 省 2 次,无多余 CPU 拷贝
上下文切换 2 次 1 次,开销更低
能否修改文件数据 可以,用户直接改映射内存 不可以,只能原样转发
访问方式 支持随机读写、频繁访问 只能顺序流式转发
适用场景 大文件随机读、可编辑文件 文件静态转发、文件服务器、流媒体
缺页开销 有缺页异常开销,访问未缓存页会触发缺页、读磁盘 无用户态缺页开销

(2)未找到缓存页(Cache Miss):
内核分配(alloc_page())一个新的空白物理页 stuct page,然后将此page插入到inode->i_mapping->page_tree基数树对应页索引的位置(此时uptodate=0),
调用文件系统底层回调:inode->i_mapping->a_ops->readpage(page),去磁盘中读数据。ext4_readpage()→ ext4 换算磁盘块 → 构建 BIO 下发块设备 → DMA 磁盘读入内核页 → 标记 Uptodate → 拷贝到用户态(copy_to_user)回到上述找到缓存页(uptodate=0的情况,这里不再赘述)。
总结:

用户read
→ 系统调用 sys_read() → vfs_read()
→ f_op->read_iter() 触发 generic_file_read_iter()→ 查PageCache页缓存命中:直接拷贝到用户态,结束未命中:分配空页调用 a_ops->readpage() → ext4_readpage()读磁盘数据进PageCache页拷贝页缓存数据到用户buffer
→ 返回用户态

read () 读取磁盘文件的完整流程
1.用户调用 read(fd, buffer, len)
2.glibc 通过 syscall(SYS_read, fd, buffer, len) 进入内核
3.内核中的 sys_read() 解析 fd
4.VFS 发现 fd 关联磁盘普通文件(ext4)
5.内核调用 file_operations.read_iter,即 generic_file_read_iter()
6.generic_file_read_iter() 根据偏移计算页索引

查找 PageCache:命中 → 直接拿到物理页未命中 → 分配新页 → 调用 a_ops->readpage →  ext4_readpage() → 发起磁盘IO → DMA读取 → 页面标记为Uptodate
确认页面是 Uptodate 后
调用 copy_to_user() 把数据从 PageCache 拷贝到用户空间 buffer
read() 返回读取的字节数,数据已存入 buffer

关于物理地址到虚拟地址的映射,会在下一篇随笔写下。
小结:此随笔是笔者学了read()调用之后,对其的简单理解,写下只是作为学习笔记以及思路整理,若想深入学习mm/filemap.c,请参考https://blog.csdn.net/qq_39665253/article/details/150440385
后续会继续补充内容(作者菜鸡一枚,若有问题还请各位大佬指正,谢谢大家!)

http://www.jsqmd.com/news/750127/

相关文章:

  • 2026木材粉碎机选型全攻略:从技术参数到实操落地,十年大厂博尚机器经验分享 - 会飞的懒猪
  • 3步搞定Linux系统RTL8852BE无线网卡驱动安装与优化
  • 如何3步搞定魔兽争霸III性能优化?WarcraftHelper插件完全指南
  • Halcon 3D点云处理实战:从PLY文件读取到曲面匹配的完整流程(附避坑指南)
  • 思源宋体TTF实用指南:如何高效获取专业中文字体
  • 从MRI数据到GNN模型:手把手教你用BrainGB复现脑网络分类实验(附代码避坑指南)
  • 2026年4月国内口碑好的猫咪眼科医生推荐,猫咪结膜瓣遮盖手术/狗狗眼睑内翻手术,猫咪眼科专家推荐分析 - 品牌推荐师
  • 如何快速掌握Dell Fans Controller:告别服务器噪音的完整指南
  • 借助 Taotoken 模型广场为不同任务选择合适的大模型
  • 公司软件使用笔记
  • ImageGlass终极指南:Windows平台轻量级图片查看器
  • 基于几何非线性的塔机结构响应平面刚架【附代码】
  • Sunshine游戏串流:5步打造你的跨设备游戏体验终极指南
  • 告别“瞎猜”:用MAT的OQL像查数据库一样精准分析JVM堆内存
  • 别再傻傻分不清了!一张图搞懂FMEA、FTA、FMECA和FRACAS到底怎么用
  • 洛谷 P4832 珈百璃堕落的开始 题解
  • 用Python复现小龙虾优化算法COA:从公式到代码的保姆级拆解(附避坑指南)
  • 从外部中断到外部时钟:两种STM32读取YF-S401脉冲的方法,哪种更适合你的项目?
  • Audamo:为极简Linux桌面实现自动化昼夜主题切换
  • 3分钟掌握终极Cookie导出方案:本地安全导出浏览器Cookie的完整指南
  • 从爬虫到工具:我是如何分析XMeta接口并封装成一个PHP查询工具的(附避坑指南)
  • 5分钟快速掌握Switch游戏文件管理:NSC_BUILDER终极指南
  • 4种飞行物数据集31909张VOC+YOLO格式
  • 火山引擎方舟API工具扩展指南
  • 线段树的区间修改和懒标记
  • 从零构建极简静态网站:复古项目www-sacred的现代启示
  • BetterNCM安装器终极指南:一键解锁网易云音乐隐藏功能
  • 基于多目标优化的PC连续刚构桥预应力钢束配束设计【附代码】
  • 第1篇:认识仓颉——搭建开发环境 仓颉原生中文编程
  • 3分钟极速上手:Thorium浏览器让老旧电脑也能流畅上网的秘诀