在 fork() 之后寻求有关“文件描述符”的简单描述

Posted

技术标签:

【中文标题】在 fork() 之后寻求有关“文件描述符”的简单描述【英文标题】:Seeking a simple description regarding 'file descriptor' after fork() 【发布时间】:2012-07-28 19:52:41 【问题描述】:

在“Unix 环境中的高级编程”第 2 版中,作者:W. Richard Stevens。第 8.3 节 fork 函数。

这里是描述:

重要的是父子节点共享相同的文件偏移量。

考虑一个派生一个子进程,然后等待子进程完成的进程。假设两个进程都写入标准输出作为其正常处理的一部分。如果父级将其标准输出重定向(可能是通过 shell),那么当子级写入标准输出时,父级的文件偏移量必须由子级更新。

我的回答:

1 这是什么意思?例如,如果父级的 std 输出被重定向到“file1”,那么子级写入后子级应该更新什么?父级的原始 std 输出偏移量或重定向输出(即 file1)偏移量?不可能是后者吧?

2 更新是如何完成的?由子显式,操作系统隐式,文件描述符本身?在 fork 之后,我认为父母和孩子各走各的路,有自己的文件描述符副本。那么child如何更新offset到parent端呢?

在这种情况下,子进程可以在父进程等待时写入标准输出;在孩子完成后,父母可以继续写入标准输出,因为知道它的输出将附加到孩子写的任何内容。如果父级和子级不共享相同的文件偏移量,则这种类型的交互将更难以完成,并且需要父级的显式操作。

如果父母和孩子都写入同一个描述符,没有任何形式的同步,例如让父母等待孩子,他们的输出将混合在一起(假设它是一个在分叉之前打开的描述符)。虽然这是可能的,但这不是正常的操作模式。

分叉后处理描述符有两种正常情况。

    父母等待孩子完成。在这种情况下,父级不需要对其描述符做任何事情。当子进程终止时,子进程读取或写入的任何共享描述符都将相应更新其文件偏移量。

    父母和孩子都各走各的路。在这里,在分叉之后,父级关闭它不需要的描述符,子级也做同样的事情。这样,既不会干扰对方的开放描述符。网络服务器通常会出现这种情况。

我的回答:

3 当 fork() 被调用时,我所理解的只是孩子得到了父母拥有的东西的副本,在这种情况下是文件描述符,并做它的事情。如果父子共享的文件描述符有任何偏移量发生变化,那只能是因为描述符本身记住了偏移量。我说的对吗?

我对这些概念有点陌生。

【问题讨论】:

【参考方案1】:

在书的同一部分,有一个图表显示了打开文件时存在的三个表。

用户文件描述符表(进程表条目的一部分)、文件表和inode表(v-node表)。 现在一个文件描述符(它是文件描述符表的索引)条目指向一个文件表条目,它指向一个 inode 表条目。 现在文件偏移量(下一次读/写发生的位置)在文件表中。

假设你有一个在父级中打开的文件,这意味着它有一个描述符,一个文件 表条目和 inode 引用。 现在,当您创建孩子时,将为孩子复制文件描述符表。 所以文件表条目中的引用计数(对于那个打开的描述符)增加了,这意味着现在对于同一个文件表条目有两个引用。

此描述符现在在父子节点中都可用,指向同一个文件表条目,因此共享偏移量。 现在有了这个背景,让我们看看你的问题,

    这是什么意思?例如,如果父级的 std 输出被重定向到“file1”,那么子级写入后子级应该更新什么?父母的原始标准输出偏移量或 重定向输出(即file1)偏移量?不可能是后者吧?]

孩子明确不需要更新任何东西。这本书的作者试图 告诉它,假设父母的标准输出被重定向到一个文件并进行了一个 fork 调用。之后父级正在等待。所以描述符现在被复制,即文件偏移量也被共享。现在,每当孩子向标准输出写入任何内容时,写入的数据都会保存在重定向文件中。 写入调用会自动增加偏移量。

