问题

就是linux的文件系统,Direct I/O, 块设备,字符设备,inode,扇区,block,这些概念我一直都是笼统的认知,没有一个系统性的,具体的认知,我该怎么动手或者学习,能建立一个完善的系统

你已经有了一些零散的概念,这是很好的基础。要建立系统性的、具体的认知,关键在于动手实践理论溯源相结合,把抽象的概念落实到可观察、可操作的对象上。下面我为你规划一条从入门到深入的学习路径,每一步都配有具体的动作,帮助你真正把这些概念印在脑子里。


第一阶段:精确定义,理清层次关系

先确保每个概念在你的脑海里有一个清晰的“坐标”——它属于哪一层?它的职责是什么?

概念 所属层次 一句话本质
扇区 (Sector) 硬件层 硬盘(包括SSD)物理上可寻址的最小单元,传统是512字节,现代多为4K字节。
块 (Block) 文件系统层 文件系统逻辑上管理的存储单元,是扇区的整数倍(如4K)。文件系统以块为单位分配磁盘空间。
inode 文件系统层 存储文件元数据(大小、权限、时间戳、数据块指针)的数据结构,每个文件/目录都有一个唯一的inode号。文件名并不在inode里。
字符设备 设备驱动层 字节流方式访问的设备(如键盘、串口)。没有缓冲区,不能随机访问。对应/dev下的c类型文件。
块设备 设备驱动层 为单位、可以随机访问的设备(如硬盘)。有缓冲区,支持挂载文件系统。对应/dev下的b类型文件。
Direct I/O 文件系统接口层 一种I/O模式,绕过Page Cache,数据直接在用户缓冲区和磁盘之间传输(通常通过DMA)。用于数据库等自备缓存的应用。
Page Cache 内核内存管理层 内核用内存缓存磁盘数据,缓冲I/O就是通过它实现。延迟写、预读都依赖它。

动作1:用命令印证概念

  • 查看磁盘扇区大小:fdisk -l /dev/sdacat /sys/block/sda/queue/hw_sector_size
  • 查看文件系统块大小:stat -f /tune2fs -l /dev/sda1 | grep 'Block size'
  • 查看文件inode信息:stat somefile.txt (观察Inode值、块大小、占用块数)
  • 查看设备文件类型:ls -l /dev/sda (b开头是块设备),ls -l /dev/tty (c开头是字符设备)

第二阶段:动手编程,观察I/O路径

用C语言写几个小程序,对比不同I/O方式的行为,这是最直观的“内窥镜”。

2.1 缓冲I/O vs Direct I/O

写一个程序,分别用普通write()和带O_DIRECT标志的open()写文件,并用strace跟踪系统调用。

// direct_io_test.c
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
    // 普通写(缓冲I/O)
    int fd1 = open("normal.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
    write(fd1, "hello", 5);
    close(fd1);

    // Direct I/O(注意:缓冲区地址和大小必须按块对齐)
    char *buf;
    posix_memalign((void**)&buf, 512, 4096);  // 512字节对齐
    int fd2 = open("direct.txt", O_WRONLY | O_CREAT | O_DIRECT | O_TRUNC, 0644);
    write(fd2, buf, 4096);  // 必须写入块大小的整数倍
    close(fd2);
    free(buf);
    return 0;
}

编译运行并用strace观察:

strace -e trace=open,write ./direct_io_test

你会看到open时多了O_DIRECT标志,write系统调用的行为没有区别,但内核内部路径完全不同。

动作2:再用time命令粗略感受性能差异(第一次写由于cache可能差异不大,可以读大文件测试)。

2.2 字符设备 vs 块设备

dd命令分别读写块设备和字符设备,感受差异。

# 读块设备(/dev/sda)——会通过Page Cache,可能读出很多数据
dd if=/dev/sda of=/dev/null bs=1M count=10

# 读字符设备(/dev/urandom)——每次读都产生新数据,不经过cache
dd if=/dev/urandom of=/dev/null bs=1M count=10

动作3:编写简单程序打开/dev/tty(字符设备),尝试lseek——你会发现不能定位,因为字符设备是流式的。而打开/dev/sda(块设备),lseek可以随意移动。


第三阶段:深入文件系统内部结构

