文章目录
文件
所有的 I/O 设备都被视为文件,因此我们能用一致的方式处理各种输入输出。
文件类型分为以下几类
- 普通文件:包含用户数据,可以是文本文件(如.txt)或二进制文件(如.jpg、.exe)
- 目录:包含一系列链接,每个连接将一个文件名映射到一个文件
- 套接字(socket):跨网络通信用的文件
读写文件
文件描述符(file descriptor)
文件描述符是一个非负整数,代表进程打开的文件的标识符。在进程中,每当打开一个文件时,操作系统会分配一个文件描述符给它。
默认情况下,0 表示标准输入(stdin),1 表示标准输出(stdout),2 表示标准错误(stderr)。通过系统调用(如 open)打开文件后,会返回一个新的文件描述符(如 3、4 等),具体值取决于当前进程中已使用的描述符情况。
以下是一个使用 open
系统调用的示例,展示如何打开一个文件并获取文件描述符:
1 |
|
不足值(short count)
“不足值”是指在读写操作中,实际读取或写入的字节数少于请求的字节数。原因有遇到 EOF、从终端读文本行(如果读终端,read 一次只传输一个文本行)、读写网络套接字。
比如调用 read(fd, buffer, 100)请求读取 100 字节,但文件只剩 50 字节可用,则返回 50(不足值)。
我们需要检查返回值,确认实际读写字节数,并根据需要调整逻辑(如循环读取剩余数据)。
描述符表、文件表、v-node 表
操作系统在内核中维护了三层数据结构来管理文件:
- 描述符表:每个进程独有,记录该进程打开的所有文件描述符及其对应的文件表项。
- 文件表:所有进程共享,表项包括偏移值、引用计数(即当前指向该表的描述符表项数)、指向 v-node 表中对应项的指针。关闭一个描述符会减少相应的文件表表项的引用计数,减到零会删除。
- v-node 表(或 inode 表):与具体文件系统相关,记录文件的元数据(如文件大小、权限、存储位置)。每个文件在 v-node 表中有一个唯一条目。
调用 open 打开文件的具体过程
当调用 open
系统调用打开一个文件时,操作系统会执行以下步骤:
- 验证和查找文件:
- 内核检查文件路径、权限等,确定文件是否存在且进程有权访问。
- 找到文件对应的 v-node(或 inode),如果文件已在 v-node 表中,则复用,否则创建新条目。
- 分配文件表项:
- 内核在文件表(file table)中创建一个新表项,记录文件的偏移量(初始为 0)、访问模式(如只读、读写)、引用计数(初始为 1)以及指向对应 v-node 的指针。
- 更新描述符表:
- 内核在调用进程的描述符表(file descriptor table)中分配一个未使用的最小描述符编号(如 3,若 0、1、2 已占用)。
- 将该描述符指向新创建的文件表项。
- 返回文件描述符:
open
调用返回分配的文件描述符给进程,供后续操作(如read
、write
)使用。
open 两次:
fork:
基于缓冲区的读写
基于缓冲区的读写将数据先写入内存缓冲区,等缓冲区满或显式刷新(如 fflush)时再一次性与底层设备做数据交换,降低了 I/O 开销。
在使用读写时,我们应尽可能使用 stdio 标准库。stdio(标准输入输出库)提供了基于缓冲区的 I/O 操作(如 fopen、fread、fwrite、printf 等),相比直接使用低级系统调用(如 read、write),它的效率更高而且更不易出错。