无法跟踪逻辑和代码输出包括 dup() dup2() 和 fork()

Having trouble tracing logic and output of code includes dup() dup2() and fork()

在操作系统考试的一个问题中,我一直在尝试跟踪以下代码但没有成功。

题目说假设是:

有人可以跟踪这段代码并为我绘制文件描述符数组吗?。 提前致谢..

main() {
    char buf[1024];
    int fd_foo = open("foo.txt", O_RDONLY);
    if (fd_foo != 4) {
        dup2(fd_foo, 4);

        close(fd_foo);
    }
    int fd_bar = open("bar.txt", O_RDONLY);
    if (fd_bar != 0) {
        close(0);
        dup(fd_bar);
        close(fd_bar);
    }
    switch (fork()) {
    case -1: exit(1);
    case 0:
        dup2(4, 5);
        close(4);
        execl("child", "child", (char *)NULL);
        break;
    default:
        wait(NULL);
        read(4, buf, 1);
        write(1, buf, 1);
    }
} // main

子源文件内容。

int main() {
    char buf[3];
    read(5, buf, 1);
    write(1, buf, 1);
    read(0, buf, 3);

    write(1, buf, 3);
}

先看主文件中的main(),再看子文件中main()的流程。

在我们开始之前,让我们回顾一下 C 应用程序在 Linux 下启动时的标准文件句柄分配,来自 stdout(3) - Linux man page

On program startup, the integer file descriptors associated with the streams stdin, stdout, and stderr are 0, 1, and 2, respectively. The preprocessor symbols STDIN_FILENO, STDOUT_FILENO, and STDERR_FILENO are defined with these values in . (Applying freopen(3) to one of these streams can change the file descriptor number associated with the stream.)

接下来让我们回顾一下 dup() 系统调用的作用,来自 DUP(2) Linux Programmer's Manual.

The dup() system call creates a copy of the file descriptor oldfd, using the lowest-numbered unused file descriptor for the new descriptor.

After a successful return, the old and new file descriptors may be used interchangeably. They refer to the same open file description (see open(2)) and thus share file offset and file status flags; for example, if the file offset is modified by using lseek(2) on one of the file descriptors, the offset is also changed for the other.

主文件中的 main() 如下所示,注释为注释:

main() {
    char buf[1024];

    // open the file foo.txt and then dup() the file handle received from the open()
    // to be file handle number 4. Close the original file handle received.
    int fd_foo = open("foo.txt", O_RDONLY);
    if (fd_foo != 4) {
        dup2(fd_foo, 4);

        close(fd_foo);
    }
    // at this point the file handle 4 refers to the file foo.txt

    // open the file bar.txt and make sure that the file handle received is file handle
    // handle 0. if not then we close file handle 0 and dup the file handle to bar.txt
    // File handle 0 is Standard Input or STDIN.
    int fd_bar = open("bar.txt", O_RDONLY);
    if (fd_bar != 0) {
        close(0);
        dup(fd_bar);
        close(fd_bar);
        // Since dup() looks for the lowest numbered file descriptor and we have
        // just closed file descriptor 0, the result of dup() is to now have the
        // file bar.txt to also be accessed through file handle 0.
    }
    // at this point we have the following file assignments:
    //  - file handle 0 which was to Standard In is now file bar.txt
    //  - file handle 1 is to Standard Out
    //  - file handle 2 is to Standard Error
    //  - file handle 4 is to file foo.txt

    // now do a fork and the forked process will then execute the program whose
    // source code is in the child source file. the child process will
    // inherit our open file handles since we did not specify otherwise.
    switch (fork()) {
    case -1: exit(1);       // if error just exit.
    case 0:
        // we be the forked process so we now
        //  - dup file handle 4 to file handle 5 and close 4
        //  - load in the child process on top of ourselves
        //  - loaded child process will inherit our open file handles
        dup2(4, 5);
        close(4);
        execl("child", "child", (char *)NULL);
        // at this point we now jump to the source code of the child source file
        break;
    default:
        // we are the parent process so now lets just wait for the child to
        // finish. Once it has finished we will then do some final file I/O
        // then exit.
        // Note: while the forked process closed file handle 4, the parent process
        //       has not so file handle 4 is still valid for the parent.
        wait(NULL);
        read(4, buf, 1);     // read 1 character from file foo.txt
        write(1, buf, 1);    // write it to Standard Output
    }
} // main

已启动的子进程。

首先查看fork子进程在加载子应用程序之前设置的子进程环境execl()

  • 文件句柄 5 附加到打开的文件 foo.txt
  • 文件句柄 0 附加到打开的文件 bar.txt
  • 文件句柄 1 附加到标准输出

子文件源代码为

int main() {
    char buf[3];
    read(5, buf, 1);    // read one character from foo.txt, the letter "a" from the string "abcdef"
    write(1, buf, 1);   // write it to Standard Out
    read(0, buf, 3);    // read three characters from bar.txt, the string "567"

    write(1, buf, 3);   // write them to Standard out
}

这一切的结果如下I/O.

  • 主进程启动,设置文件描述符,fork,加载子进程

  • 主进程等待子进程完成

  • 子进程从文件 foo.txt 中读取 "a",留下 "bcdef" 未读。

  • 子进程将 "a" 写入标准输出

  • 子进程从文件中读取“567”bar.txt 没有任何未读内容

  • 子进程将“567”写入标准输出

  • 子进程退出

  • 主进程继续运行

  • 主进程从文件 foo.txt 中读取 "b",留下 "cdef" 未读

  • 主进程将 "b" 写入标准输出

  • 主进程退出

这样做的结果是"a567b"被这两个协作进程写入标准输出。它们共享相同的两个文件,尽管 foo.txt 由两个不同的文件描述符访问,并且它们共享相同的标准输出。