DAY 11-文件 IO 与零拷贝

0 阅读4分钟

1. 文件描述符:一切 IO 的起点

问:文件描述符是什么?

一个非负整数,本质是进程文件描述符表的索引。每个进程的 PCB 里都有一个 files 指针,指向一张表,这张表记录了进程打开的所有文件。

进程 PCB → files 指针 → 文件描述符表
                         ├── fd 0 → 标准输入
                         ├── fd 1 → 标准输出
                         ├── fd 2 → 标准错误
                         └── fd 3 → 你打开的文件/socket

新打开的文件描述符从最小可用整数开始分配。ulimit -n 看上限,/proc/[PID]/fd 看具体进程。

在 IPC 开发中,你的程序同时操作着摄像头设备(/dev/video0)、网络 socket、SD 卡录像文件,它们都通过 fd 来管理。资源耗尽报错 "too many open files" 时,你就知道是在排查什么了。


2. 系统调用 vs 库函数:到底差在哪?

问:readfread 有什么区别?

read 是系统调用,直接进内核,每次都有用户态↔内核态切换开销。fread 是库函数,内部有缓冲区,大部分数据从缓冲区取,满了才真正调用 read

系统调用(read)库函数(fread)
是否进内核每次都进缓冲区满了才进
用户态缓冲区有(stdio buffer)
适合场景大块数据高频小数据

所以:写日志用 fprintf(小数据高频),读视频帧用 readmmap(大数据块)。


3. 零拷贝:高性能 IO 的核心

问:什么是零拷贝?为什么需要它?

传统方式从磁盘读文件再通过网络发出去,数据要走 4 次拷贝:

硬盘 → (DMA) → Page Cache → (CPU) → 用户缓冲区 → (CPU) → Socket 缓冲区 → (DMA) → 网卡

4 次拷贝,4 次上下文切换。CPU 成了"搬运工",浪费严重。

零拷贝的目标:让 CPU 不参与数据搬运,只负责发指令。硬件用 DMA 直接搬。

问:mmap 怎么用?有什么坑?

把文件映射到进程地址空间,访问内存就是访问文件:

void* addr = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, offset);
// 直接访问 addr 即可读文件,无需 read()

应用场景:

  • V4L2 摄像头 Buffer 映射(IPC 开发必备)
  • 大文件读取
  • 进程间共享内存

坑点(面试高频)

后果
访问越界SIGSEGV
文件被截断SIGBUS
修改后未同步数据丢失(需 msync
特殊文件不可映射管道、socket 不行

问:sendfile 是什么?比 mmap 好在哪?

sendfile 在内核态直接把文件数据发到 socket,数据完全不经过用户空间

sendfile(socket_fd, file_fd, NULL, file_size);

2.6.33 之后配合 DMA,CPU 完全不参与数据搬运,真正的"零拷贝"。

对比

mmap + writesendfile
拷贝次数3 次2 次(DMA 下 0 次 CPU)
系统调用2 次1 次
数据是否经用户态经(映射后用户态可访问)不经

IPC 开发中的选择

  • V4L2 采集 → mmap(需要读/处理帧数据)
  • 发送录像文件 → sendfile(无需处理,直接转发)

4. 硬链接 vs 软链接

问:硬链接和软链接有什么区别?

理解它俩,先要知道 inode。每个文件都有两部分:

  • inode:文件的"身份证",存大小、权限、指向数据块的指针
  • 数据块:文件的实际内容

文件名只是指向 inode 的一个标签。

硬链接

ln file1 file2   # 创建硬链接

file1 和 file2 指向同一个 inode

inode 123456
    │
    ├── 文件名:file1
    └── 文件名:file2

删除 file1,inode 引用计数减 1,file2 还在,文件就还在。硬链接 = 同一个实体有多个名字。限制:不能跨分区,不能链接目录。

软链接

ln -s file1 file1_link   # 创建软链接

file1_link 是一个独立的新文件(新的 inode),它的内容就是字符串 "file1"

inode 234567(软链接)
    └── 内容:"file1"

inode 123456(原文件)
    └── 内容:"hello"

打开 file1_link 时,系统读到 "file1",然后去打开 file1。删除 file1 后,软链接还在,但指向的位置空了,就变成了"悬空链接"。软链接 = 一个文件存的是另一个文件的路径,可以跨分区,可以链接目录。

硬链接软链接
本质同一 inode 的多个文件名独立文件,存目标路径
删除原文件仍可访问失效(悬空链接)
跨文件系统不可以可以
链接目录不可以可以

5. 总结

文件 IO 知识图谱
│
├── 文件描述符 → 非负整数,表索引,一切 IO 的句柄
│
├── 系统调用 vs 库函数
│   ├── 系统调用:进内核,有切换开销
│   └── 库函数:用户态缓冲,减少切换
│
├── 零拷贝
│   ├── 传统 IO:4 次拷贝
│   ├── mmap:映射内存,3 次拷贝
│   └── sendfile:内核态转发,2 次拷贝(DMA 下 0 次 CPU)
│
└── 链接
    ├── 硬链接:同一 inode
    └── 软链接:存路径

核心理解:IO 优化的本质是减少拷贝次数减少上下文切换次数mmap 省了用户态拷贝,sendfile 省了用户态拷贝和一次系统调用,DMA 让 CPU 不用搬数据。你在 IPC 开发中写高效网络服务时,这些就是你的武器。