现在说孩子退出。 所以父母从等待中出来并在标准输出上写了一些东西(这是 重定向)。现在 parent 的 write 调用的输出将放置在 -> 数据之后,该数据由孩子写入。为什么 -> 因为偏移量的当前值现在在孩子写入之后发生了变化。

 Parent ( )
  
    open a file for writing, that is get the 
    descriptor( say fd);
    close(1);//Closing stdout
    dup(fd); //Now writing to stdout  means writing to the file
    close(fd)
        //Create a child that is do a  fork call.
    ret = fork();
    if ( 0 == ret )
    
        write(1, "Child", strlen("Child");
        exit ..
    
        wait(); //Parent waits till child exit.

         write(1, "Parent", strlen("Parent");
    exit ..

Pl。看上面的伪代码,打开的文件包含的最终数据将是 ChildParent。因此,您会看到文件偏移量在子级写入时发生了更改,并且这对父级的 write 调用是可用的,因为 offest 是共享的。

2.更新是如何完成的?由子显式,操作系统隐式,文件描述符本身?分叉后,我以为父母和孩子各走各的路 有自己的文件描述符副本。那么child如何更新offset到parent端呢?]

Now I think the answer is clear-> by the system call that is by the OS.

[3.当 fork() 被调用时,我所理解的是孩子得到父母所拥有的副本, 在这种情况下,文件描述符,并做它的事情。如果父子共享的文件描述符有任何偏移量发生变化,那只能是因为描述符本身记住了偏移量。我说的对吗?]

这也应该很清楚。用户文件表的入口指向文件表 表项(包含偏移量)。

换句话说,系统调用可以从描述符中获取偏移量。

【讨论】:

“文件表条目中的引用计数(对于那个打开的描述符)增加了”。你能指出一个参考吗?这意味着 fork 复制父内存不是浅拷贝。【参考方案2】:

区分文件描述符文件描述很重要,文件描述符是进程在读取和写入调用中用来识别文件的一个小整数,这是内核中的一个结构。文件偏移量是文件描述的一部分。它存在于内核中。

作为一个例子,让我们使用这个程序:

#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>

int main(void)

    int fd;

    fd = open("output", O_CREAT|O_TRUNC|O_WRONLY, 0666);

    if(!fork()) 
        /* child */
        write(fd, "hello ", 6);
        _exit(0);
     else 
        /* parent */
        int status;

        wait(&status);
        write(fd, "world\n", 6);
    

(已省略所有错误检查)

如果我们编译这个程序,调用它hello,然后像这样运行它:

./hello

发生了什么:

程序打开output 文件,如果它不存在则创建它,如果它存在则将其截断为零大小。内核创建一个文件描述(在 Linux 内核中这是一个struct file)并将其与调用进程的文件描述符(该进程的文件描述符表中尚未使用的最小非负整数)相关联。文件描述符被返回并分配给程序中的fd。为了论证起见,假设fd 是 3。

程序执行 fork()。新的子进程获得其父文件描述符表的副本,但不会复制文件描述。两个进程的文件表中的条目号 3 指向相同的struct file

父进程等待子进程写入。孩子的写导致"hello world\n"的前半部分存入文件中,文件偏移量前移6。文件偏移量在struct file中!

子进程退出,父进程的wait() 完成,父进程使用 fd 3 写入,该文件描述仍然与相同的文件描述相关联,该文件的偏移量由子进程的 write() 更新。因此,消息的后半部分存储在第一部分之后,而不是像父文件偏移量为零时那样覆盖它,如果文件描述不存在,就会出现这种情况共享。

最终父级退出,内核看到struct file不再使用并释放它。

【讨论】:

艾伦,这个解释太棒了!感谢您花时间为新手详细解释。 @AlanCurry 如果我不在父程序中使用 wait() 怎么办?当父进程退出时,文件描述是否免费? "获取其父文件描述符表的副本,但不复制文件描述。"这是文件描述符的特定行为(内核描述是共享的事实)吗?因为例如全局变量是复制的,而不是共享的,afaik。 @Allan Curry,你为什么叫_exit而不是exit?请您解释一下与您的答案的关系吗?

以上是关于在 fork() 之后寻求有关“文件描述符”的简单描述的主要内容,如果未能解决你的问题,请参考以下文章

在进程间传递文件描述符

linux tcp socket并发编程,调用accept函数后调用fork。两台电脑测试,为何accept返回的描述符是相同的?

Linux报错-bash: fork: retry: Resource temporarily unavailable和进程描述符之间的关系

Linux报错-bash: fork: retry: Resource temporarily unavailable和进程描述符之间的关系

在不同进程之间传递文件描述符的可移植方式

Linux 面试