存储功能
存储 |
---|
文件和目录访问 |
虚拟文件系统 |
页面缓存 |
逻辑文件系统 |
块设备 |
存储驱动程序 |
存储功能通过文件和目录提供对各种存储设备的访问。大多数存储都是持久性的,例如闪存、SSD 和传统硬盘。另一种存储是临时的。文件系统提供了一种抽象来将信息组织成由唯一名称标识的独立数据块(称为文件)。每个文件系统类型定义自己的结构和逻辑规则,用于管理这些信息组及其名称。Linux 支持大量不同的文件系统类型,包括本地和远程、本地和来自其他操作系统的文件系统。为了适应这种差异,内核定义了一个通用的顶层,即虚拟文件系统(VFS)层。
四种基本文件访问系统调用
- man 2 open ↪ do_sys_open id - 按名称打开文件并返回一个文件描述符(fd)。以下函数对 fd 进行操作。
- man 2 close ↪ close_fd id
- man 2 read ↪ ksys_read id
- man 2 write ↪ ksys_write id
Linux 和 UNIX 中的文件不仅仅是持久存储上的物理文件。文件接口用于访问管道、套接字和其他伪文件。
🔧待办事项
- man 2 readlink , man 2 symlink , man 2 link
- man 3 readdir ⇾ man 2 getdents
- man 7 path_resolution
- man 2 fcntl – 操作文件描述符
⚙️ 文件和目录内部结构
📚 文件和目录参考
文件锁是允许进程协调对共享文件的访问的机制。这些锁有助于防止多个进程或线程同时尝试访问同一个文件时发生冲突。
💾历史:强制锁定功能在 Linux 5.15 及更高版本中不再受支持,因为该实现不可靠。
⚲ API
- man 8 lslocks – 列出本地系统锁
- man 3 lockf – 对打开的文件应用、测试或删除 POSIX 锁
- man 2 flock – 对打开的文件应用或删除 BSD 建议锁
- man 2 fcntl – 操作文件描述符
- F_SETLK id – 建议记录锁
- F_OFD_SETLK id – 打开文件描述符锁
- flock id – 锁参数
⚙️ 内部结构
🚀高级功能
AIO
- https://lwn.net/Kernel/Index/#Asynchronous_IO
- man 2 io_submit man 2 io_setup man 2 io_cancel man 2 io_destroy man 2 io_getevents
- uapi/linux/aio_abi.hinc
- fs/aio.csrc
- io/aio ltp
🌱自 2019 年 5 月发布 5.1 版本以来的新功能
- https://blogs.oracle.com/linux/an-introduction-to-the-io_uring-asynchronous-io-framework
- https://thenewstack.io/how-io_uring-and-ebpf-will-revolutionize-programming-in-linux/
- io_uring_enter id io_uring_setup id io_uring_register id
- linux/io_uring.hinc
- uapi/linux/io_uring.hinc
- fs/.csrc
- https://lwn.net/Kernel/Index/#io_uring
- io_uring ltp
允许对多个文件描述符进行非阻塞访问。
高效事件轮询epoll
⚲ API
- uapi/linux/eventpoll.hinc
- man 7 epoll
- man 2 epoll_create ↪ do_epoll_create id
- man 2 epoll_ctl ↪ do_epoll_ctl id
- man 2 epoll_wait ↪ do_epoll_wait id
⚙️ 内部结构
💾 历史:select 和 poll 系统调用源于 UNIX
⚲ API
⚙️ 内部结构
🚀 高级特性
矢量 I/O,也称为分散/收集 I/O,是一种输入输出方法,通过该方法,单个过程调用可以按顺序从多个缓冲区读取数据并将其写入单个数据流,或者从数据流读取数据并将其写入多个缓冲区,如缓冲区向量中定义的那样。分散/收集指的是从给定缓冲区集中收集数据或将数据分散到给定缓冲区集中的过程。矢量 I/O 可以同步或异步运行。使用矢量 I/O 的主要原因是效率和便捷性。
⚲ API
⚙️ 内部结构
📚 参考资料
- 快速分散/收集 I/O,GNU C 库
- https://lwn.net/Kernel/Index/#Vectored_IO
- https://lwn.net/Kernel/Index/#Scattergather_chaining
The 虚拟文件系统 (VFS) 是一个抽象层,位于具体的逻辑文件系统之上。VFS 的目的是允许客户端应用程序以统一的方式访问不同类型的逻辑文件系统。例如,VFS 可以用来透明地访问本地和 网络存储 设备,而客户端应用程序不会察觉到差异。它可以用来弥合 Windows、经典 Mac OS/macOS 和 Unix 文件系统之间的差异,以便应用程序可以访问这些类型本地文件系统上的文件,而无需知道它们正在访问哪种类型的文件系统。VFS 指定了内核和逻辑文件系统之间的接口(或“契约”。因此,只需满足契约,就可以轻松地为内核添加对新文件系统类型的支持。
🔧 TODO: vfsmount id, vfs_create id, vfs_read id, vfs_write id
📚 VFS 参考资料
一个 文件系统(或文件系统)用于控制数据的存储和检索方式。如果没有文件系统,存储区域中的信息将是一个很大的数据主体,没有办法区分一个信息片段的结束和下一个信息片段的开始。通过将数据分成单独的片段,并给每个片段一个名称,可以轻松地分离和识别信息。每个数据组被称为“文件”。用来管理信息组及其名称的结构和逻辑规则被称为“文件系统”。
有很多不同类型的文件系统。每个文件系统都有不同的结构和逻辑,以及速度、灵活度、安全性、大小等方面的特性。有些文件系统是为特定应用程序而设计的。例如,ISO 9660 文件系统专门为光盘而设计。
文件系统可以在许多不同类型的存储设备上使用。每个存储设备都使用不同类型的介质。如今最常用的存储设备是 SSD。曾经使用过的其他介质包括硬盘、磁带、光盘和 。在某些情况下,计算机的主内存(RAM)用于创建短期使用的临时文件系统。原始存储被称为块设备。
Linux 支持许多不同的文件系统,但块设备上系统磁盘的常用选择包括 ext* 系列(如 ext2、ext3 和 ext4)、XFS、ReiserFS 和 btrfs。对于没有 闪存转换层 (FTL) 或 内存技术设备 (MTD) 的原始闪存,有 UBIFS、JFFS2 和 YAFFS 等等。 SquashFS 是一种常见的压缩只读文件系统。NFS 和其他网络 FS 在段落 网络存储 中有更详细的描述。
⚲ Shell 接口
- cat /proc/filesystems
- ls /sys/fs/
- man 8 mount
- man 8 umount
- man 8 findmnt
- man 1 mountpoint
- man 1 df
基础设施 ⚲ API 函数 register_filesystem id 注册结构体 file_system_type id 并将它们存储在链接列表中 ⚙️ file_systems id。函数 ext4_init_fs id 注册 ext4_fs_type id。文件系统打开的操作被称为挂载:ext4_mount id
⚙️ 内部结构
📚 参考资料
页缓存或磁盘缓存是来自辅助存储设备(如硬盘驱动器)的内存页的透明缓存。操作系统在主内存的未使用部分中保留页缓存,从而可以更快地访问缓存页的内容并提高整体性能。页缓存由内核实现,对应用程序来说大多是透明的。
通常,所有未直接分配给应用程序的物理内存都被操作系统用于页面缓存。由于内存否则将处于闲置状态,并且在应用程序请求时可以轻松回收,因此通常没有相关的性能损失,操作系统甚至可能会将此类内存报告为“空闲”或“可用”。页面缓存还有助于写入磁盘。在将数据写入磁盘期间已修改的主内存中的页面被标记为“脏”,并且必须在它们可以被释放之前刷新到磁盘。当发生文件写入时,会查找支持特定块的页面。如果它已经在页面缓存中找到,则写入将完成到主内存中的该页面。否则,如果写入完全落在页面大小边界上,则该页面甚至不会从磁盘读取,而是被分配并立即标记为脏。否则,将从磁盘获取该页面(或这些页面),并进行请求的修改。
并非所有缓存的页面都可以写入,因为程序代码通常被映射为只读或写时复制;在后一种情况下,对代码的修改仅对进程本身可见,不会写入磁盘。
⚲ API
- man 2 fsync ↪ do_fsync id
- man 2 sync_file_range ↪ ksys_sync_file_range id
- man 2 syncfs ↪ sync_filesystem id
📚 参考资料
更多
- DAX 的未来 - 直接访问绕过缓存
- Linux 内核 2.4 内部机制中的 Linux 页面缓存
🚀高级功能
将数据写入存储和读取是非常占用资源的操作。复制内存也是一项耗时且占用 CPU 的操作。一组避免复制操作的方法被称为零拷贝。零拷贝方法的目标是在系统内进行快速高效的数据传输。
第一个也是最简单的方法是管道,由 shell 中的运算符 "|" 调用。而不是将数据写入临时文件并读取,而是通过管道有效地传递数据,绕过存储。第二种方法是tee。
⚲ 系统调用
⚲ API 和 ⚙️ 内部机制
- man 2 pipe2 ↪ do_pipe2 id - 创建管道
- man 2 tee ↪ do_tee id- 复制管道内容
- 调用 link_pipe id
- man 2 sendfile ↪ do_sendfile id - 在文件描述符之间传输数据,输出可以是套接字。用于网络存储 和服务器。
- man 2 copy_file_range ↪ vfs_copy_file_range id - 在文件之间传输数据
- man 2 splice ↪ do_splice id - 将数据拼接进/出管道。
- 关于哪一端是管道,有三种情况
- do_splice_from id - 只有输入是管道
- do_splice_to id - 只有输出是管道。
- splice_pipe_to_pipe id - 两者都是管道
- man 2 vmsplice ↪
- vmsplice_to_pipe id – 将用户页面拼接进管道
- vmsplice_to_user id – 将管道拼接进用户页面
⚲ API
⚙️ 内部结构
🔧 TODO: zerocopy_sg_from_iter id 从 iov_iter 构建零拷贝 skb 数据报。用于 tap_get_user id 和 tun_get_user id.
📚 参考资料
- man 7 pipe
- man 7 fifo
- splice 和管道 doc
- splice(系统调用)
- LTP: pipe ltp, pipe2 ltp, tee ltp, sendfile ltp, copy_file_range ltp, splice ltp, vmsplice ltp
Linux 存储基于块设备。
块设备提供对硬件的缓冲访问,始终允许读取或写入任何大小的块(包括单个字符/字节),并且不受对齐限制的影响。它们通常用于表示诸如硬盘之类的硬件。
⚲ 接口
- linux/genhd.hinc
- linux/blk_types.hinc
- bio id – 块层和下层的主要 I/O 单位
- linux/bio.hinc
- block_deviceid
- alloc_disk_node id 分配 gendisk id
- add_diskid
- block_device_operationsid
- register_blkdevid
- bio id - 块层和下层(即驱动程序和堆叠驱动程序)的主要 I/O 单位
- requestid
- request_queueid
⚙️ 内部结构。
👁 示例
- drivers/block/brd.c src - 小型 RAM 支持的块设备驱动程序
- drivers/block/null_blksrc
设备映射器是内核提供的框架,用于将物理块设备映射到更高级别的“虚拟块设备”。它构成了 LVM2、软件 RAID 和 dm-crypt 磁盘加密的基础,并提供额外的功能,例如文件系统快照。
设备映射器通过将数据从虚拟块设备(由设备映射器本身提供)传递到另一个块设备来工作。数据也可以在传输过程中进行修改,例如,在设备映射器提供磁盘加密的情况下进行修改。
需要创建新映射设备的用户空间应用程序通过 libdevmapper.so
共享库与设备映射器通信,该库反过来向 /dev/mapper/control
设备节点发出 ioctl。
设备映射器提供的功能包括线性、条带化和错误映射,以及加密和多路径目标。例如,两个磁盘可以通过一对线性映射(每个磁盘一个)连接到一个逻辑卷中。另一个例子是,crypt 目标使用 Linux 内核的加密 API 对通过指定设备的数据进行加密。
以下映射目标可用
- cache - 允许通过使用固态驱动器 (SSD) 作为硬盘驱动器 (HDD) 的缓存来创建混合卷
- crypt - 使用 Linux 内核的加密 API 提供数据加密
- delay - 将读取和/或写入到不同的设备延迟(用于测试)
- era - 以类似于线性目标的方式运行,同时跟踪在用户定义的时间段内写入的块
- error - 模拟所有映射块的 I/O 错误(用于测试)
- flakey - 模拟周期性的不可靠行为(用于测试)
- linear - 将连续的块范围映射到另一个块设备
- mirror - 映射镜像逻辑设备,同时提供数据冗余
- multipath - 通过使用其路径组支持多路径设备的映射
- raid - 提供与 Linux 内核的软件 RAID 驱动程序 (md) 的接口
- snapshot 和 snapshot-origin - 用于创建 LVM 快照,作为基础复制写入方案的一部分
- striped - 将数据跨物理设备条带化,以条带数量和条带化块大小作为参数
- zero - 等效于
/dev/zero
,所有读取都返回零块,写入被丢弃
📚 参考资料
blk-mq API 通过利用多个队列进行并行处理来提高 IO 性能,从而解决了传统单队列设计中的瓶颈。它使用软件队列来调度、合并和重新排序请求,并使用硬件队列与设备直接接口。如果硬件资源有限,请求将暂时排队以供以后调度。
⚲ 接口
- linux/blk-mq.hinc
- blk_mq_hw_ctx id – 硬件调度队列
⚙️ 内部结构
- block/blk-mq.hsrc
- blk_mq_ctx id – 软件暂存队列
- block/blk-mq.csrc
- ...
📖 参考资料
I/O 调度(或磁盘调度)是内核选择的方法,用于决定以何种顺序将块 I/O 操作提交到存储卷。I/O 调度通常需要处理硬盘驱动器,这些驱动器对放置在远离磁盘磁头当前位置的请求(此操作称为查找)的访问时间很长。为了最大限度地减少对系统性能的影响,大多数 I/O 调度程序实现了电梯算法的变体,该算法重新排序传入的随机排序请求,以便以最小的机械臂/磁头移动来访问相关数据。
可以使用特定块设备的 sysfs 文件系统中的 /sys/block/<block_device>/queue/scheduler
文件在运行时切换特定块设备使用的 I/O 调度程序。某些 I/O 调度程序还具有可调参数,可以通过 /sys/block/<block_device>/queue/iosched/
中的文件进行设置。
⚲ 接口
⚙️ 内部结构
- block/elevator.csrc
- block/Kconfig.ioschedsrc
- block/bfq-iosched.csrc
- block/kyber-iosched.csrc
- block/mq-deadline.csrc
- include/trace/events/block.hinc
📖 参考资料
- I/O 调度
- 电梯算法
- 切换调度程序 doc
- BFQ - 预算公平排队 doc
- 截止日期 IO 调度程序可调参数 doc
- https://www.cloudbees.com/blog/linux-io-scheduler-tuning/
- https://wiki.ubuntu.com/Kernel/Reference/IOSchedulers
📖 参考资料
📚 进一步阅读
🔧待办事项
⚙️ 内部结构
- drivers/scsi src - 小型计算机系统接口
- drivers/virtiosrc
- drivers/sdio src - 安全数字输入输出
- drivers/nvmem src - 非易失性存储设备,如 EEPROM、Efuse
- drivers/mtd src - 用于🤖嵌入式设备的内存技术设备
NVM Express 驱动程序提供了对计算机 非易失性存储器 的访问。本地存储通过 PCI Express 总线连接。PCI NVMe 设备驱动程序的入口点是 nvme_init id。远程存储驱动程序称为目标,本地 代理 驱动程序称为主机。 结构 连接远程目标和本地主机。结构可以基于 RDMA、TCP 或 光纤通道 协议。
⚲ API
⚙️ 内部实现:
⚲ 接口
- drivers/nvme/host/nvme.hsrc
- nvme_init_ctrl id 初始化 NVMe 控制器结构 nvme_ctrl id,包含操作 nvme_ctrl_ops id
- nvme_scan_work id 的一个子程序使用 device_add_disk id 添加一个新的磁盘。
- nvme_init_ctrl id 初始化 NVMe 控制器结构 nvme_ctrl id,包含操作 nvme_ctrl_ops id
- nvme_init id - 本地 PCI nvme 模块初始化。
- nvme_core_init id - 模块初始化。
结构
⚲ 接口
- drivers/nvme/host/fabrics.hsrc
- nvmf_init id - 结构模块初始化。
⚙️ 内部实现
- nvmf_init id - 结构模块初始化。
⚲ 接口: drivers/nvme/target/nvmet.h src
- nvmet_init id - 模块初始化。
- fcloop_init id - 环回测试模块初始化,可用于测试 NVMe-FC 传输接口。
👁 例如: nvme_loop_init_module id nvme 环回
- nvme_loop_transport id - 结构操作
- nvme_loop_ops id - 目标操作
附录
[edit | edit source]🚀 高级
- man 1 pidstat – 报告任务统计信息。
- /proc/self/io – 进程的 I/O 统计信息(参见 man 5 proc)。
💾 历史存储驱动程序
📖 关于存储的更多阅读