38

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

这是描述:

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

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

我的回应:

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

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

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

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

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

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

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

我的回复:

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

我对这些概念有点陌生。

4

2 回答 2

95

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

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

#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 内核中是 a struct file)并将其与调用进程的文件描述符(该进程的文件描述符表中尚未使用的最小非负整数)相关联。在程序中返回并分配文件描述符fd。为了论证起见,假设fd是 3。

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

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

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

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

于 2012-07-31T06:28:30.547 回答
5

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

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

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

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

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

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

现在说孩子退出。所以父母从等待中出来并在标准输出(被重定向)上写了一些东西。现在 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.更新是如何完成的?由子显式,操作系统隐式,文件描述符本身?在 fork 之后,我认为父母和孩子各走各的路,拥有自己的文件描述符副本。那么子级如何将偏移量更新到父级?]

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

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

这也应该清楚。用户文件表的条目指向文件表表条目(其中包含偏移量)。

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

于 2012-07-31T06:55:31.503 回答