了解dup2和关闭文件描述符

时间:2015-06-08 16:19:56

标签: c++ linux dup2

我发布我的代码只是为了提问我的问题。我没有明确地寻找你来帮助修复它,我更希望了解我从手册页和其他许多堆栈溢出问题中找不到的dup2系统调用。

    pid = fork();

    if(pid == 0) {
        if(strcmp("STDOUT", outfile)) {
            if (command->getOutputFD() == REDIRECT) {
                if ((outfd = open(outfile, O_CREAT | O_WRONLY | O_TRUNC)) == -1)
                    return false;
                command->setOutputFD(outfd);
                if (dup2(command->getOutputFD(), STDOUT_FILENO) == -1)
                    return false;
                pipeIndex++;
            }
            else if (command->getOutputFD() == REDIRECTAPPEND) {
                if ((outfd = open(outfile, O_CREAT | O_WRONLY | O_APPEND)) == -1)
                    return false;
                command->setOutputFD(outfd);
                if (dup2(command->getOutputFD(), STDOUT_FILENO) == -1)
                    return false;
                pipeIndex++;
            }
            else {
                if (dup2(pipefd[++pipeIndex], STDOUT_FILENO) == -1)
                    return false;
                command->setOutputFD(pipefd[pipeIndex]);
            }
        }

        if(strcmp("STDIN", infile)) {
            if(dup2(pipefd[pipeIndex - 1], STDIN_FILENO) == -1)
                return false;
            command->setOutputFD(pipefd[pipeIndex - 1]);
            pipeIndex++;
        }


        if (execvp(arguments[0], arguments) == -1) {
            std::cerr << "Error!" << std::endl;
            _Exit(0);
        }

    }

    else if(pid == -1) {
        return false;
    }

对于您的上下文,该代码表示​​基本Linux shell的执行步骤。命令对象包含命令参数,IO&#34; name&#34;和IO描述符(我想我可能会将文件描述符删除为字段)。

我最难理解的是何时以及关闭哪些文件描述符。我想我会问一些问题,试着提高我对这个概念的理解。

1)使用我用于处理管道的文件描述符数组,父项具有所有这些描述符的副本。父母持有的描述符何时关闭?甚至更多,哪些描述符?是这些吗?所有那些都没有被执行命令使用?

2)当处理子节点内的管道时,哪些描述符被哪些进程打开?如果我执行命令,请说:ls -l | grep的 &#34; [用户名]&#34;,应该为ls进程保留哪些描述符?只是管道的写端?如果是这样的话?同样的问题适用于grep命令。

3)当我处理IO重定向到文件时,必须打开一个新文件并使用STDOUT(我不支持输入重定向)。这个描述符什么时候关闭?我在示例中看到它在调用dup2之后立即关闭,但是如果文件已经关闭,那么如何将任何内容写入文件?

提前谢谢。我已经坚持这个问题好几天了,我真的很想完成这个项目。

编辑我已经为修改过的代码和示例输出更新了这个内容,以便有兴趣为我的问题提供特定帮助的任何人。首先,我有处理执行的整个for循环。我已经通过调用关闭各种文件描述符来更新它。

while(currCommand != NULL) {

    command = currCommand->getData();

    infile = command->getInFileName();
    outfile = command->getOutFileName();
    arguments = command->getArgList();

    pid = fork();

    if(pid == 0) {
        if(strcmp("STDOUT", outfile)) {
            if (command->getOutputFD() == REDIRECT) {
                if ((outfd = open(outfile, O_CREAT | O_WRONLY | O_TRUNC)) == -1)
                    return false;
                if (dup2(outfd, STDOUT_FILENO) == -1)
                    return false;
                close(STDOUT_FILENO);
            }
            else if (command->getOutputFD() == REDIRECTAPPEND) {
                if ((outfd = open(outfile, O_CREAT | O_WRONLY | O_APPEND)) == -1)
                    return false;
                if (dup2(outfd, STDOUT_FILENO) == -1)
                    return false;
                close(STDOUT_FILENO);
            }
            else {
                if (dup2(pipefd[pipeIndex + 1], STDOUT_FILENO) == -1)
                    return false;
                close(pipefd[pipeIndex]);
            }
        }
        pipeIndex++;

        if(strcmp("STDIN", infile)) {
            if(dup2(pipefd[pipeIndex - 1], STDIN_FILENO) == -1)
                return false;
            close(pipefd[pipeIndex]);
            pipeIndex++;
        }

        if (execvp(arguments[0], arguments) == -1) {
            std::cerr << "Error!" << std::endl;
            _Exit(0);
        }
    }

    else if(pid == -1) {
        return false;
    }

    currCommand = currCommand->getNext();

}

