目录1. 进程对文件的管理1.1 操作系统进程文件三者的联系1.2 打开文件的方式1.3 标准调用库函数1.4 标准I/O库函数对系统调用的封装2. 重定向3. 文件的缓冲区机制1. 进程对文件的管理1.1 操作系统进程文件三者的联系文件是静态的是存储在硬盘上的数据。进程是动态的是程序运行起来的实例进程像操作系统发起请求操作系统负责把文件资源安全地分配给进程使用。交互机制文件描述符fd进程内部维护一张文件描述符表操作系统内部维护着文件的信息inode通过openwrite等系统调用进行交互。一个进程可以同时打开多个文件多个进程也可以同时打开一个文件。对文件的操作本质上是进程对文件的操作1.2 打开文件的方式File*类型是 C 语言标准库stdio.h中用来表示文件流的指针类型。在底层File是一个被封装好的结构体通常包含文件描述符fd缓冲区buffer和标志位flags。flags用来记录文件是以什么方式被打开的常见的文件打开方式1. r只读文件不存在时失败返回NULL2. w只写会清空原文件文件不存在时创建新文件3. a追加不清空原文件内容在末尾追加文件不存在时创建新文件在 Windows 系统下打开方式还可以加上 b来区分是否是以二进制模式打开还是以文末模式默认打开在linux下加不加b效果是一样的但为了跨平台性在读写二进制文件时建议加上b来区分1.3 标准调用库函数fopenfclose属于标准I/O库函数底层封装的是系统调用函数openclose在 Linux 环境下系统文件 I/O 最核心的四个基础调用函数如下1.open打开或创建文件int open(const char *pathname, int flags, mode_t mode)返回一个文件描述符(fd)失败返回 -12.close关闭文件int close(int fd)释放文件描述符资源失败返回 -13.write向文件写入数据ssize_t write(int fd, const void *buf, size_t count)返回实际写入的字节数失败返回 -14.read从文件读取数据ssize_t read(int fd, void *buf, size_t count);返回实际读取的字节数0代表读到文件末尾失败返回 -1open函数的参数open的第二个参数是位掩码常用的有O_RDONLY只读、O_WRONLY只写、O_RDWR读写——这三个必须选一个O_CREAT文件不存在则创建、O_TRUNC打开时清空文件、O_APPEND追加模式位掩码是一种编程手段C语言中通常用宏定义来实现#define O_RDONLY 00000000 // 二进制: ...0000 #define O_WRONLY 00000001 // 二进制: ...0001 #define O_RDWR 00000002 // 二进制: ...0010 #define O_CREAT 00000100 // 二进制: ...0100 #define O_TRUNC 00001000 // 二进制: ...1000当第二个参数包含了O_CREAT时需要写第三个参数mode设置文件权限使用方式如下// 对应 fopen(file.txt, r); int fd open(file.txt, O_RDONLY); // 对应 fopen(file.txt, w); // 需要组合只写 创建 清空 int fd open(file.txt, O_WRONLY | O_CREAT | O_TRUNC, 0644); // 对应 fopen(file.txt, a); // 需要组合只写 创建 追加 int fd open(file.txt, O_WRONLY | O_CREAT | O_APPEND, 0644);系统不关心你是以文本写入还是二进制写入它只看字节数负责把指定字节的内容写入硬盘1.4 标准I/O库函数对系统调用的封装当你调用了fopen(log.txt,w)时C标准库做了三件事1. 将字符串模式w,r翻译成标志位2. 调用系统调用成功时返回一个fd3. 分配一个FILE结构体封装了fd把FILE*指针返回进程调用文件的具体流程如下每个进程的 PCB 里确实有一个指针通常叫files指向该进程独有的“文件描述符表”这个表本质上就是一个指针数组。数组的下标就是我们常说的fd数组里的每个元素指针指向的是内核维护的一张全局的“打开文件表”【进程空间】 【内核空间】 ┌──────────────┐ ┌──────────────────────────────┐ │ 进程 PCB │ │ 文件描述符表 (数组) │ │ (task_struct)│ │ ┌───┬───┬───┬───┬───┐ │ │ │ │ │ 0 │ 1 │ 2 │ 3 │ 4 │ ... │ -- 数组下标就是 fd (int) │ files ──────┼────────┼─→│ │ │ │ │ │ │ │ │ │ │ └──────────────┘ │ └─┼─┴─┼─┴─┼─┴─┼─┴─┼─┘ │ │ │ │ │ │ │ │ │ ↓ ↓ ↓ │ │ │ │ stdin stdout stderr │ │ │ │ │ │ │ │ ↓ ↓ │ │ ┌───────────────┐ │ │ │ struct file │ │ -- 打开文件表 (记录偏移量offset等) │ │ (打开实例1) │ │ │ │ offset 50 │ │ │ └───────┬───────┘ │ │ │ │ │ ↓ │ │ ┌───────────────┐ │ │ │ inode │ │ -- 内存中的 inode (记录磁盘位置、权限等) │ │ (文件实体信息) │ │ └──────┴───────┬───────┘ │ │ │ ↓ │ 【磁盘/硬件空间】 │ ┌───────────────┐ │ │ 实际文件数据 │ │ │ (Hello World) │ │ └───────────────┘ │open系统调用的时候操作系统确实会把文件的元数据也就是 inode包含文件大小、权限、在磁盘上的位置等加载到内存里。但请注意文件真正的海量数据比如一部 2GB 的电影并不会在 open 时就全部加载到内存而是等你调用read时系统才会按需把数据从磁盘搬运到内存的缓冲区中。2. 重定向linux操作系统有一句经典名言一切皆文件我们的输入设备输出设备都是文件显示器输出信息本质上就是往显示器文件里写内容当一个新的进程被创建时比如通过fork和exec操作系统内核会自动为该进程分配并打开前三个文件描述符0,1,2分别对应标准输入标准输出和标准错误C 标准库在程序启动时main函数执行前会调用底层的系统接口将文件描述符 0、1、2 封装成FILE *结构体指针也就是我们熟知的stdin、stdout和stderr这也就是为什么printf向显示器打印信息底层调用的就是write向fd1的文件里写入数据那么重定向是什么呢一句话总结重定向就是更改文件描述表的指针指向这样你原本想写入显示器的数据就写到指定文件上了演示如下3. 文件的缓冲区机制缓冲区机制是为了提高效率在计算机中用户态你的程序和内核态操作系统之间的切换是非常消耗资源的。如果你想往磁盘上写入数据硬盘等存储设备在读写数据时批量读写的速度远远快于零散读写。缓冲区机制本质上是一种“以空间换时间”的策略用内存中的一小块空间缓冲区暂时存放数据通过攒够一波再统一处理的方式极大地减少了昂贵的系统调用次数并让硬件的读写更加顺畅演示如下C标准库也有缓冲区机制刷新条件有三种1.立即刷新无缓冲通常用于报错2.全缓冲效率最高一般用于普通文件3.行缓冲用于显示器进程退出也会进行刷新也可以fflush强制刷新总的来说语言层的缓冲区可以自己控制内核层的缓冲区比较“高冷”规则由操作系统内核的算法决定主要看时间内存空闲程度子进程会复制父进程的内存区域包括文件描述符表环境变量表缓冲区信息等而像进程pcb会在自己的内核空间新建属性拷贝终