2015-06-08 112 views
1

我发布我的代码只是为了我的问题的上下文。我并没有明确地寻找你来帮助解决它,我更期望理解dup2系统调用,我只是从手册页和许多其他的stackoverflow问题中找不到。了解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“名称”和IO描述符(我想我可能会将文件描述符作为字段除去)。

我最难理解的是什么时候和哪个文件描述符关闭。我想我会问一些问题,试图提高我对这个概念的理解。

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

2)在处理子级内的管道时,哪些描述符由哪些进程保持打开状态?说如果我执行命令:ls -l | grep “[username]”,应该为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 -lgrep "cook"所示,应该生成输出到控制台。

回答

7

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

文件描述符可能会以三种方式进行封闭:

  1. 你明确地调用它close()
  2. 进程终止,操作系统自动关闭每个仍处于打开状态的文件描述符。
  3. 当进程调用七个exec()函数之一并且文件描述符具有O_CLOEXEC标志时。

正如您所看到的,大多数情况下,文件描述符将保持打开状态,直到您手动关闭它们。这也是您的代码中发生的情况 - 因为您没有指定O_CLOEXEC,所以在子进程调用execvp()时文件描述符未关闭。孩子在孩子终止后关闭。父母也是如此。如果您希望在终止之前发生这种情况,您必须手动拨打close()

当处理孩子内的管道时,哪些描述符留下了 打开哪个进程?说如果我执行命令:ls -l | grep “[username]”,应该为ls 进程保留哪些描述符?只是管道的写入结束?如果是的话?同样的 问题也适用于grep命令。

这里的壳做什么(粗糙)的想法,当你键入ls -l | grep "username"

  1. 外壳调用pipe()创建一个新的管道。管道文件描述符在下一步中由子代继承。
  2. 壳分叉两次,我们称这些过程为c1c2。我们假设c1将运行lsc2将运行grep
  3. c1,管道的读取通道是封闭的close(),然后调用dup2()与管道写通道和STDOUT_FILENO,从而使写入stdout等同于书面形式向管道。然后,调用七个exec()函数之一开始执行lsls写入stdout,但由于我们将stdout复制到管道的写入通道,因此ls将写入管道。
  4. c2中,会发生相反的情况:管道的写通道关闭,然后调用dup2()使stdin指向管道的读通道。然后,调用七个exec()函数中的一个开始执行grepgrepstdin中读取,但由于我们的dup2()'d标准输入到管道的读取通道,因此grep将从管道中读取。

当我处理IO的重定向到一个文件,新文件必须打开 和欺骗到标准输出(我不支持输入重定向)。 这个描述符何时关闭?我在例子中看到它在调用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的任何程序都将写入该文件。

UPDATE

因此,对于您的特定问题,这里就是我有后通过代码简单地看说:

你不应该调用此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是如何分配的代码清晰,你有多少管道创建,但是请注意:

  1. 一个进程将永远无法从管道读取和写入到另一个管道。例如,如果outfile不是STDOUTinfile不是STDIN,则最终会关闭读取和写入通道(更糟糕的是,在关闭读取通道后,您尝试复制它)。这将无法工作。
  2. 父进程在等待孩子终止之前关闭每个管道。这引发了一个竞争条件。

我建议重新设计管理管道的方式。你可以在这个答案中看到一个工作的裸骨壳的例子:https://stackoverflow.com/a/30415995/2793118

+0

感谢您对dup2系统调用的详细解答。但是在关闭我认为合适的文件描述符之后,我仍然无法让dup2正常运行。我绝对想要独自完成这个项目,但我真的迷失在这里。我已经更新了原始文章,并提供了样本和更新的代码,如果您可以检查并提供更有针对性的建议。 再次,谢谢。我更好地理解函数调用,但我仍然无法将其应用于上下文中。 – MichaelCook

+0

@MichaelCook请检查更新后的答案。 –