for(int i = 0; i < numPipes * 2; i++)
    close(pipefd[i]);

for(int i = 0; i < commands->size();i++) {
    if(wait(status) == -1)
        return false;
}

执行此代码时,我收到以下输出

ᕕ( ᐛ )ᕗ ls -l
total 68
-rwxrwxrwx 1 cook cook   242 May 31 18:31 CMakeLists.txt
-rwxrwxrwx 1 cook cook   617 Jun  1 22:40 Command.cpp
-rwxrwxrwx 1 cook cook  9430 Jun  8 18:02 ExecuteExternalCommand.cpp
-rwxrwxrwx 1 cook cook   682 May 31 18:35 ExecuteInternalCommand.cpp
drwxrwxrwx 2 cook cook  4096 Jun  8 17:16 headers
drwxrwxrwx 2 cook cook  4096 May 31 18:32 implementation files
-rwxr-xr-x 1 cook cook 25772 Jun  8 18:12 LeShell
-rwxrwxrwx 1 cook cook   243 Jun  5 13:02 Makefile
-rwxrwxrwx 1 cook cook   831 Jun  3 12:10 Shell.cpp
ᕕ( ᐛ )ᕗ ls -l > output.txt
ls: write error: Bad file descriptor
ᕕ( ᐛ )ᕗ ls -l | grep "cook"
ᕕ( ᐛ )ᕗ 

ls -l > output.txt的输出意味着我关闭了错误的描述符,但是在没有错误的情况下关闭其他相关描述符并没有向文件提供输出。正如ls -l所示,grep "cook"应该生成输出到控制台。

1 个答案:

答案 0 :(得分:8)

  

使用我用于处理管道的文件描述符数组,父项   有一份所有这些描述符的副本。描述符何时被保留   父母关闭了吗?甚至更多,哪些描述符?是全部吗?   他们?所有那些都没有被执行命令使用?

