在 Ubuntu 中,我date
直接在 pid 为 6913 的交互式 bash shell 中运行。
$ date
Wed Mar 2 23:57:44 EST 2016
同时,我使用以下命令从另一个交互式 bash shell 跟踪 bash shell 6913 strace
:
$ sudo strace -f -e trace=process -p 6913
Process 6913 attached
clone(Process 9098 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD,
child_tidptr=0x7f457c05ca10) = 9098
[pid 6913] wait4(-1, <unfinished ...>
[pid 9098] execve("/bin/date", ["date"], [/* 66 vars */]) = 0
[pid 9098] arch_prctl(ARCH_SET_FS, 0x7f40d6a4f740) = 0
[pid 9098] exit_group(0) = ?
[pid 9098] +++ exited with 0 +++
<... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WSTOPPED|WCONTINUED, NULL) = 9098
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=9098, si_status=0, si_utime=0, si_stime=0} ---
wait4(-1, 0x7ffea6781518, WNOHANG|WSTOPPED|WCONTINUED, NULL) = -1 ECHILD (No child processes)
clone(Process 9099 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f457c05ca10) = 9099
[pid 9099] clone(Process 9100 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f457c05ca10) = 9100
[pid 9099] wait4(-1, <unfinished ...>
[pid 9100] execve("/bin/sed", ["sed", "s:\\([^/]\\)[^/]*/:\\1/:g"], [/* 66 vars */]) = 0
[pid 9100] arch_prctl(ARCH_SET_FS, 0x7f998bb03840) = 0
[pid 9100] exit_group(0) = ?
[pid 9100] +++ exited with 0 +++
[pid 9099] <... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 9100
[pid 9099] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=9100, si_status=0, si_utime=0, si_stime=0} ---
[pid 9099] wait4(-1, 0x7ffea6780c58, WNOHANG, NULL) = -1 ECHILD (No child processes)
[pid 9099] exit_group(0) = ?
[pid 9099] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=9099, si_status=0, si_utime=0, si_stime=0} ---
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WNOHANG|WSTOPPED|WCONTINUED, NULL) = 9099
wait4(-1, 0x7ffea6780f18, WNOHANG|WSTOPPED|WCONTINUED, NULL) = -1 ECHILD (No child processes)
在我看来,输出可以分为两部分:
首先bash shell 6913
clone()
本身创建一个子进程9098,然后子进程9098execve()
date
并退出。在这些之后,bash shell 6913
clone()
本身创建一个子进程9099,然后子进程9099clone()
本身创建一个子子进程9100,然后子进程9100execve()
sed
。我的问题关于第二部分:最后两个
clone()
和最后一个execve()
是否属于处理 bash shell 6913 接收到的 SIGCHLD 的操作?该动作有什么作用?为什么要处理9100
execve()
sed
?这里做什么sed
?为什么进程 9099 不是那个进程
execve()
sed
?为什么 9099clone()
创建 9100,然后创建 9100execve()
sed
?换句话说,为什么我们需要两个连续的克隆 9099 和 9100,而不是一个克隆 9099?
回答评论:
$ echo $PROMPT_COMMAND
pwd2=$(sed "s:\([^/]\)[^/]*/:\1/:g" <<<$PWD)
$ echo $PS1
\u@\h:$pwd2\$
在shel 6913中运行后unset PROMPT_COMMAND
,跟踪输出为
$ sudo strace -f -e trace=process -p 6913
[sudo] password for t:
Process 6913 attached
clone(Process 12918 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f457c05ca10) = 12918
[pid 6913] wait4(-1, <unfinished ...>
[pid 12918] execve("/bin/date", ["date"], [/* 66 vars */]) = 0
[pid 12918] arch_prctl(ARCH_SET_FS, 0x7ff00c632740) = 0
[pid 12918] exit_group(0) = ?
[pid 12918] +++ exited with 0 +++
<... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WSTOPPED|WCONTINUED, NULL) = 12918
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=12918, si_status=0, si_utime=0, si_stime=0} ---
wait4(-1, 0x7ffea6781518, WNOHANG|WSTOPPED|WCONTINUED, NULL) = -1 ECHILD (No child processes)
那么上面的前两个问题现在已经有了答案。对于第三个问题我还是不太清楚。
答案1
clone(Process 9099 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f457c05ca10) = 9099
[pid 9099] clone(Process 9100 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f457c05ca10) = 9100
[pid 9099] wait4(-1, <unfinished ...>
[pid 9100] execve("/bin/sed", ["sed", "s:\\([^/]\\)[^/]*/:\\1/:g"], [/* 66 vars */]) = 0
这两个分叉(在现代 Linux 系统上,分叉是通过使用clone
系统调用完成的)是由于 bash 评估你的PROMPT_COMMAND
变量,你说的是
pwd2=$(sed "s:\([^/]\)[^/]*/:\1/:g" <<<$PWD)
这些分叉与之前接收到的 SIGCHLD 信号没有直接关系。
这bash手册说:
在 Bash 打印每个主提示之前,会检查变量 PROMPT_COMMAND 的值。如果设置了 PROMPT_COMMAND 并且具有非空值,则执行该值就像在命令行上键入一样。
在内部,bash 最终调用解析并执行来评估 的内容PROMPT_COMMAND
。 Bash 尝试最小化它需要执行的分叉数量。对于诸如 之类的简单语句pwd2=$PWD
,不需要分叉。对于更复杂的语句,可以进行一个或多个分叉。在您的情况下,$( ... )
会产生 shell 的 fork(pid 9099),然后它将评估括号之间的命令。对非内置实用程序的调用sed
会产生另一个 fork,pid 9100,后跟 execve /bin/sed
。
sed 在这里做什么?
它似乎截断了当前工作目录上方每个目录名称中除第一个字符之外的所有字符。