本页目录

VFS 进阶

覆盖: page cache 与 VFS 交互 → inode 状态机 → file locking (POSIX/OFD) → fsync/fdatasync 保证 → sendfile/splice/vmsplice 零拷贝 内核版本: 2.6 ~ 6.x

Page Cache 与 VFS 交互

inode 与 address_space 的关系

// inode->i_mapping 指向该文件的 page cache
// 这是一个 address_space 结构,管理该文件在内存中的所有缓存页面

// 大多数 inode->i_mapping 指向内嵌的 inode->i_data:
struct inode {
    struct address_space *i_mapping;  // 通常 = &inode->i_data
    struct address_space i_data;      // 内嵌 (block device 文件系统)
};

// 对于块设备的裸读写:
struct block_device {
    struct inode bd_inode;            // 块设备也当 inode 管
    // bd_inode.i_mapping 是块设备自己的 page cache
    // 与文件系统通过 buffer_head 交互
};

inode 状态机

// include/linux/fs.h
#define I_NEW           0   // inode 刚分配, 还未从磁盘读
#define I_DIRTY_SYNC    1   // 元数据 dirty, 需要同步 (如 i_size)
#define I_DIRTY_DATASYNC 2  // 数据 dirty (仅 fdatasync 关心)
#define I_DIRTY_PAGES   3   // i_mapping 中有 dirty 页面
#define I_WILL_FREE     4   // 即将释放
#define I_FREEING       5   // 正在释放中

// inode 生命周期:
// alloc_inode()  → I_NEW
// insert_inode_locked() → 加入 inode hash
// unlock_new_inode() → 清 I_NEW, 唤醒等待者
// ... 活跃期 ...
// iput() → i_count == 0 → evict() → I_FREEING → destroy_inode()

File Locking

POSIX Lock (fcntl)

// POSIX advisory/强制锁
struct flock {
    short l_type;       // F_RDLCK / F_WRLCK / F_UNLCK
    short l_whence;     // SEEK_SET/SEEK_CUR/SEEK_END
    off_t l_start;
    off_t l_len;
    pid_t l_pid;        // 持有锁的进程 PID (GETLK 时填充)
};

fcntl(fd, F_SETLK, &lock);   // 非阻塞
fcntl(fd, F_SETLKW, &lock);  // 阻塞等待
fcntl(fd, F_GETLK, &lock);   // 查询

// 实现: fs/locks.c
// 锁挂在 inode->i_flctx (file_lock_context)
// 检测冲突: posix_locks_conflict() → 遍历 conflict chain
// 进程关闭 fd → 自动释放所有 POSIX lock (无论 fork 关系)

OFD Lock (Open File Description, 3.15+)

// POSIX lock 的问题: 同一个进程的不同 fd 不能各自持锁
// OFD lock 解决: 锁挂在 "打开文件描述" (file) 上, 不是进程
fcntl(fd, F_OFD_SETLK, &lock);

// fork 场景:
//   POSIX: 子进程继承父进程的锁 (因为同一个 PID)
//   OFD:   子进程不继承 (不同 file, 不一定)

FLOCK (BSD Lock)

flock(fd, LOCK_SH);  // 共享
flock(fd, LOCK_EX);  // 排他
flock(fd, LOCK_UN);  // 解锁

// 与 POSIX lock 的区别:
//   FLOCK 挂在 file 上 (不是 inode, 不是进程)
//   同一个 file 的多次 flock 自动关联
//   fork 继承 flock (因为 file 共享)
//   仅 advisory (没有强制锁)

fsync / fdatasync: 持久性保证

// fs/sync.c
SYSCALL_DEFINE1(fsync, unsigned int, fd)do_fsync(fd, 0)vfs_fsync(file, 0)
    ├─ file_write_and_wait_range()  // 1. 回写 dirty page cache
    │   └─ __filemap_fdatawrite_range()do_writepages()
    │       → 每个 dirty folio → a_ops->writepage()
    │   └─ __filemap_fdatawait_range()  // 等回写完成
    │
    └─ vfs_fsync_range()
        └─ f_op->fsync()  // 2. 文件系统: 写 journal / 强制 flush
            → ext4: jbd2 提交事务 + blkdev_issue_flush
            → XFS: 强制日志刷盘 + flush
            → NFS: COMMIT 操作

// fdatasync (FDATASYNC): 只刷数据部分 (不刷 i_atime/i_mtime)
//   → vfs_fsync_range(file, start, end, 1)
//   → 文件系统可以跳过时间戳同步

零拷贝: sendfile / splice / vmsplice

// sendfile(): 文件 → socket, 不经过用户空间
sendfile(out_fd, in_fd, &offset, count)do_sendfile()
    └─ splice_file_to_pipe() → pipe buffer ← 文件内容
        └─ splice_pipe_to_socket() → pipe buffer → socket

// splice(): 两个 fd 之间管道传输 (内核空间)
splice(in_fd, NULL, pipefd[1], NULL, count, 0);  // fd → pipe
splice(pipefd[0], NULL, out_fd, NULL, count, 0);  // pipe → fd

// vmsplice(): 用户空间内存 → pipe (与 splice 配合)
vmsplice(pipefd[1], iov, nr_segs, SPLICE_F_GIFT);
  // 用户内存的页被"送给"内核 (不再是用户态的了)
  // → pipe 引用这些页 → splice 到 socket → 零拷贝发网卡

DMA 零拷贝路线

零拷贝路径对比:CPU 拷贝次数从 2 次降到 0 次

路径 A · 传统读写 磁盘 page cache 用户缓冲区 socket buffer 网卡 4 次上下文切换 · 2 次 CPU 拷贝

路径 B · sendfile 磁盘 page cache socket buffer 网卡 2 次上下文切换 · 1 次 CPU 拷贝

路径 C · sendfile + DMA scatter-gather(推荐) 磁盘 page cache 网卡(DMA 直接取) 2 次上下文切换 · 0 次 CPU 拷贝!

拷贝次数一路走低:传统 2 次 → sendfile 1 次 → sendfile+DMA 0 次;上下文切换 4 → 2 → 2。 0 拷贝依赖网卡支持 TX checksum offload + SG——几乎所有现代网卡都满足。

调试

# inode cache 统计
cat /proc/sys/fs/inode-nr      # nr_inodes, nr_free_inodes
cat /proc/sys/fs/inode-state   # 详细 inode 使用状态

# 查看进程的文件锁
cat /proc/locks                 # 所有活跃的 POSIX/OFD/FLOCK
lslocks                         # 更好的人类可读格式

# 追踪 fsync 行为
strace -e trace=fsync,fdatasync -p <pid>

参考与延伸

  • 内核文档⁠: Documentation/filesystems/locks.rst, Documentation/filesystems/vfs.rst
  • LWN:
    • "Better file locking with OFD locks" (lwn.net/Articles/586904/)
    • "Splice and sendfile" (lwn.net/Articles/178199/)
  • 源码文件⁠:
    • fs/locks.c — POSIX/OFD/FLOCK 实现
    • fs/sync.c — fsync/fdatasync
    • fs/splice.c — splice/sendfile/vmsplice
    • fs/inode.c — inode 生命周期

关键词: VFS, inode state, POSIX lock, OFD lock, FLOCK, fsync, fdatasync, sendfile, splice, zero-copy