本人对于文件共享的理解还不是很深入,以下陈述如果有问题,欢迎大家及时指正。
在内核中,用了3个相关的数据结构来表示打开的文件。
1.描述符表(descriptor table):每个进程都有一个自己的单独的文件描述符表,这个表的每一项有它所分配的文件描述符来索引的。对于每一个进程,一般都默认打开三个文件描述符(0,标准输入;1,标准输出;2标准错误输出),所以,一个进程的文件描述符一般是从3开始分配的。具体地,对于每一个文件描述符,指向下面所要说的文件表中的一个表项。文件描述符是int类型的,它和我们进程使用的FILE *类型是有区别的,如果想把FILE *类型所描述的文件转化为int类型,需要调用函数fileno(),但是如果考虑到程序的可移植性,建议是用FILE *类型的描述符来编写程序,因为在windows下没有之类对我头文件,也不涉及低级的UNIX I/O函数。
2.文件表(file table):我们打开的文件是由一张文件表来表示的,所有的进程共享这张表。每一个文件表的表项组成有些复杂,但主要包括当前文件的位置、应用计数(即现在指向该表项的描述符表项数),以及一个指向v-node表(下面会介绍)项的指针。如果关闭一个文件描述符(也就是调用了close()函数),则会给相应的文件表表项的引用计数的值减1,当引用计数的值为0时,内核会删除这个文件表的表项。
3.v-node表(v-node table):所有的进程共享这张表。这个v-node表的每一个表项包括stat结构中的大多数信息,对于stat这个数据结构的解释如下:
struct stat {
mode_t st_mode; //文件对应的模式,文件,目录等
ino_t st_ino; //inode节点号
dev_t st_dev; //设备号码
dev_t st_rdev; //特殊设备号码
nlink_t st_nlink; //文件的连接数
uid_t st_uid; //文件所有者
gid_t st_gid; //文件所有者对应的组
off_t st_size; //普通文件,对应的文件字节数
time_t st_atime; //文件最后被访问的时间
time_t st_mtime; //文件内容最后被修改的时间
time_t st_ctime; //文件状态改变时间
blksize_t st_blksize; //文件内容对应的块大小
blkcnt_t st_blocks; //伟建内容对应的块数量
};
总之,stat这个结构体记录的是文件的大多信息。
在了解以上概念的基础上,我们先看一个简单的小程序:
-
//test.c
-
#include
-
#include
-
#include
-
#include
-
#include
-
-
int main()
-
{
-
int fd;
-
char ch;
-
pid_t pid;
-
-
if((pid = fork()) > 0) {
-
fd = open("test", O_RDONLY);
-
read(fd, &ch, 1);
-
printf("ch=%c\n", ch);
-
wait(NULL);
-
} else if(pid == 0) {
-
fd = open("test", O_RDONLY);
-
read(fd, &ch, 1);
-
printf("ch=%c\n", ch);
-
} else {
-
exit(1);
-
}
-
close(fd);
-
return 0;
-
}
在同一目录下,一个文件test,其内容如下:
abcdefg
编译、链接和执行的结果是什么呢?如果你猜想的结果是:
ch=a
ch=a
哈哈,那你就对了,这个程序的执行就过就是这样的。
我们把上面的程序稍做修改,那个”test”文件内容保持不变。
-
//test.c
-
#include
-
#include
-
#include
-
#include
-
#include
-
-
int main()
-
{
-
int fd;
-
char ch;
-
pid_t pid;
-
-
fd = open("test", O_RDONLY);
-
if((pid = fork()) > 0) {
-
read(fd, &ch, 1);
-
printf("ch=%c\n", ch);
-
wait(NULL);
-
} else if(pid == 0) {
-
read(fd, &ch, 1);
-
printf("ch=%c\n", ch);
-
} else {
-
exit(1);
-
}
-
close(fd);
-
return 0;
-
}
如果你猜想这个程序的执行结果与前面的那个程序的执行结果是一样的话,哈哈,那你这次就错了。这个程序的正确执行结果是:
ch=a
ch=b
怎么能是这样的结果能?根据我刚才给出的那三个与文件有关的三张表慢慢分析,可以得出答案来。
首先,是父进程打开了一个文件“test”,那么父进程就会把为“test”分配的文件描述符加到自己的描述符表中,并为之创建一个新的文件表表项加到文件表中(注意这个文件表是所有进程共享的),再新建一个v-node表表项加到v-node表中。当父进程创建一个新的进程之后,子进程就会继承父进程的打开文件(对于标准输入、输出这些文件,我还不清楚,我猜想应该是不继承的)。至于子进程在这这种情况下是如何继承的,应该只继承了一个文件描述符而已,而文件表表项和v-node表项是不继承的(至于原因,应该是与创建进程有关,因为进程创建的时候,只是复制父进程的变量而已)。这个继承之后的结果如下图1所示:
看到这里,对于上面的结果为什么是
ch=a
ch=b
就不奇怪了吧。