我对命令的执行有一个简单的疑问ls
。根据我在互联网上所做的研究的理解,我理解以下几点。
当我们输入
ls
命令时,shell 会解释该命令。然后,shell 进程分叉并创建子进程,父进程(shell)执行
wait()
系统调用,有效地将自身置于睡眠状态,直到子进程退出。子进程继承所有打开的文件描述符和环境。
子进程(shell)执行程序的一部分
exec()
,ls
导致ls
二进制文件从磁盘(文件系统)加载并在同一进程中执行。当
ls
程序运行完成时,它调用exit()
,并且内核向其父进程发送一个信号,指示子进程已终止。
我的疑问从这里开始,一旦ls
完成它的任务;它是将结果发送回父进程,还是将输出显示到屏幕上?如果它将输出发送回父级,那么它是否pipe()
隐式使用?
答案1
ls
将输出它必须输出的内容标准输出。为此,它调用write
系统调用,例如:
write(1, "file1 file2...\n", 16)
(或更可能它调用类似或最终执行系统调用的libc
函数)printf
fwrite
write()
它假设文件描述符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
只是另一个进程,其fd
1 连接到某些资源(例如终端),而 shell 是另一个忙于执行waitpid()
.
但您可能会发现,当 stdout 不是终端时,它会缓冲其输出,并且仅在积累了足够的数据(足够几千字节)或正在关闭其输出或正在退出时ls
才会调用。write()
因此,在这方面,您会发现退出时已完成,但仅作为这些 I/O 库正在完成的缓冲区的write
一部分。flushing
答案2
答案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)
成功,调用它的程序就不再存在。它被程序exec
ed 取代。运行的新程序继承了原始程序的环境,特别是打开的文件。