Linux IO
用户态,内核态切换
用户进程在系统中运行时,大部分时间是处在用户态空间里的,在其需要操作系统帮助完成一些用户态没有特权和能力完成的操作时就需要切换到内核态。
用户进程如何切换到内核态的情况
- 系统调用 trap
- 异常 exception
-
中断(interrupt)。
-
系统调用:用户进程主动发起的操作。用户态进程发起系统调用主动要求切换到内核态,陷入内核之后,由操作系统来操作系统资源,完成之后再返回到进程。
- 异常:被动的操作,且用户进程无法预测其发生的时机。当用户进程在运行期间发生了异常(比如某条指令出了问题),这时会触发由当前运行进程切换到处理此异常的内核相关进程中,也即是切换到了内核态。异常包括程序运算引起的各种错误如除 0、缓冲区溢出、缺页等。
- 中断:当外围设备完成用户请求的操作后,会向 CPU 发出相应的中断信号,这时 CPU 会暂停执行下一条即将要执行的指令而转到与中断信号对应的处理程序去执行,如果前面执行的指令是用户态下的程序,那么转换的过程自然就会是从用户态到内核态的切换。中断包括 I/O 中断、外部信号中断、各种定时器引起的时钟中断等。中断和异常类似,都是通过中断向量表来找到相应的处理程序进行处理。区别在于,中断来自处理器外部,不是由任何一条专门的指令造成,而异常是执行当前指令的结果。
read / write 系统调用
- 32位系统下,4GB的内存空间,前3G为用户空间,最后1G为系统空间
- 当发生系统调用时,会产生中断,进程从用户空间切换到系统空间
- 用户空间向内核空间交换数据使用
copy_from_user,copy_to_user两个函数
读磁盘写出到网卡流程
一次完整的读磁盘文件然后写出到网卡的底层传输过程如下:
mmap 与 read/write 的区别
mmap (Memory Mapped Files) 和传统的 read/write 系统调用是 Linux 下两种主要的文件访问方式,它们在原理、性能和适用场景上有显著区别。
1. 调用机制与开销
- read/write:
- 系统调用: 每次读写都需要发起系统调用(
read()/write()),涉及用户态和内核态的上下文切换。 - 数据拷贝: 此时数据通常需要经过两次拷贝:磁盘 -> 内核 Page Cache -> 用户缓冲区(User Buffer)。
- 系统调用: 每次读写都需要发起系统调用(
- mmap:
- 系统调用: 只需一次
mmap()系统调用将文件映射到进程的虚拟地址空间。后续访问就像访问内存一样,没有系统调用开销(但在发生缺页中断时会有内核介入)。 - 数据拷贝: 减少了一次拷贝。文件数据直接存在于内核的 Page Cache 中,用户进程通过映射直接访问 Page Cache,避免了内核空间到用户空间的拷贝(Zero Copy 的一种体现)。
- 系统调用: 只需一次
2. 内存与缺页中断
- read/write: 数据被显式读入用户指定的 buffer。
- mmap: 建立映射后,并未立即加载数据。当进程真正访问这块地址时,触发缺页中断 (Page Fault),内核将数据从磁盘加载到 Page Cache。
3. 性能对比
- 小文件/顺序读写:
read/write可能略优或持平。因为mmap的 setup 开销(建立页表、VMA维护)和缺页中断(Page Fault)的开销在小数据量下可能超过直接系统调用的开销。 - 大文件/随机读写:
mmap优势明显。- 减少了数据拷贝。
- 减少了系统调用次数。
- 适合跨进程共享内存(多个进程 map 同一个文件,共享 Page Cache)。
4. 总结
| 特性 | read / write | mmap |
|---|---|---|
| 访问方式 | 系统调用,显式拷贝 | 内存地址直接访问 (Load/Store) |
| 数据拷贝次数 | 2次 (Disk -> Kernel -> User) | 1次 (Disk -> Kernel/User Shared) |
| 上下文切换 | 频繁 (每次 IO) | 少 (Setup + Page Fault) |
| 优势场景 | 简单的顺序读写,流式处理 | 频繁随机访问,大文件,进程间共享 |
| 缺点 | 拷贝开销大 | 缺页中断开销,对大文件可能占用大量虚拟内存空间 |
