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 库函数:到底差在哪?
问:read 和 fread 有什么区别?
read 是系统调用,直接进内核,每次都有用户态↔内核态切换开销。fread 是库函数,内部有缓冲区,大部分数据从缓冲区取,满了才真正调用 read。
| 系统调用(read) | 库函数(fread) | |
|---|---|---|
| 是否进内核 | 每次都进 | 缓冲区满了才进 |
| 用户态缓冲区 | 无 | 有(stdio buffer) |
| 适合场景 | 大块数据 | 高频小数据 |
所以:写日志用 fprintf(小数据高频),读视频帧用 read 或 mmap(大数据块)。
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 + write | sendfile | |
|---|---|---|
| 拷贝次数 | 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 开发中写高效网络服务时,这些就是你的武器。