存储与IO子系统

1 Linux 存储与 I/O 子系统(基于 5.10 内核)

面向有嵌入式经验的同行:从系统调用到块设备驱动的纵向梳理,便于对照 5.10 源码阅读、实验与排障。文中路径与符号均以 Linux 5.10 树为参照;不同发行版配置与 backport 可能略有差异。


1.1 总览:一条主链与两条存储分叉

1.1.1 块设备主链(eMMC / SD / NVMe / SATA SSD 等)

进程通过系统调用进入内核 → VFSstruct file / inode / dentry)→ 具体文件系统 (如 ext4、f2fs)→ 页缓存address_space、radix tree)→ 通用块层bioblk-mq、请求队列)→ 块设备驱动 (如 mmcblknvme)→ 带 FTL 的存储介质。

1.1.2 嵌入式常见分叉:MTD 路径

SPI NOR / 裸 NAND 等常走 MTD + UBI + UBIFS(或 JFFS2 等),语义与「块设备 + ext4」不同:擦写粒度、坏块、磨损均衡、卷管理在栈中位置都不一样。本文仍以块设备栈为主,在相关章节用简短对比提醒。


1.2 从进程到内核:系统调用入口

读写文件最常见的入口:

  • read(2) / write(2)fs/read_write.cvfs_read / vfs_write

  • pread / pwrite:带偏移的包装,最终仍落到 VFS 层。

  • mmap(2)mm/mmap.c 与文件映射相关逻辑;文件页仍与 页缓存 强关联。

  • fsync(2) / fdatasync(2):将「文件」层面的持久化要求向下传递,具体保证取决于文件系统实现与设备写缓存策略。

  • open(2) 标志 O_DIRECT:绕过页缓存的直访路径(有对齐等约束)。

5.10 阅读建议 :在 fs/read_write.c 里跟一次 vfs_readfile->f_op->read_iter(新接口)或 read(老接口)的路径;write 同理看 write_iter


1.3 VFS 层:路径如何变成 inode 与 file

1.3.1 核心对象(概念分工)

  • struct file :进程一次「打开」的实例,含当前偏移、f_op、路径指向的 dentry/inode 等。

  • struct dentry:路径分量缓存;negative dentry 与查找性能、行为有关。

  • struct inode:文件在存储上的「逻辑元数据」(权限、大小、时间戳等)及具体文件系统的私有数据。

1.3.2 操作向量

  • file_operations:open/read/write/mmap/poll/ioctl 等实例操作。

  • inode_operations:lookup、create、unlink、 setattr 等 inode 级操作。

  • address_space_operations :页缓存与后备存储之间的桥梁(读页、写页、写回等),是理解 缓冲 I/O 的关键。

嵌入式启示:很多「奇怪行为」来自 VFS 与具体 FS 的组合(例如某些 ioctl、O_TMPFILE、dnotify/inotify 与存储无关但干扰排障思路),先确认问题落在 VFS 还是 FS 还是块层。


1.4 页缓存与 address_space

1.4.1 是什么

页缓存 将「文件数据」以页为单位缓存在内存中。每个 inode 关联一个 address_spaceinclude/linux/fs.h),表示该文件在内存中的页集合及一套操作方法(address_space_operations)。

1.4.2 读路径直觉

  • 缓冲读:先查页缓存是否已有对应页;缺页时通过 FS 注册的 readpage / readahead 等从块设备填充。

  • 预读(readahead):顺序读时内核会提前拉取后续页,吞吐会明显好于随机小读。

1.4.3 写路径直觉

  • 普通 write:常把数据拷入 脏页(dirty page) ,标记为脏;真正刷盘由 回写(writeback) 机制异步完成(受 vm.dirty_* 等参数与负载影响)。

  • fsync / fdatasync:强制把该文件相关数据/元数据(依调用)推到持久化语义边界(仍受下层设备缓存影响)。

1.4.4 与嵌入式相关的现象

  • writefsync:掉电可能丢失「你以为已经写进去」的数据。

  • 大量小随机写:脏页分散、元数据更新多,在 eMMC 上延迟与写放大往往比桌面 SSD 更难看。

  • 内存压力:回收、回写、kworker 活动可能与实时任务抢占资源相关,需结合 cgroup 与业务设计。

1.4.5 可读的典型文件

  • mm/filemap.c:页缓存查找、缺页、预读、generic_file_read_iter 等。

  • mm/page-writeback.c:脏页限、回写触发(概念级精读即可)。

诊断/proc/meminfo(Dirty、Writeback)、/proc/vmstatnr_dirtynr_writeback 等)。


1.5 回写与「全局刷盘」

1.5.1 回写

