在 Linux 内核层面,“增删改查”实际上是文件系统(File System)的核心操作


  1. 引言:界定范围(VFS 与 具体文件系统如 Ext4),核心概念(用户态到内核态的转换)。
  2. 核心数据结构:介绍 Inode, Dentry, Superblock, File Object。
  3. 架构全景:VFS 抽象层的作用。
  4. 深度解析 CRUD
    • Create (创建):Inodes 分配与目录项链接。
    • Read (读取):页缓存(Page Cache)与缺页中断。
    • Update (写入):脏页(Dirty Page)、回写机制与日志(Journaling)。
    • Delete (删除):硬链接计数与资源释放。
  5. 总结:一张图概括流程。

一、 核心概念与数据结构 (The Anatomy)

在深入 CRUD 之前,必须先理解 Linux 文件系统的“四大金刚”。Linux 使用 VFS (Virtual File System) 来屏蔽底层不同文件系统(Ext4, XFS, NTFS)的差异。

  1. Superblock (超级块)
    • 作用:存储整个文件系统的元数据(如文件系统大小、块大小、空闲块数量)。
    • 类比:停车场的总控室,知道整个停车场有多少车位,剩下多少车位。
  2. Inode (索引节点)
    • 作用这是文件的灵魂。存储文件的元数据(权限、所有者、大小、时间戳)以及数据块的指针(即数据在磁盘哪个位置)。注意:Inode 不包含文件名。
    • 类比:车位上的详细档案卡,记录了这辆车是谁的、停了多久,但没写车牌号(文件名)。
  3. Dentry (目录项)
    • 作用:连接文件名和 Inode。Linux 内核维护一个 Dentry Cache (dcache) 来加速路径查找。
    • 类比:停车场系统的查找表,记录了“车牌号(文件名)”对应“档案卡编号(Inode号)”。
  4. Data Block (数据块)
    • 作用:真正存储文件内容的地方。

二、 架构层级 (The Architecture)

用户发起的操作流程如下:
用户程序 (User Space) -> 标准库 (glibc) -> 系统调用 (System Call) -> VFS 层 -> 具体文件系统 (如 Ext4) -> 通用块设备层 -> 磁盘驱动 -> 物理磁盘


三、 增 (Create):从无到有的过程

场景:用户执行 touch myfile.txt 或代码调用 open("myfile.txt", O_CREAT)

底层原理

  1. 路径解析:内核通过 VFS 的 path_lookup 逐级解析路径。
  2. 寻找父目录:找到目标目录的 Inode。
  3. 分配 Inode
    • 内核在Inode Bitmap(Inode 位图)中查找一个空闲位。
    • 分配一个新的 Inode 结构体,初始化元数据(权限、时间、UID/GID)。
  4. 创建 Dentry
    • 在内存中创建一个新的 Dentry 对象,将文件名 "myfile.txt" 与新申请的 Inode 绑定。
  5. 写入父目录
    • 将这个 Dentry 信息写入父目录的数据块中(因为目录本质上也是一个文件,里面存的是文件名到 Inode 号的映射列表)。
  6. 持久化 (Journaling)
    • 为了防止断电导致数据不一致,Ext4 等日志文件系统会先将上述元数据变更写入 Journal (日志区),成功后再刷入磁盘的实际位置。

核心点:创建文件主要是在操作元数据(Inode 和 Dentry),此时通常还没分配实际存储数据的数据块(Data Block)。


四、 查 (Read):缓存为王

场景:用户执行 cat myfile.txt 或代码调用 read(fd, buf, size)

底层原理

  1. 查找文件对象:通过文件描述符(fd)找到内核中的 struct file 对象,进而找到 Dentry 和 Inode。
  2. 页缓存 (Page Cache) 检查
    • Linux 不会直接从磁盘读数据。它先检查内核的 Page Cache(内存)中是否已经缓存了该文件的对应页。
    • 命中 (Hit):直接将内存中的数据拷贝到用户态 buffer,速度极快(纳秒级)。
    • 未命中 (Miss):触发缺页异常 (Page Fault) 或者内核启动预读机制。
  3. 磁盘寻址
    • 文件系统驱动(如 Ext4)根据 Inode 中的Extent Tree(或旧式的块指针)计算逻辑偏移量对应的物理磁盘块号。
  4. DMA 传输
    • CPU 发指令给磁盘控制器,通过 DMA(直接内存访问)将数据从磁盘搬运到内核的 Page Cache 中。
  5. 拷贝给用户
    • 数据进入 Page Cache 后,内核再将其 copy_to_user 到用户程序的内存空间。

核心点:读取操作是“内存优先”的。此时内存是磁盘的缓存。


五、 改 (Update):延迟写入与脏页

场景:代码调用 write(fd, buf, size)

底层原理

  1. 写入 Page Cache
    • 同样,数据不是直接写到磁盘的。
    • 内核将用户数据拷贝到 Page Cache 对应的页中。
    • 将该页标记为 Dirty (脏页)。此时 write 系统调用通常就会返回成功(这叫 Write-back 策略)。
  2. 延迟分配 (Delayed Allocation)
    • 为了优化磁盘碎片,Ext4 可能不会立即决定数据存放在磁盘哪个块,而是等到真正要刷盘时,再一次性分配连续的磁盘块。
  3. 回写 (Writeback)
    • 脏页会在内存中停留几十秒(默认 30秒)。
    • 由内核线程 pdflushkworker 周期性地,或者在内存不足时,将脏页刷入磁盘。
  4. 日志保障 (Journaling)
    • 如果是 data=ordered 模式(默认),内核会确保数据块先落盘,再更新元数据(Inode),最后更新日志。这保证了崩溃重启后不会看到垃圾数据。

核心点:写入操作极其依赖 Page Cache 和 Journal。用户以为写完了,其实还在内存里飘着。


六、 删 (Delete):只是断开了链接

场景:用户执行 rm myfile.txt 或代码调用 unlink("myfile.txt")

底层原理

  1. 检查权限:检查用户是否有权限修改父目录(因为删除文件本质是修改父目录的内容)。
  2. 删除 Dentry
    • 从内核的 Dcache 中移除该条目。
    • 从父目录的数据块中移除文件名到 Inode 的映射记录。
  3. 减少链接计数 (i_nlink)
    • 将该文件的 Inode 结构中的 i_nlink 计数器减 1。
  4. 真正的释放
    • 内核检查 i_nlink 是否为 0。
    • 并且检查该文件的打开引用计数(Open file count)是否为 0(即是否有进程正打开着这个文件)。
    • 如果都为 0:
      • 释放数据块:在 Block Bitmap 中将对应位标记为空闲。
      • 释放 Inode:在 Inode Bitmap 中将对应位标记为空闲。
      • 注意:磁盘上的实际数据内容通常不会被抹去(除非使用 shred 等工具),只是标记为“可覆盖”。

核心点rm 只是删除了目录项(名字)。只有当所有硬链接都被删除且没有进程打开该文件时,空间才会被释放。


七、 总结图表

操作 关键系统调用 涉及的核心组件 关键机制
Create open, mkdir Inode Bitmap, Dentry 元数据操作,先写日志,再写磁盘
Read read, mmap Page Cache 缓存命中,未命中则 DMA 读取,零拷贝优化
Update write Page Cache, Dirty Bit Write-back (回写),延迟分配,脏页刷盘
Delete unlink i_nlink, Superblock 引用计数,软删除,仅仅修改位图状态
posted on 2025-12-22 09:32  LeeHang  阅读(0)  评论(0)    收藏  举报