在 child 进程中打开文件是否会在内核中的系统打开文件 table 中创建一个单独的条目?
Does opening a file in a child process create a separate entry in the system open file table in the kernel?
我了解到在调用 fork() 之后,child 进程继承了其 parent 的 per-process 文件描述符 table(指向相同的 system-wide 打开文件 tables)。因此,当在 parent 进程中打开文件然后调用 fork() 时,child 和 parent 都可以写入该文件而不会覆盖彼此的输出(由于共享偏移量在 open-file table 条目中)。
但是,假设我们在 fork 之后(在 parent 和 child 中)对某个文件 调用 open()。这会在 system-wide 打开的文件 table 中创建一个 单独的条目 ,并为 [=33] 使用一组单独的偏移量和 read-write 权限标志=](尽管它在技术上是同一个文件)?我已经尝试查找此内容,但似乎无法找到明确的答案。
我问这个主要是因为我在玩写文件,似乎只有 parent 和 child 的输出最终出现在上述文件中情况。这似乎意味着打开文件 table 中有两个单独的打开调用的单独条目,因此有单独的偏移量,因此较慢的进程会覆盖另一个进程的输出。
为了说明这一点,请考虑以下代码:
int main(void) {
int fd;
if(!fork()) {
/* child */
fd = open("output", O_CREAT|O_TRUNC|O_WRONLY, 0666);
write(fd, "hello ", 6);
_exit(0);
} else {
fd = open("output", O_CREAT|O_TRUNC|O_WRONLY, 0666);
write(fd, "world\n", 6);
}
}
这只会打印“hello”或“world”之一。相反,如果我们在分叉之前调用 open()(并在之后删除两个打开的调用),我们会看到“hello world” (或者可能是“world hello”),这是有道理的。那么系统中是否有两个不同的条目 open-file table?
文件和文件描述符 (FD) 之间存在差异。
所有进程共享相同的文件。他们不一定能访问相同的文件,文件也不是它的名字;打开同名的两个不同进程实际上可能不会打开同一个文件,例如,如果第一个文件被重命名或取消链接并且一个新文件与该名称相关联。但如果他们确实打开了同一个文件,则该文件必然是共享的,并且更改将相互可见。
但是文件描述符不是文件。它指的是一个文件( 不是 文件名,见上文),但它还包含其他信息,包括调用 read
和 [= 时使用和更新的文件位置11=]。 (如果不想使用FD中的位置,可以使用“定位”读写,pread
和pwrite
。)文件descriptors在 parent 和 child 进程之间共享,因此文件在 FD 中的位置也是共享的。
另一个存储在文件描述符(在内核中,用户进程无法访问它)中的东西是允许的操作列表(在 Unix 上,读取、写入、and/or 执行,并且可能其他)。权限存储在文件目录中,而不是文件本身,并且在打开文件时将请求的权限复制到文件描述符中(如果权限可用。) child 进程可能有一个与 parent 不同的用户或组,特别是如果 parent 以增强权限启动但在生成 child 之前删除它们。以这种方式打开的文件的文件描述符仍然具有与 child 共享的相同权限,即使 child 本身能够打开该文件。
根据POSIX,每次使用F_DUPFD
或F_DUPFD_CLOEXEC
命令调用open()
creates both a new open file descriptor and a new open file description. These are not shared with any other process. After a fork()
, the same file descriptor number in the parent and the child are separate file descriptors, but they refer to the same open file description. Similarly, a dup()
or dup2()
call creates a new file descriptor but it refers to the same open file description as the duplicated file descriptor refers to. (You can also use fcntl()
复制一个文件描述符;新的文件描述符也引用了与复制的相同的打开文件描述。)
如果在 fork()
之后的父进程和子进程都分别使用 open()
打开某个文件名,那么(取决于与文件重命名等相关的时间问题)这两个进程有单独的文件描述符(必然——它们是不同的进程),但它们也有单独的文件描述,因此有单独的文件偏移量,即使它们都访问同一个文件。一个人所做的任何更改都可以覆盖另一个人所做的更改。
请注意,文件偏移量(位置)是打开文件描述的 属性 而不是打开文件描述符;它可以在文件描述符之间共享,甚至可以在进程之间共享。
对于您的具体示例,由于您使用的是 O_TRUNC
标志,因此最后写入文件的进程将显示在输出中。将其更改为:
if(!fork()) {
/* child */
fd = open("output.txt", O_CREAT|O_APPEND|O_WRONLY, 0666);
char buff[100];
int written = sprintf(buff, "%d: hello there\n", getpid());
write(fd, buff, strlen(buff));
close(fd);
_exit(0);
} else {
fd = open("output.txt", O_CREAT|O_APPEND|O_WRONLY, 0666);
char buff[100];
int written = sprintf(buff, "%d: hello there\n", getpid());
write(fd, buff, strlen(buff));
close(fd);
wait(NULL);
}
其中不使用 O_TRUNC
标志,而是使用 O_APPEND
标志,两个过程输出都将在文件中显示为
44116: hello there
44117: hello there
另外,也许你可以试试这个例子来澄清你的疑惑
if(!fork()) {
/* child */
int devNUll = open("/dev/null", O_WRONLY, 0666);
fd = open("output.txt", O_CREAT|O_APPEND|O_WRONLY, 0666);
char buff[500];
struct stat fdStats;
fstat(fd, &fdStats);
int written = sprintf(buff, "%d: hello there via fd %d with inode %lld\n", getpid(), fd, fdStats.st_ino);
write(fd, buff, strlen(buff));
printf("%d: waiting....\n", getpid());
sleep(5);
close(fd);
close(devNUll);
_exit(0);
} else {
fd = open("output.txt", O_CREAT|O_APPEND|O_WRONLY, 0666);
char buff[500];
struct stat fdStats;
fstat(fd, &fdStats);
int written = sprintf(buff, "%d: hello there via fd %d with inode %lld\n", getpid(), fd, fdStats.st_ino);
write(fd, buff, strlen(buff));
close(fd);
wait(NULL);
}
我刚刚添加了一个 /dev/null
的虚拟 open 来说明尽管进程有单独的 fd
数字(这是他们 FD table 的索引),他们应该共享基础 output.txt
的相同 inode 编号。在 运行 这个例子中,检查
的输出
ls -i output.txt
并且它应该显示与两个进程都打开的 fds 相同的 inode 编号。所以,output.txt
看起来像这样:
45317: hello there via fd 3 with inode 48259093
45320: hello there via fd 4 with inode 48259093
等
❯ ls -i output.txt
48259093 output.txt
每个进程通过其 fd
拥有自己独立的文件视图,但在 OS 层的下方,它们共享相同的 inode。
我了解到在调用 fork() 之后,child 进程继承了其 parent 的 per-process 文件描述符 table(指向相同的 system-wide 打开文件 tables)。因此,当在 parent 进程中打开文件然后调用 fork() 时,child 和 parent 都可以写入该文件而不会覆盖彼此的输出(由于共享偏移量在 open-file table 条目中)。
但是,假设我们在 fork 之后(在 parent 和 child 中)对某个文件 调用 open()。这会在 system-wide 打开的文件 table 中创建一个 单独的条目 ,并为 [=33] 使用一组单独的偏移量和 read-write 权限标志=](尽管它在技术上是同一个文件)?我已经尝试查找此内容,但似乎无法找到明确的答案。
我问这个主要是因为我在玩写文件,似乎只有 parent 和 child 的输出最终出现在上述文件中情况。这似乎意味着打开文件 table 中有两个单独的打开调用的单独条目,因此有单独的偏移量,因此较慢的进程会覆盖另一个进程的输出。
为了说明这一点,请考虑以下代码:
int main(void) {
int fd;
if(!fork()) {
/* child */
fd = open("output", O_CREAT|O_TRUNC|O_WRONLY, 0666);
write(fd, "hello ", 6);
_exit(0);
} else {
fd = open("output", O_CREAT|O_TRUNC|O_WRONLY, 0666);
write(fd, "world\n", 6);
}
}
这只会打印“hello”或“world”之一。相反,如果我们在分叉之前调用 open()(并在之后删除两个打开的调用),我们会看到“hello world” (或者可能是“world hello”),这是有道理的。那么系统中是否有两个不同的条目 open-file table?
文件和文件描述符 (FD) 之间存在差异。
所有进程共享相同的文件。他们不一定能访问相同的文件,文件也不是它的名字;打开同名的两个不同进程实际上可能不会打开同一个文件,例如,如果第一个文件被重命名或取消链接并且一个新文件与该名称相关联。但如果他们确实打开了同一个文件,则该文件必然是共享的,并且更改将相互可见。
但是文件描述符不是文件。它指的是一个文件( 不是 文件名,见上文),但它还包含其他信息,包括调用 read
和 [= 时使用和更新的文件位置11=]。 (如果不想使用FD中的位置,可以使用“定位”读写,pread
和pwrite
。)文件descriptors在 parent 和 child 进程之间共享,因此文件在 FD 中的位置也是共享的。
另一个存储在文件描述符(在内核中,用户进程无法访问它)中的东西是允许的操作列表(在 Unix 上,读取、写入、and/or 执行,并且可能其他)。权限存储在文件目录中,而不是文件本身,并且在打开文件时将请求的权限复制到文件描述符中(如果权限可用。) child 进程可能有一个与 parent 不同的用户或组,特别是如果 parent 以增强权限启动但在生成 child 之前删除它们。以这种方式打开的文件的文件描述符仍然具有与 child 共享的相同权限,即使 child 本身能够打开该文件。
根据POSIX,每次使用F_DUPFD
或F_DUPFD_CLOEXEC
命令调用open()
creates both a new open file descriptor and a new open file description. These are not shared with any other process. After a fork()
, the same file descriptor number in the parent and the child are separate file descriptors, but they refer to the same open file description. Similarly, a dup()
or dup2()
call creates a new file descriptor but it refers to the same open file description as the duplicated file descriptor refers to. (You can also use fcntl()
复制一个文件描述符;新的文件描述符也引用了与复制的相同的打开文件描述。)
如果在 fork()
之后的父进程和子进程都分别使用 open()
打开某个文件名,那么(取决于与文件重命名等相关的时间问题)这两个进程有单独的文件描述符(必然——它们是不同的进程),但它们也有单独的文件描述,因此有单独的文件偏移量,即使它们都访问同一个文件。一个人所做的任何更改都可以覆盖另一个人所做的更改。
请注意,文件偏移量(位置)是打开文件描述的 属性 而不是打开文件描述符;它可以在文件描述符之间共享,甚至可以在进程之间共享。
对于您的具体示例,由于您使用的是 O_TRUNC
标志,因此最后写入文件的进程将显示在输出中。将其更改为:
if(!fork()) {
/* child */
fd = open("output.txt", O_CREAT|O_APPEND|O_WRONLY, 0666);
char buff[100];
int written = sprintf(buff, "%d: hello there\n", getpid());
write(fd, buff, strlen(buff));
close(fd);
_exit(0);
} else {
fd = open("output.txt", O_CREAT|O_APPEND|O_WRONLY, 0666);
char buff[100];
int written = sprintf(buff, "%d: hello there\n", getpid());
write(fd, buff, strlen(buff));
close(fd);
wait(NULL);
}
其中不使用 O_TRUNC
标志,而是使用 O_APPEND
标志,两个过程输出都将在文件中显示为
44116: hello there
44117: hello there
另外,也许你可以试试这个例子来澄清你的疑惑
if(!fork()) {
/* child */
int devNUll = open("/dev/null", O_WRONLY, 0666);
fd = open("output.txt", O_CREAT|O_APPEND|O_WRONLY, 0666);
char buff[500];
struct stat fdStats;
fstat(fd, &fdStats);
int written = sprintf(buff, "%d: hello there via fd %d with inode %lld\n", getpid(), fd, fdStats.st_ino);
write(fd, buff, strlen(buff));
printf("%d: waiting....\n", getpid());
sleep(5);
close(fd);
close(devNUll);
_exit(0);
} else {
fd = open("output.txt", O_CREAT|O_APPEND|O_WRONLY, 0666);
char buff[500];
struct stat fdStats;
fstat(fd, &fdStats);
int written = sprintf(buff, "%d: hello there via fd %d with inode %lld\n", getpid(), fd, fdStats.st_ino);
write(fd, buff, strlen(buff));
close(fd);
wait(NULL);
}
我刚刚添加了一个 /dev/null
的虚拟 open 来说明尽管进程有单独的 fd
数字(这是他们 FD table 的索引),他们应该共享基础 output.txt
的相同 inode 编号。在 运行 这个例子中,检查
ls -i output.txt
并且它应该显示与两个进程都打开的 fds 相同的 inode 编号。所以,output.txt
看起来像这样:
45317: hello there via fd 3 with inode 48259093
45320: hello there via fd 4 with inode 48259093
等
❯ ls -i output.txt
48259093 output.txt
每个进程通过其 fd
拥有自己独立的文件视图,但在 OS 层的下方,它们共享相同的 inode。