内核用三个相关的数据结构来表示打开的文件:
描述符表:每个进程有它独立的描述符表,表项由它打开的文件描述符来索引每个打开的描述符表项指向文件表中的一个表项
文件表:打开文件的集合是由一张文件表来表示的,所有进程共享这一张表
v-node 表:同文件表一样,所有进程共享这一张v-node表。
如图是两种常见的引用方式,打开不同文件和同一文件。无论是哪一种描述符指向的文件表都是独立的,互不干扰的。
dup2(fd1,fd2)常用函数:I/O重定向函数将fd2将指向fd1所指向的文件表
接下来看几个例子
例1
#include "csapp.h"
int main(int argc, char *argv[])
{
int fd1, fd2, fd3;
char c1, c2, c3;
char *fname = argv[1];
fd1 = Open(fname, O_RDONLY, 0);
fd2 = Open(fname, O_RDONLY, 0);
fd3 = Open(fname, O_RDONLY, 0);
dup2(fd2, fd3);
Read(fd1, &c1, 1);
Read(fd2, &c2, 1);
Read(fd3, &c3, 1);
printf("c1 = %c, c2 = %c, c3 = %c\n", c1, c2, c3);
Close(fd1);
Close(fd2);
Close(fd3);
return 0;
}
读取的文件中假设内容为abcde
分析:fd1,fd2,fd3先后打开同一个文件,都从内容开头读。fd3与fd2共用fd2的文件表。fd1读取第一个字节a(光标定位到a后),fd2读取第一个字节a(fd2的光标定位到a后),fd3与fd2共用同一个文件表,所以此时fd3从a的后面开始读,读取到b,所以结果为c1=a,c2=a,c3=b
#include "csapp.h"
int main(int argc, char *argv[])
{
int fd1, fd2, fd3;
char *fname = argv[1];
fd1 = Open(fname, O_CREAT|O_TRUNC|O_RDWR, S_IRUSR|S_IWUSR);
Write(fd1, "pqrs", 4);
fd3 = Open(fname, O_APPEND|O_WRONLY, 0);
Write(fd3, "jklmn", 5);
fd2 = dup(fd1); /* Allocates new descriptor */
Write(fd2, "wxyz", 4);
Write(fd3, "ef", 2);
Close(fd1);
Close(fd2);
Close(fd3);
return 0;
}
分析:首先fd1是打开(创建)一个空白文件,接着在fd1指向的文件中写入“pqrs”,fd1光标移动到s后;fd3打开同一个文件(fd1写入了pqrs的文件),并将光标移动到文件内容末端(O_APPEND),fd3再写入”jklmn“,(此时文件中”pqrsjklmn“),fd3光标移动到n后;fd2指向fd1的文件表,也就是说fd2的光标在s后,接着写入wxyz,(覆盖了fd3写入的jklm)fd2和fd1光标定位到z后;fd3在n后继续写入ef,关闭fd1,fd2,fd3。最后输出pqrswxyznef
例3:
#include "csapp.h"
int main(int argc, char *argv[])
{
int fd1;
int s = getpid() & 0x1;
char c1, c2;
char *fname = argv[1];
fd1 = Open(fname, O_RDONLY, 0);
Read(fd1, &c1, 1);
if (fork()) {
/* Parent */
sleep(s);
Read(fd1, &c2, 1);
printf("Parent: c1 = %c, c2 = %c\n", c1, c2);
} else {
/* Child */
sleep(1-s);
Read(fd1, &c2, 1);
printf("Child: c1 = %c, c2 = %c\n", c1, c2);
}
return 0;
}
父子进程共享相同的打开文件表集合
我
如上图,父子进程共享同一个打开文件表集合,也就是说父进程对文件的操作会直接影响子进程。所以例3的运行结果是