内核在后台把脏页写回块设备;策略与 backing device 、dirty 比例、周期有关。不必背所有 sysctl,但要理解:回写是异步的 ,与进程 write 返回成功不同步。

1.5.2 sync(2)syncfs(2)

  • sync:倾向把整个系统的脏数据刷出(粗粒度,嵌入式上可能造成明显卡顿)。

  • syncfs:针对单个文件系统实例。

设计建议 :关键数据用 fsync/fdatasync 明确边界 ,而不是依赖 sync


1.6 缓冲 I/O、O_DIRECT、mmap 的对比(同行速查)

1.6.1 缓冲 I/O(默认)

  • 优点:内核统一缓存、预读、写合并友好。

  • 缺点:双份数据拷贝(用户态 ↔ 页缓存);一致性边界要靠自己用 fsync 等理清。

1.6.2 O_DIRECT

  • 绕过页缓存,用户缓冲区与设备/FS 约束的对齐要求严格(具体因文件系统与块大小而异)。

  • 适合自管缓存的数据库式负载或已验证缓存有害的场景;踩坑多,上线前要在目标 FS/存储上实测。

1.6.3 mmap

  • MAP_SHARED:修改可见性与回写节奏与页缓存一致;与并发、信号量、文件截断交互要小心。

  • 嵌入式里常见于大块共享、日志、多媒体管线;性能不一定魔法,要看缺页与 TLB 行为。


1.7 块层:bio、请求队列与 blk-mq(5.10 主线)

1.7.1 bio

struct bioinclude/linux/blk_types.h)描述一次块 I/O:目标设备、方向、与一组 bvec(页、偏移、长度)的 scatter-gather 列表。它是通用块层向驱动表达工作的核心抽象之一。

1.7.2 从 FS 到驱动(简化)

文件系统通过块层提交 I/O → 形成 request (在 blk-mq 下与硬件队列 tag 等结合)→ 驱动 queue_rq 或等价入口取出执行。

1.7.3 blk-mq(multi-queue block layer)

5.10 时代 NVMe 与多队列设备已广泛采用 blk-mq :按硬件队列能力组织,减少单队列锁竞争,提高并发。eMMC/SD 等仍是块设备,同样走 blk-mq 框架,但设备本身随机写延迟与队列深度限制更明显,调 scheduler 往往不如改业务写入模式

1.7.4 I/O 调度器(elevator)

块设备队列上可挂 kyber、mq-deadline、none、bfq 等(取决于配置)。对低速 eMMC,mq-deadline 等有时能改善尾延迟;需用基准与 iostat 验证,避免教条。

1.7.5 推荐阅读入口

  • block/blk-core.cblock/blk-mq.cblock/blk-mq-sched.c

  • include/linux/blkdev.hinclude/linux/blk_types.h

** sysfs**:/sys/block/<dev>/queue/schedulernr_requestsmax_sectors_kbread_ahead_kb 等(理解含义后再改)。


1.8 文件系统层(ext4 / f2fs 等)在栈中的位置

1.8.1 同行评估维度(比「哪个快」更重要)

  • 掉电一致性:日志模式、ordered/writeback(概念)、checkpoint 频率(log-structured 类如 f2fs)。

  • 元数据写入频率 :小文件与频繁 fsync 对 eMMC 不友好。

  • 挂载与修复时间 :启动约束、是否接受 fsck

  • 只读根、OTA、A/B 分区:与 FS 特性、double-write 策略是否匹配。

1.8.2 源码阅读提示

  • ext4:fs/ext4/ 中从 inode.cfile.creadpage/writepages 相关路径入手。

  • f2fs:fs/f2fs/ 关注 segment、GC、checkpoint 与块层交互。


1.9 MTD / UBI / UBIFS 与块设备栈的差异(提要)

  • UBIFS 运行在 UBI 之上,UBI 管理擦除块、坏块与卷;LEB/PEB 与磨损均衡是日常设计词汇。

  • 掉电、性能瓶颈、调试工具链与 ext4-on-mmc 不同;若你的产品是这条线,应单独建一张「MTD 栈」图,不要硬套块设备经验。


1.10 持久化与屏障:REQ_PREFLUSH、REQ_FUA、设备写缓存

1.10.1 分层理解「掉电后数据在哪」

  • 应用层 :是否等待 fsync/fdatasync

  • 文件系统层:事务提交点、journal 提交策略。

  • 块设备层 :设备是否有易失写缓存;内核如何通过 REQ_PREFLUSHREQ_FUA 等标志要求刷新或强制落盘(能力依设备与驱动而异)。

同行要能回答:「这条产品路径上,最后一次保证持久化的边界在哪里?」答不出来就容易在客户现场被「掉电损坏」反杀。


