IO 地图
点一个框看它做什么、在哪篇讲过;选一条路径,一步一步看一次 read、一次 recv、一次 sendfile 怎么走。蓝字可以直接点开对应的文章或地图。
进程地图
速查页
静态图
读一个文件
写一个文件
映射一个文件
收一个包
发一个包
把文件发到网络
看原尺寸
IO 地图:数据怎么在设备、内核和你的缓冲区之间走
上面是用户态,中间是内核,下面是设备和内存。左边一列是文件,中间一列是网络。数字都是量级。
橙线 = CPU 拷贝;蓝色虚线 = DMA(设备直接读写内存,不占 CPU);绿色虚线 = 映射,不拷贝;灰线 = 调用或通知。
蓝字 = 在哪里讲过,进程地图、硬件地图会打开对应的那张图。
用户态:你的进程
系统调用:同一个线程切到内核态,一次来回约 100 ns 到几百 ns
内核态
设备和内存
你的进程
read / write:读写文件
send / recv:收发网络数据
mmap:把文件映射进地址空间
每一个都是一次系统调用
进程地图
用户缓冲区 buf
你自己分配的内存:栈上、堆上
read / recv:内核把数据拷进来
write / send:内核从这里拷走
进程地图:栈、堆
mmap 映射区
页缓存里的页直接映射进来
读写就是普通的访存,不拷贝
第一次碰一页要缺页
进程地图:mmap 区
IO 模型:等数据的时候,线程在干什么
阻塞:数据没好,线程睡在 read / recv 里
非阻塞:数据没好,立刻返回 EAGAIN,过会儿再试
IO 多路复用(select / poll / epoll):一个线程等很多 fd,读还是自己读
异步(io_uring):把“读到 buf 里”交给内核,读完了再通知你
前三种都是同步 IO:把数据拷进 buf 这一步,是你的线程在等
epoll:红黑树、就绪链表和回调
VFS(虚拟文件系统)
按 fd 找到打开的文件
再交给具体的文件系统(ext4、xfs……)
页缓存(page cache)
读过、写过的文件内容,按页缓存在内存里
read 命中就直接拷,不碰磁盘
write 写到这里、标成脏页就返回
free 命令里的 buff/cache 主要就是它
回写线程(writeback)
脏页过一会写回磁盘
默认脏了约 30 s 必写,每 5 s 查一次
fsync:等这个文件写完才返回
块设备层 + 磁盘驱动
把要读写的页组成请求,排队、合并
交给 NVMe 驱动,发给磁盘
socket 缓冲区
接收缓冲:收到了、还没被 recv 的数据
发送缓冲:send 拷进来、对方还没确认的
满了:send 阻塞,或返回 EAGAIN
epoll:就绪通知
socket 收到数据,回调把它挂上 epoll 的就绪链表,
再叫醒睡在 epoll_wait 里的线程
只通知“可以读了”,数据还得你自己 recv
epoll:红黑树、就绪链表和回调
TCP/IP 协议栈
收:校验、去包头,按 IP 和端口找到 socket
发:分段、等确认、丢了重传
网卡驱动 + 收发环
收:软中断(NAPI)一次取一批包
发:把包挂到发送环,通知网卡
零拷贝:把文件发到网络上
read + write:
磁盘 → 页缓存(DMA)→ buf(CPU)→ socket 缓冲(CPU)→ 网卡(DMA)
4 次拷贝,4 次用户态、内核态切换
mmap + write:页缓存直接映射进来,省掉拷进 buf 那一次
sendfile:数据不出内核,页缓存 → 网卡
网卡支持分散收集 DMA 时,CPU 一次都不拷,只剩 2 次 DMA、2 次切换
splice:在两个 fd 之间搬,中间经过一个管道,也不拷进用户态
磁盘(NVMe SSD)
一次随机读约 10–100 µs
机械硬盘要几 ms
做完用 DMA 写进内存,再发中断
硬件地图:外部设备
内存(DRAM)
页缓存、socket 缓冲、收发环
其实都是内存里的页
DMA:设备直接读写内存,不占 CPU
硬件地图:内存控制器 → 通道
网卡(NIC)
收:DMA 写进收包环,再发中断
发:DMA 从发送环读出来发走
同一机房往返约几十 µs
硬件地图:网卡 → 中断 → 某个核
read / write
send / recv
CPU 拷贝
CPU 拷贝
映射,不拷贝
读写请求
DMA
DMA
中断
回调
查
没命中
图随文章更新:讲到 io_uring、管道和信号时再往上补。