基础IO
共识原理
- 文件 = 内容 + 属性
- 被打开的文件需要加载到内存中
- 内存中的文件需要被操作系统管理
用户级文件接口
Linux系统调用接口
fd 文件描述符 与访问文件的本质
fd
(file descriptor),即文件描述符,下文的系统调用接口经常以fd
命名变量,fd
是整形变量,作为数组下标,用于管理打开的文件
可以看到,一个进程通过struct files _struct
里的指针数组,管理多个同时打开的文件
且每个进程启动时,会默认打开三个文件,且默认fd
固定
- stdout
read
所需头文件#include <unistd.h>
声明ssize_t read(int fd, void *buf, size_t count);
参数
fd
即为目标文件的文件描述符buf
为要从文件读取字节到的内存地址count
为最大读取字节数
返回值
- 若成功,返回读取文件的字节数,类型为
ssize_t
,是层层封装的long int
- 若失败,返回
-1
,并设置errno
的值
write
所需头文件#include <unistd.h>
声明ssize_t write(int fd, const void *buf, size_t count);
参数
fd
为目标文件的文件描述符buf
为要写入文件的源内存地址
,输入字节数量取决于count
形参count
为要输入的字节数量,若要输入为字符串,且要输入字符串的全部内容,建议使用strlen(buf)
,防止输入\0
,因为对于文件来说,\0
是非法字符
返回值
- 若成功,返回写入文件的字节数,类型为
ssize_t
,是层层封装的long int
- 若失败,返回
-1
,并设置errno
的值
特别的read
函数从文件中读取的是字节
内容,不把读取的内容看作字符串,因此,不会自动添加\0
在写入buf
内容的结尾
用法见后文对open
的介绍
open
所需头文件
1 |
声明int open(const char *pathname, int flags);
int open(const char *pathname, int flags,mode_t mode);
参数:
pathname
为文件路径,若只有文件名,则默认在当前工作路径搜索flag
则是一个位图
,而不应看作整型参数,传参时可用|
位运算传递多个参数到位图中,例如O_CREAT | O_WRONLY
mode
则是在创建文件时,传入权限信息,这里使用八进制表示法
,例如传入0666
返回值:- 若成功,返回打开文件的
fd
值 - 若失败,则返回
-1
写入操作
相关的flags
O_WRONLY
仅写入O_CREAT
如果文件不存在,就创建,注新文件的权限由open
函数传入的mode
参数决定O_TRUNC
如果文件已存在且是常规文件
,并且打开的模式组合支持写入操作(O_RDWR
或O_WRONLY
),该文件内容将会被清除。但如果该文件是FIFO
(命名管道)文件或终端设备文件
,则O_TRUNC
将会被忽略O_EXCL
保证此次open
操作打开新文件。必须和O_CREAT
联合使用,否则打开失败。若pathname
存在,即该路径的文件存在时,也会打开失败
打开已有文件并写入
1 |
|
当原本log.txt
为空文件时
1 | this is a msg |
当原log.txt
不为空且内容长度大于程序输入的msg
时,发生部分覆写
例如原内容为0000111100001111
时,执行后为
1 | this is a msg111 |
可以看到有一部分没有被覆盖
打开空文件 或 创建空文件
1 |
|
追加写入
追加写入只需把O_TRUNC
改成O_APPEND
即可
1 |
|
这里我们事先删除log.txt
文件,然后运行两次编译出的程序,可以获得如下内容
1 | this is a msgthis is a msg |
可以看到内容追加了两次
读取操作
相关的flags
O_RDONLY
只读模式打开文件O_RDWR
读写模式打开文件
只读模式读取内容
1 |
|
实现准备内容为123456
的log.txt
文件
然后运行./mycmd
得到输出和文件内容
1 | 456 |
1 | zzz456 |
关于read
没读取到前面新写入的zzz
,是因为wtrite
和read
操作都是从文件的同一处继续
操作的,并不会发生回退
close
int close(int fd);
用于冲刷缓冲区,并关闭一个文件描述符
dup2 文件重定向
int dup2(int oldfd, int newfd);
如图所示,dup2
能将oldfd
对应的数组元素覆盖
到newfd
对应的数组元素处,完成对newfd
对应文件的重定向
图中就是完成了对标准输出
的重定向,像printf
之类的函数会直接输出内容到文件中,而不是显示器
1 |
|
运行代码后,可以看到终端
没有输出
而打开log.txt
1 | output1 |
子进程 与 父进程的文件关系
子进程对父进程的拷贝
先运行一段代码测试 子进程是否继承父进程的打开文件
1 |
|
这段代码中,我们在fork
之前完成了对标准输出的重定向,然后fork
之后令父进程和子进程进行不同的标准输出
运行结果为父进程和子进程的标准输出
都重定向到了文件
1 | child output |
子进程和父进程的 独立性
接下来一段代码测试 父进程 和 子进程 的打开文件是否独立
1 |
|
这里我们在fork
之前都不进行重定向,fork
后仅对子进程进行了标准输出重定向,而父进程不作任何重定向
在运行后发现子进程的输出重定向不会影响父进程,二者有独立性
1 | parent output |
1 | child output |
进程替换
先在同级文件夹准备一个待替换的程序
execute.c
1 |
|
然后运行gcc -o execute execute.c
编译获得一个程序
然后准备主程序
mycmd.c
1 |
|
这里我们使子进程先标准输出重定向, 再进行进程替换,发现替换后的进程,也是标准输出重定向的状态
1 | exe output |
结论
进程替换不会改变原进程的文件打开状态和重定向关系