1.11 观测与排障工具(建议熟练度分级)

1.11.1 基础(应熟练使用)

  • iostat -xz 1%utilawaitavgqu-sz、每秒读写量;判断瓶颈在设备还是 CPU/锁。

  • vmstat 1:粗看内存与 I/O 等待。

  • /proc/meminfo/proc/vmstat:dirty、writeback、回收相关。

1.11.2 中级

  • pidstat -d 1:按进程看 I/O。

  • /sys/block/<dev>/queue/*:队列与调度器参数(改前记录 baseline)。

1.11.3 进阶

  • blktrace + blkparse / btt:延迟分段。

  • BPF/bpftrace(若环境允许):5.10 已具备较好 BPF 基础,可跟踪块层与 VFS 延迟(需自行编写或采用现成脚本)。

1.11.4 嵌入式现场

若卡顿与 kworker 写回并发**:结合 vmstat/proc/vmstat 中 dirty 相关计数与业务写入模式对齐分析;避免一上来大面积 sysctl「调优」而无假设。


1.12 与 Linux 5.10 对齐的源码阅读路线(按周拆解可参考)

1.12.1 第一周:系统调用到 VFS 读写

  • fs/read_write.cvfs_readvfs_writedo_iter_readdo_iter_write

  • 选一个具体 FS(如 ext4),跟 read_iter / write_iter 如何接到 generic_file_read_itergeneric_file_write_iter

1.12.2 第二周:页缓存与回写

  • mm/filemap.cgeneric_file_read_iter、预读、grab_cache_page_write_begin 等写路径相关函数。

  • mm/page-writeback.c:dirty 背景比与回写触发(概念 + 关键函数名)。

1.12.3 第三周:块层 bio 与 blk-mq

  • block/bio.csubmit_bio 链路。

  • block/blk-mq.c:入队、queue_rq、完成回调;对照你平台上某个驱动的 mmc/nvme 请求处理。

1.12.4 第四周:持久化与工具验证

  • 结合 fsync 与块层 flush 标志读相关 FS 文档与代码路径。

  • iostat + 压力程序验证队列深度、调度器改动的实际效果。


1.13 刻意练习清单(建议实操)

  • 白板/文档 :画你产品上的完整存储链(含分区、FS、是否 overlay),标注掉电边界与 fsync 责任归属。

  • 一致性实验 :写小程序循环 writefsync,在虚拟机或受控环境强制断电,记录文件系统表现;再加 fsync 对比。

  • 延迟分解 :压测时用 iostatpidstat 对齐,区分「设备慢」与「进程/锁慢」。

  • sysfs 假设驱动 :选一个 /sys/block/.../queue 参数,写出前后假设、改值、用同一 workload 记录差异。

  • 分叉对比:若团队同时维护 MMC 与 MTD 产品,用同一维度表格对比掉电语义、磨损、挂载与调试工具。


1.14 延伸阅读与关键词

  • LWN.net :检索 blk-mqwritebackext4 journalf2fs 等主题的历史文章(对理解设计权衡很有帮助)。

  • 内核自带文档Documentation/block/Documentation/filesystems/(以 5.10 树内版本为准)。

  • 关键搜索词address_space_operationswritepageswbc(writeback control)、submit_bioblk_mq_tag_setREQ_OP_WRITEREQ_PREFLUSH


1.15 版本与免责声明

本文以 Linux 5.10 通用主线为参照;各厂商内核可能 backport 或裁剪子系统。生产变更(sysctl、调度器、挂载参数)请在目标硬件与负载下回归测试。

相关推荐
smart19984 天前
创新SSD缓存性能,应用在数据库与VDI业务中性能表现显著提升
存储
七夜zippoe6 天前
DolphinDB数据压缩与存储优化
优化·存储·数据·压缩·dolphindb
审判长烧鸡13 天前
GO时区【2】跨时区应用
go·存储·时区
木斯佳14 天前
HarmonyOS 本地存储实战:用一个记账本案例吃透 RDB 与 KVStore
harmonyos·存储
XSKY星辰天合17 天前
XSKY 与平凯星辰(TiDB)完成联合解决方案互认证,存储+数据库联合交付能力再获验证
数据库·存储
数据存储张24 天前
第2章:基于内存的只读文件系统
文件系统·存储
smart199825 天前
虚拟化授权费用又涨了,Infortrend存储带开源虚拟化应用抢占市场
存储
晴天¥1 个月前
使用Openfiler为达梦数据库集群搭建共享存储
运维·服务器·存储
xcLeigh1 个月前
KES数据库表空间目录自动创建特性详解与存储运维最佳实践
大数据·运维·服务器·数据库·表空间·存储