文件描述符及重定向
在Linux下,一切皆文件, 现在我们来深刻感受一下什么叫一切皆文件。我们知道一个进程可以打开多个文件,多个进程就可以打开多个文件。打开后的文件,会被加载进内存中。那么操作系统就要对打开的文件做管理。那么也是需要 先描述,再组织。研究文件的操作也就是研究 进程和打开文件的关系。
在讲解之前我们来回顾一下C语言中是如何操作文件的。
一,C语言的文件接口
我们先来看一段代码:
向文件里写:
#include <stdio.h>
#include <string.h>
int main()
{
FILE *fp = fopen("myfile", "w");
if(!fp){
printf("fopen error!\n");
}
const char *msg = "hello bit!\n";
int count = 5;
while(count--){
fwrite(msg, strlen(msg), 1, fp);
}
fclose(fp);
return 0;
}
读文件:
#include <stdio.h>
#include <string.h>
int main()
{
FILE *fp = fopen("myfile", "r");
if(!fp){
printf("fopen error!\n");
}
char buf[1024];
const char *msg = "hello bit!\n";
while(1){
//
ssize_t s = fread(buf, 1, strlen(msg), fp);
if(s > 0){
buf[s] = 0;
printf("%s", buf);
}
if(feof(fp)){
break;
}
}
fclose(fp);
return 0;
}
这里的文件操作的返回值都是一个FILE* 的类型,打开文件时都要传入对应的参数表示打开方式。
这里我们先来说一个概念:
C语言默认会打开三个输入输出流,分别是
stdin, stdout, stderr
。而且这三个流的类型也是FILE*
,也就是文件指针
。
我们在打开或者关闭文件的时候,打开的都是磁盘上的文件,磁盘属于外设,所以文件操作的接口底层一定封装了系统调用,我们直接来看系统调用。
二,文件操作的相关系统调用接口
下面我们来一一介绍系统调用接口:
2.1 open
这里可以看到,open接口的
返回值为int
,我们后面再讨论。而这里的参数pathname
:表示要打开或创建的目标文件,而flags
表示打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags:(相当于C语言打开文件时传入的打开方式)
int fp = open("test2.txt",O_RDONLY|O_CREAT,0666);
2.2 close
close可以关闭指定的文件,这个参数fd表示文件描述符
close(fd);
2.3 write
write可以向文件描述符为fd的文件中写入nbyte字节的buf
char* buffer = "abcdef";
int fd = open("/home/ljh/test.txt",O_WRONLY);
write(fd,buffer,sizeof(buffer));
这段代码意思是以只写的方式打开
/home/ljh/
下的test.txt
,向其中写入buffer
2.4 read
read接口可以打开文件描述符为fd的文件,并且把文件的内容读到buf中,读nbyte个字节。
char buffer[1024];
int fd = open("/home/ljh/test.txt",O_WRONLY);
ssize_t n = read(fd,buffer,sizeof(buffer));
if(n > 0)
{
buffer[n] = '\0';//将字符串变成C语言风格,以\0结尾
}
三,文件描述符
我们在使用系统调用时都会看到对应的接口都有一个返回值,这个返回值类型为int。这个返回值其实就是文件描述符,那么什么是文件描述符呢?我们现在来看看打开一个文件,底层都做了哪些工作。
3.1 文件描述符的本质
当一个进程打开一个文件时,其实操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了
file结构体
。表示一个已经打开的文件对象
。
而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个
指针*files
, 指向一张表files_struct
。这个表也叫文件描述符表
。该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针。
而文件描述符其实就是该数组的下标,只要拿着文件描述符,就可以找到对应的文件
从上面的图中我们可以清楚看到,每个进程的文件描述符表中前三个默认就是stdin,stdout,stderr.对应的文件描述符为0,1,2。也就是说每个进程都会打开三个文件,分别是键盘,显示器,显示器。
3.2 理解一切皆文件
Linux下一切皆文件其实就是操作系统为显示器,键盘等都创建一个结构体,结构体中包含该硬件的读写方法的函数指针,可以通过read系统调用接口来调用硬件的读写方法,可以忽视底层的差异。
其实也就是多态
3.3 文件描述符分配规则
从上面我们可以知道,文件描述符表的前三个都默认给了标准输入,标准输出,标准错误。那么其余的文件描述符表示的是什么呢?
看下面的代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}
输出后
fd=3
关闭0号文件后观察结果:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
close(0);
//close(2);
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}
输出为
fd=0
从这个例子我们可以得到:
文件描述符的分配规则就是,在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符
四,重定向的本质
4.1 重定向的本质
知道了文件描述符的分配规则,从上面的例子我们可以看到,当关闭了0号文件后,这个0号下标的文件从键盘改到了log.txt中。
所以再次运行程序后,我们就不能从键盘中来获取输入,而是从log.txt中获取。这种现象我们叫重定向
如果想让把打在显示器中的内容打在文件中,我们可以关掉1号下标的文件,也就是显示器,进而我们printf的内容都会被写在我们打开的文件中。
4.2 重定向的接口
这里也有专门进行重定向的系统调用接口,dup2就可以将文件描述符为oldfd的文件覆盖到newfd位置上。
int fd = open("log.txt",O_WRONLY | O_CREAT);
dup2(3,1);
这样我们就可以将原本打在显示器上的内容打在log.txt中了。
五,总结
这篇文章只讲解了Linux的I/O的一半的内容,后面我们还要了解Linux的文件系统和动静态库和缓冲区的问题,希望大家可以继续关注。