目录
一、前言
本文先回顾了C语言文件围绕FILE*的IO的操作,随后介绍系统调用的open,close,write,read四个函数,接着了解Linux中围绕文件操作符fd的IO操作,探究文件描述符的本质是进程中文件描述符表的下标,最后理解重定向的本质底层原理。
二、C语言文件IO
在正式讲解Linux中如何对文件进行IO前,我们先简单回顾下C语言中的文件IO操作。
fopen用于打开文件:FILE* fopen(const char *pathname, const char *mode);
其中pathname是打开文件的路径,可以是绝对路径或相对路径,mode则是打开文件的模式,有以下常见的模式:
- "r":只读,若文件不存在则报错
- "w":只写,若文件不存在则创建同名文件,打开文件时清空文件原有内容
- "a":只写,若文件不存在则创建同名文件,打开文件时不会清空,在末尾追加内容
该函数会返回一个FILE*的指针,C语言中通过操作这个FILE*的指针来控制文件的IO。当我们打开文件时,每个被打开的文件都会在内存中开辟一个相应的文件信息区,用来存放文件相关信息,而它们都被保存在名为FILE的结构体中,我们通过FILE*这个指针来操纵文件。

C语言程序在启动的时候,就默认打开了三个流:stdin,stdout,stderr ,之前我们说过Linux中一切皆文件,这三个流其实就是向显示器和键盘对应的文件进行读取、写入而已。
三、Linux文件IO
每个语言都有一套自己的文件操作函数,但这都是上层语言的变化,其底层都是封装的系统调用,因此接下来就学习文件的系统调用:open,close,write,read
在C语言中文件通过FILE*来操纵,而在Linux中所有IO操作都围绕着文件描述符fd
1.open


这个flag虽然是int类型,但实际上是当做位图来使用的,通过判断某位是否为1来查看对应哪个选项,可以使用以下这些宏定义,若要使用多个,用 |(或)连接

例如O_WRONLY | O_CREAT 的意思就是以只读形式打开文件,若不存在则创建
至于open的第三个参数mode,那是用来控制文件的初始权限的,若没有传参,生成的文件的权限值是随机的。但mode控制也仅仅是初始权限,要经过umask过滤后得到的才是实际权限,因此要先通过umask(0000)将权限掩码置为0,在传参mode(例如0666),那么最后该文件的权限就是0666,也就是rw-rw-rw-
2.close
close就很简单了,传入对应文件的文件描述符fd来关闭文件,成功则返回0。
3.write

write是向指定文件描述符fd的文件中写入数据,数据来源于buf,写入的字节数是count,调用成功后返回写入到文件的字节数。
char* buffer = "abcdef";
int fd = open("/home/zyh/text.txt",O_WRONLY);
write(fd,buffer,sizeof(buffer));
4.read

read是从文件描述符fd对应的文件中读取数据到buf中,count是最多读取的字节数,读取成功返回读取到的字符个数,读取到文件的末尾则返回0,读取发生错误返回<0的数。
四、文件描述符
文件描述符fd是int类型的,那么它是否有什么规律呢?
int fd1 = open("/home/zyh/text1.txt",O_WRONLY | O_CREAT);
int fd2 = open("/home/zyh/text2.txt",O_WRONLY | O_CREAT);
int fd3 = open("/home/zyh/text3.txt",O_WRONLY | O_CREAT);
int fd4 = open("/home/zyh/text4.txt",O_WRONLY | O_CREAT);
printf("%d, %d, %d, %d",fd1,fd2,fd3,fd4);
结果是3,4,5,6,可以发现新文件描述符是依次递增的,那么0,1,2号到哪去了?实际上0,1,2号正是我们之前提到的stdin,stdout,stderr这三个标准文件。后续如果把3号文件关闭了,那么新创建的文件fd就是3而不是7了。
在操作系统内很可能有多个打开的文件,那么OS就必须要来维护这些资源,和之前的进程管理一样,先描述再组织,用struct file来描述文件属性,再用链表将其连接起来:

这是文件在OS内部的管理体系,而每个进程也都知道自己打开了哪些文件,因此进程的PCB中会保存一张文件描述符表(本质是结构体指针),这个表中存放了这个进程打开的所有文件:

从这张图中我们不难看出,其实文件描述符的本质就是数组的下标,每次打开文件都会去数组中扫描没有被使用的下标,来充当新打开文件的fd
五、一切皆文件
之前说过,底层不同的各种硬件,如磁盘、显卡、网卡等都对应了不同的操作方法,但这些设备的核心功能就是读写,也就是IO

操作系统会为每一个底层硬件创建一个struct file的结构体,包含两个函数指针,分别对应读方法和写方法,于是和硬件的交互就变为了访问struct file中的指针,使所有硬件达到了一视同仁

六、输入重定向
根据上述的结论,我们知道0,1,2对应的是stdin,stdout和stderr,如果此时我们将1号进程关闭,再打开一个新文件,这个文件的文件描述符是否就是1,那么此时再运行会发生什么?


我们可以发现新打开文件的fd确实是1,但此时printf和fprintf都不会输出到屏幕了,而是输出到test.txt中,说明stdout是只认1号文件标识符的,不管此时1还是不是标准输出。

结论:重定向的本质就是在OS内部修改fd对应的内容指向
七、重定向的系统调用
如果每次重定向都要先关闭一个文件再来操作就十分麻烦,可以直接使用系统调用dup或dup2来完成重定向操作

一般都使用dup2,它的意思是将oldfd拷贝给newfd,也就是说newfd对应文件的fd变成了oldfd,例如我们要将打印在显示器(1号)的信息打印在test.txt中就这样使用:
open("test.txt",O_WRONLY | O_CREAT);
dup2(3,1);//oldfd - newfd


1180

被折叠的 条评论
为什么被折叠?



