Linux用三个相关的数据结构表示打开的文件:
(1)描述符表。每个进程都会有一个描述符表,表项是由一个进程中的打开的文件描述符来索引的。比如说:fd1 = open(“foobar.text”, O_RDONLY, 0);这时fd就成为了一个文件描述符。
(2)文件表:所有进程只共享这一张表。现在假设:fd1 = open(“foobar.text”,O_RDONLY , 0);
fd2 = open(“foobar.text”,O_RDONLY , 0);这两个描述符在同一个进程中,所以在一个描述符表内,但是却打开了两个文件表,因为每条描述符都会重新打开一个文件表,除非把它重定向到别的描述符的位置。
(3)v-node表。这也是所有进程共享的,会因文件的不同有差别。
搞清楚这三个数据结构之后,之后再来说一下I/O重定向:
重定向在Linux中是这样工作的:
#include <unistd.h>
int dup2(int oldfd, int newfd);
这个函数表示将新的描述符重定向到旧的描述符的文件表的位置。
fd都会被定义成整型数据,因为在linux中是用非负整数来表示每个描述符的,每次创建描述符就会赋予当前最小的整数作为他们的id。但是在默认情况下,Linux会自动打开一下三个文件,所以最小的三个数0,1,2被占用:
0:标准输入文件;
1:标准输出文件;
2:标准出错文件;
下面我们来看一个具体的例子:
#include "csapp.h"
int main()
{
int fd1, fd2;
char c;
fd1 = Open("foobar.text", O_RDONLY, 0);
fd2 = Open("foobar.text", O-RDONLY, 0);
Read(fd2, &c , 1);
Dup2(fd2,fd1);
Read(fd1 , &c, 1);
printf("c = %c\n, c");
exit(0);
}
已知:磁盘文件foobar.text由6个ASCII码字符“foobar”组成。
分析这个程序:
由于这个程序只有一个进程,所以只有一张进程描述符表,并且有两个描述符fd1和fd2,他们的值分别是3和4.因为一直打开文件之后没有关闭文件。
第一步:fd1和fd2打开了这两个文件之后是Read函数去读取一个字符,此时c=f。这是因为虽然fd2是第二次打开与fd1相同的文件,但是会有两个文件表,两个文件表同时指向同一个v-node表罢了。所以尽管是第二次打开也是从第一个位置开始读。
第二部:函数Dup2将fd1重定向到fd2的文件表那里(详情请参考后面的数据结构解析图)。所以对fd1调用Read函数是接着fd2的后面读的,因为现在fd1与fd2在同一张文件表里。fd2读第一个字符,fd1就会接着读第二个字符。
所以最终输出结果是:c = o。
下面是在Linux系统下运行的结果:
编译之后输出结果为:c = o。所以是fd1经过重定向读到的是第二个字符。