3.1 用工具解剖磁盘上的文件系统

选择一个空闲分区(或创建一个磁盘镜像文件),用dd生成一个空白镜像,然后用mkfs.ext4格式化,再用debugfsdumpe2fs查看内部结构。

dd if=/dev/zero of=myfs.img bs=1M count=100
mkfs.ext4 myfs.img
dumpe2fs myfs.img   # 查看超级块、块组信息

动作4:挂载镜像,创建几个文件,然后用debugfsstat命令查看inode细节(如数据块指针)。

sudo mount -o loop myfs.img /mnt
cd /mnt
echo "hello" > test.txt
cd ..
sudo umount /mnt

debugfs myfs.img
debugfs:  stat test.txt
# 观察里面的 BLOCKS: 列出的数据块号

你会看到文件内容存放在哪些块里,块号与dumpe2fs中看到的块位图对应。

3.2 理解目录的本质

目录其实也是一个特殊文件,内容就是文件名到inode号的映射表。可以用ls -i查看inode号,再用debugfsls -l命令查看目录内容。

debugfs myfs.img
debugfs:  ls -l /
# 你会看到每个文件名后面跟着inode号和文件类型

动作5:尝试创建一个硬链接,观察inode号不变且链接计数增加;创建一个软链接,观察它是一个独立的小文件,内容指向目标路径。


第四阶段:追踪内核源码(可选但推荐)

当你对概念有感性认识后,阅读内核源码能帮你建立最牢固的理解。不必通读全部,而是针对关键数据结构进行“点穴式”阅读。

4.1 关键数据结构在源码中的位置(以Linux 5.x为例)

  • inodestruct inode 定义在 include/linux/fs.h。关注 i_mode(文件类型)、i_sizei_blocks(占用的块数)、i_data(数据块指针)。
  • 文件struct file 也在 fs.h,代表一个打开的文件实例,包含 f_pathf_op(操作函数表)。
  • 块设备struct block_deviceinclude/linux/blkdev.h,关联到请求队列和磁盘分区。
  • 字符设备struct cdevinclude/linux/cdev.h,通过设备号与inode关联。

4.2 追踪一次读写的内核路径

使用ftracetracepoints观察函数调用。

# 安装 trace-cmd
trace-cmd record -e block:* -e ext4:* -e filemap:* cat /mnt/test.txt
trace-cmd report

这会显示从VFS (ext4_file_read_iter) 到块层 (block_rq_insert) 再到驱动层的完整路径。你可以结合源码阅读,理解每一步在做什么。


第五阶段:画图总结,形成知识网络

当你完成了上面的实践,试着在一张大纸上画出从用户程序到SSD的完整路径,并在每个环节标出相关概念:

  • 用户空间:应用程序、C库
  • 系统调用接口read/write(对应VFS)
  • VFSstruct file, struct inode, struct dentry
  • 具体文件系统:ext4(分配块、更新inode、处理日志)
  • Page Cacheaddress_space、脏页回写
  • 块层:I/O调度、请求合并
  • 设备驱动:块设备 (/dev/sda)、字符设备 (/dev/tty)、NVMe驱动
  • 硬件:扇区、FTL(SSD内部映射)

把每个概念用箭头连接,并标注它们之间的关系。这张图就是你头脑中知识体系的外化。


学习资源推荐

    • 《Linux内核设计与实现》(第三版)——对VFS、块I/O、设备驱动讲解清晰。
    • 《深入理解Linux内核》——更详细,可作为参考。
    • 《Linux系统编程》——讲解系统调用和文件I/O的细节。
  • 在线
    • Linux内核文档:Documentation/filesystems/Documentation/block/
    • The Linux Kernel documentation 的Filesystem部分。
    • LWN.net 上的内核机制分析文章。

最后一点建议

不要试图一次性记住所有细节。按照上面的阶段,每周攻克一两个概念,动手做对应的实验。当你在实践中遇到问题(比如O_DIRECT为什么需要对齐?为什么/dev/zero是字符设备?),再回头查资料或源码,这样记忆会非常深刻。

如果你能把这些实验记录成笔记,甚至写一个“Linux文件系统探险”系列博客,知识就真正属于你了。动手吧!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