系统级 I/O
[!question] 为什么要学习 Unix I/O - 帮助理解其他的系统概念: I/O 在进程的创建和执行中扮演着重要的角色, 繁殖, 进程创建又在不同进程间的文件共享中扮演着关键角色. 因此, 要真正理解 I/O, 必须要理解进程, 反之亦然. - 别无选择: 在某些重要的情况中, 使用高级 I/O 函数不太可能或不太合适: 标准 I/O 库没有提供读取文件元数据的方式, 且 I/O 库中存在的问题, 使得用它来进行网络编程非常冒险.
Unix I/O
在 linux 中, 所有的 I/O 设备都被抽象为文件 (Everything is file) , 而所有的 I/O 都会被当做对相应的文件的读和写来执行. 这种将设备优化地映射为文件的方式, 允许 Linux 内核引出一个简单且低级的应用接口, 这些接口称为 Unix I/O, 这为所有的输入和输出以一种统一的方式来进行:
- 打开文件: 应用程序通过要求内核打开相应的文件, 来告诉内核它要访问一个 I/O 设备. 内核向这个应用程序返回一个称为描述符的非负整数, 描述符用于在后续对文件的操作中标识这个文件且描述符是唯一的, 应用程序只需要记住这个描述符, 就可以对 I/O 设备进行访问, 而内核负责记录有关这个打开文件的所有信息.
- Linux shell 创建的每个进程开始时都有 3 个打开的文件:
- 标准输入 (描述符 0) -> STDIN_FILENO
- 标准输出 (描述符 1) -> STDOUT_FILENO
- 标准错误 (描述符 2) -> STDERR_FILENO
- 改变当前的文件位置: 对于每个打开的文件, 内核维护一个文件位置 k
, 且初始值为 0
. 文件位置标识了从文件开头起始的字节偏移量, 应用程序可以通过执行 seek
操作, 显式的修改 k
- 读写文件:
- 读: 从文件中复制 n
个字节到内存, 范围 k ~ k + n
- 对于大小为 m
字节的文件, 当 k >= m
时执行读操作会触发 end-of-file (EOF)的条件, 应用程序可以检测到这个条件. 文件中并没有存储 EOF.
- 写: 从内存复制 n
个字节到文件的 k
位置, 然后更新 k
为 k + n
- 关闭文件: 当应用程序完成了对文件的访问过后, 就通知内核关闭这个文件. 内核释放文件打开时候所创建的所有数据结构, 并释放描述符到可用的描述符池中
- 无论进程因为什么原因终止, 内核都会关闭所有打开的文件并释放其内存资源