文件描述符可以通过以下三种方式之一关闭:

  1. 您明确地在其上调用close()
  2. 进程终止,操作系统自动关闭仍处于打开状态的每个文件描述符。
  3. 当进程调用七个exec()函数之一且文件描述符具有O_CLOEXEC标志时。
  4. 正如您所看到的,大多数情况下,文件描述符将保持打开状态,直到您手动关闭它们。这也是您的代码中发生的事情 - 因为您没有指定O_CLOEXEC,所以当子进程调用execvp()时,文件描述符不会关闭。在孩子中,他们在孩子终止后关闭。父母也一样。如果您希望在终止之前随时发生这种情况,则必须手动呼叫close()

      

    处理子项内的管道时,会留下哪些描述符   打开哪个流程?如果我执行命令,请说:ls -l | grep的   &#34; [用户名]&#34;,哪些描述符应该留给ls   处理?只是管道的写端?如果是这样的话?相同   问题适用于grep命令。

    这是[粗略]了解当您输入ls -l | grep "username"时shell执行的操作:

    1. shell调用pipe()来创建新管道。管道文件描述符将在下一步中由子项继承。
    2. shell分叉两次,让我们调用这些进程c1c2。我们假设c1将运行lsc2将运行grep
    3. c1中,管道的读取通道已使用close()关闭,然后使用管道写入通道和dup2()调用STDOUT_FILENO,以便写入stdout等同于写入管道。然后,调用七个exec()函数中的一个来开始执行lsls写入stdout,但由于我们将stdout复制到管道的写入通道,ls将写入管道。
    4. c2中,相反的情况发生:管道的写入通道已关闭,然后调用dup2()使stdin指向管道的读取渠道。然后,调用七个exec()函数中的一个来开始执行grepgrepstdin读取,但由于我们dup2()是管道读取通道的标准输入,grep将从管道中读取。
    5.   

      当我处理IO重定向到文件时,必须打开一个新文件   并欺骗STDOUT(我不支持输入重定向)。什么时候   这个描述符被关闭了?我在例子中看到它被关闭了   在调用dup2之后立即,但接下来怎么办   如果文件已经关闭,写入文件?

      因此,当您致电dup2(a, b)时,其中任何一个都是真的:

      • a == b。在这种情况下,没有任何反应,dup2()过早返回。没有关闭文件描述符。
      • a != b。在这种情况下,如果需要,b将关闭,然后b将引用与a相同的文件表条目。文件表条目是包含当前文件偏移量和文件状态标志的结构;多个文件描述符可以指向同一个文件表条目,这正是复制文件描述符时发生的情况。因此,dup2(a, b)可以使ab共享相同的文件表条目。因此,写入ab将最终写入同一文件。因此,关闭的文件是b,而不是a。如果您dup2(a, STDOUT_FILENO),则关闭stdout,并使stdout的文件描述符指向与a相同的文件表条目。写入stdout的任何程序都将写入该文件,因为stdout的文件描述符指向您复制的文件。

      <强>更新

      因此,针对您的具体问题,以下是我在简要查看代码后要说的内容:

      你不应该在这里打电话给close(STDOUT_FILENO)

      if (command->getOutputFD() == REDIRECT) {
          if ((outfd = open(outfile, O_CREAT | O_WRONLY | O_TRUNC)) == -1)
              return false;
          if (dup2(outfd, STDOUT_FILENO) == -1)
              return false;
          close(STDOUT_FILENO);
      }
      

      如果您关闭stdout,当您尝试写入stdout时,将来会出现错误。这就是你得到ls: write error: Bad file descriptor的原因。毕竟,ls正在写信给stdout,但您已将其关闭。糟糕!

      您反向执行此操作:您想要关闭outfd。您打开outfd以便将STDOUT_FILENO重定向到outfd,重定向完成后,您不再需要outfd,您可以关闭它。但您绝对不想关闭stdout,因为我的想法是stdout写入outfd引用的文件。

      所以,继续这样做:

      if (command->getOutputFD() == REDIRECT) {
          if ((outfd = open(outfile, O_CREAT | O_WRONLY | O_TRUNC)) == -1)
              return false;
          if (dup2(outfd, STDOUT_FILENO) == -1)
              return false;
          if (outfd != STDOUT_FILENO)
              close(outfd);
      }
      

      请注意,最后的if是必要的:如果outfd恰好等于STDOUT_FILENO,那么您不会因为我刚才提到的原因而关闭else if (command->getOutputFD() == REDIRECTAPPEND)

      同样适用于outfd内的代码:您要关闭STDOUT_FILENO而不是else if (command->getOutputFD() == REDIRECTAPPEND) { if ((outfd = open(outfile, O_CREAT | O_WRONLY | O_APPEND)) == -1) return false; if (dup2(outfd, STDOUT_FILENO) == -1) return false; if (outfd != STDOUT_FILENO) close(STDOUT_FILENO); }

      ls -l

      至少应该让pipefd按预期工作。

      至于管道的问题:你的管道管理不是很正确。从您显示的代码中可以看出outfile的分配位置和方式,以及您创建的管道数量,但请注意:

      1. 进程永远无法从管道读取并写入另一个管道。例如,如果STDOUT不是infileSTDIN不是{{1}},那么您最终会关闭读取和写入通道(更糟糕的是,在关闭读取后)频道,你试图复制它)。这无法发挥作用。
      2. 父进程在等待终止子进程之前关闭每个管道。这引发了竞争条件。
      3. 我建议重新设计你管理管道的方式。在这个答案中,您可以看到一个使用管道的工作裸骨外壳的示例:https://stackoverflow.com/a/30415995/2793118