ls 命令中的 exec() 之后发生了什么:父进程是将输出打印到控制台还是子进程?

ls 命令中的 exec() 之后发生了什么:父进程是将输出打印到控制台还是子进程?

我对命令的执行有一个简单的疑问ls。根据我在互联网上所做的研究的理解,我理解以下几点。

  1. 当我们输入ls命令时,shell 会解释该命令。

  2. 然后,shell 进程分叉并创建子进程,父进程(shell)执行wait()系统调用,有效地将自身置于睡眠状态,直到子进程退出。

  3. 子进程继承所有打开的文件描述符和环境。

  4. 子进程(shell)执行程序的一部分exec()ls导致ls二进制文件从磁盘(文件系统)加载并在同一进程中执行。

  5. ls程序运行完成时,它调用exit(),并且内核向其父进程发送一个信号,指示子进程已终止。

我的疑问从这里开始,一旦ls完成它的任务;它是将结果发送回父进程,还是将输出显示到屏幕上?如果它将输出发送回父级,那么它是否pipe()隐式使用?

答案1

ls将输出它必须输出的内容标准输出。为此,它调用write系统调用,例如:

 write(1, "file1  file2...\n", 16)

(或更可能它调用类似或最终执行系统调用的libc函数)printffwritewrite()

它假设文件描述符1(按照惯例为 stdout)已经打开并指向某些内容。实际上,ls确实检查其文件描述符 1 是否指向终端或其他东西。如果它不指向终端,则会指向终端:

 write(1, "file1\nfile2...\n", 15)

也就是说,当输出未发送到终端时,它会每行写入一个文件。

当你写:

 ls file1 file2

ls的文件描述符 1 将指向与 shell 的 fd 1 相同的资源(例如,如果它是由 启动的交互式 shell xterm,则它将指向由 xterm 控制的伪终端设备)。 shell 没有做任何特殊的事情,它是继承的fork,并且由于O_CLOEXEC通常不为文件描述符设置该标志,因此它被保留在execve.

如果你写:

var=$(ls file1 file2)

shell创建一个管道,并将子进程的fd 1分配给该管道的写入端,并读取管道的另一端来填充变量var

它并不是在退出时神奇地完成的,它只是作为进程工作的一部分完成的。它独立于 shell 的任何其他活动。ls只是另一个进程,其fd1 连接到某些资源(例如终端),而 shell 是另一个忙于执行waitpid().

但您可能会发现,当 stdout 不是终端时,它会缓冲其输出,并且仅在积累了足够的数据(足够几千字节)或正在关闭其输出或正在退出时ls才会调用。write()因此,在这方面,您会发现退出时已完成,但仅作为这些 I/O 库正在完成的缓冲区的write一部分。flushing

答案2

通常父进程通过调用等待直到子进程结束waitpid。父进程获取该进程的PIDfork

这意味着子进程永远不会以任何方式向父进程发出退出或发生了什么的信号。这是由系统而不是子进程完成的。

如果您正在谈论程序的输出,则父进程通常永远不会接收子进程的输出,除非它提供了 fds。这也意味着子进程打印输出并不是父进程。父进程只接收有关进程状态的信息(有关更多信息,请参阅waitpid手册页)

答案3

严格来讲两者都不父级和子级的实际输出到屏幕;相反,操作系统内核(特别是tty驱动程序)执行输出。进程仅将数据发送到文件描述符。

另外,不要strace -f /bin/bash "ls;exit"清楚地推测谁正在向文件描述符写入什么内容(BASH 似乎通过不分叉来优化“-c ls”,所以我之后使用了“;exit”),如下所示(bash 使用clone,而不是fork):

...
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f9814b5d9d0) = 30029
Process 30029 attached
[pid 30028] rt_sigprocmask(SIG_SETMASK, [],  <unfinished ...>
[pid 30029] rt_sigprocmask(SIG_SETMASK, [],  <unfinished ...>
...
[pid 30028] wait4(-1,  <unfinished ...>
...
[pid 30029] execve("/usr/bin/ls", ["ls"], [/* 72 vars */]) = 0
...
[pid 30029] write(1, "00708022-PTF-26962\nCASE00280714\n"..., 18500708022-PTF-26962) = 185
...
[pid 30029] exit_group(0)               = ?
[pid 30029] +++ exited with 0 +++
<... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 30029
...

所以你看到实际上 PID 30029(子进程)写出文件名,退出,然后父进程继续(在 后wait4)。

答案4

如果exec(3)成功,调用它的程序就不再存在。它被程序execed 取代。运行的新程序继承了原始程序的环境,特别是打开的文件。

相关内容