Michael
Posted on May 29, 2024
问题
Bash使用或者读各种文档时候,提到很多情况使用子进程执行,另外有的文档说执行命令时候,直接新建子进程,然后在子进程中执行,父进程等待知道子进程执行结束,对此有些疑惑,本文使用strace
查看子进程和信号情况。本次使用Bash版本为5.1.x
bash --version
# GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)
# ...
背景知识
- 新建进程和执行过程。Linux进程执行先使用
fork
库函数(glibc)调用系统调用clone
新建子进程,然后在子进程中使用exec各种库函数(glibc)(比如execl
等)调用execve
系统调用执行命令。上面提到的两个系统调用clone
和execve
可以使用strace
查看。 - Bash有多种情况会使用子进程执行,比如
$(command)
,|
, 外部命令等 - strace工具用于trace系统调用和信号,官方的man文档可以很快的掌握。本次主要使用如下命令查看:
# 2763是Bash进程pid
# `-f` 跟踪子进程
# `-v` 查看详细参数
# 如果想过滤掉signal,比如SIGCHLD,使用`-e signal=none`
# 如果想过滤掉exit和attach消息,使用`-qq`或者自定义`--quiet=exit`
sudo strace -f -e execve -p 2763
实操
使用strace
查看子进程和信号情况
打开两个terminal,一个用于执行Bash命令,一个执行strace
查看结果。
- 获取前者的进程id
echo $$ # 获取Bash pid,比如2763
- 在另一个terminal中输入上面所述的
strace
命令,使用获取的pid替换 - 在Bash终端中输入
echo hello
, 会发现strace终端没有输出 - 在Bash终端中输入
echo hello | xargs printf "%s\n"
, 后者会有类似输出
strace: Process 2763 attached
strace: Process 3016 attached
strace: Process 3017 attached
[pid 3016] +++ exited with 0 +++
[pid 3017] execve("/usr/bin/xargs", ["xargs", "printf", "%s\\n"], 0x5575977a96d0 /* 55 vars */) = 0
strace: Process 3018 attached
[pid 3018] execve("/home/shouhua/.local/bin/printf", ["printf", "%s\\n", "hello"], 0x7fffdf063448 /* 55 vars */) = -1 ENOENT (No such file or directory)
[pid 3018] execve("/usr/local/sbin/printf", ["printf", "%s\\n", "hello"], 0x7fffdf063448 /* 55 vars */) = -1 ENOENT (No such file or directory)
[pid 3018] execve("/usr/local/bin/printf", ["printf", "%s\\n", "hello"], 0x7fffdf063448 /* 55 vars */) = -1 ENOENT (No such file or directory)
[pid 3018] execve("/usr/sbin/printf", ["printf", "%s\\n", "hello"], 0x7fffdf063448 /* 55 vars */) = -1 ENOENT (No such file or directory)
[pid 3018] execve("/usr/bin/printf", ["printf", "%s\\n", "hello"], 0x7fffdf063448 /* 55 vars */) = 0
[pid 3018] +++ exited with 0 +++
[pid 3017] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=3018, si_uid=1000, si_status=0, si_utime=0, si_stime=1} ---
[pid 3017] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=3016, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
上述输出很清晰涉及到三个进程,pipe
,xargs
和printf
,每个进程退出时发送SIGCHLD
信号
使用strace
查看执行细节
上述输出日志中可以看到各种执行时参数,这对于调式某些shell expansion情况特别有用,比如参数是否expansion等。比如ls *.txt
和ls "*.txt"
。
前者的输出,可以看到*.txt
的使用pattern match进行了filename expansion
strace: Process 2763 attached
strace: Process 3055 attached
[pid 3055] execve("/usr/bin/ls", ["ls", "--color=auto", "plain.txt", "request.txt", "test.txt"], 0x5575977a96d0 /* 55 vars */) = 0
[pid 3055] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=3055, si_uid=1000, si_status=0, si_utime=0, si_stime=1} ---
后者输出,对比之下,发现double quote下的没有进行filename expansion,并且Bash报错,'*.txt': No such file or directory
strace: Process 2763 attached
strace: Process 3071 attached
[pid 3071] execve("/usr/bin/ls", ["ls", "--color=auto", "*.txt"], 0x5575977a96d0 /* 55 vars */) = 0
[pid 3071] +++ exited with 2 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=3071, si_uid=1000, si_status=2, si_utime=0, si_stime=0} ---
还可以通过给strace
添加-v
查看子进程继承的环境变量。当然上述也可以通过命令set -x
输出调试结果。
bash -c
执行情况
默认情况下会新建进程执行里面的命令,然后里面的命令如果是builtin命令在本进程执行,如果是外部命令则在子进程中执行,但是最后一个命令会在本进程执行。
strace: Process 2763 attached
strace: Process 3088 attached
[pid 3088] execve("/usr/bin/bash", ["bash", "-c", "echo hello; sleep .5; ls; hostna"...], 0x5575977a96d0 /* 55 vars */) = 0
strace: Process 3089 attached
[pid 3089] execve("/usr/bin/sleep", ["sleep", ".5"], 0x564ff0d5df60 /* 55 vars */) = 0
[pid 3089] +++ exited with 0 +++
[pid 3088] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=3089, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
strace: Process 3090 attached
[pid 3090] execve("/usr/bin/ls", ["ls"], 0x564ff0d5df60 /* 55 vars */) = 0
[pid 3090] +++ exited with 0 +++
[pid 3088] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=3090, si_uid=1000, si_status=0, si_utime=0, si_stime=1} ---
[pid 3088] execve("/usr/bin/hostname", ["hostname"], 0x564ff0d61c00 /* 55 vars */) = 0
[pid 3088] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=3088, si_uid=1000, si_status=0, si_utime=0, si_stime=2} ---
Bash不同版本细节差异
在bash -c
中执行的日志中发现,最后hostname命令没有新开子进程执行,而是直接在父进程3088
中执行,但是在Bash另外一个版本4.4.x
中是在子进程中执行的,可以猜测,新版本的Bash在bash -c
执行命令时,如果只有一个命令或者命令组中最后一个命令的情况会在当前进程中执行,不新建子进程。
bash --version
# GNU bash, version 4.4.20(1)-release (x86_64-pc-linux-gnu)
# ...
以下为输出日志,注意hostname
那行使用进程52098
执行的,此时父进程为51801
strace: Process 51801 attached
strace: Process 52095 attached
[pid 52095] execve("/bin/bash", ["bash", "-c", "sleep .5; ls; hostname"], 0x5654ba8e1490 /* 31 vars */) = 0
strace: Process 52096 attached
[pid 52096] execve("/bin/sleep", ["sleep", ".5"], 0x55910809ad80 /* 31 vars */) = 0
[pid 52096] +++ exited with 0 +++
[pid 52095] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=52096, si_uid=3001, si_status=0, si_utime=0, si_stime=0} ---
strace: Process 52097 attached
[pid 52097] execve("/bin/ls", ["ls"], 0x55910809ad80 /* 31 vars */) = 0
[pid 52097] +++ exited with 0 +++
[pid 52095] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=52097, si_uid=3001, si_status=0, si_utime=0, si_stime=0} ---
strace: Process 52098 attached
[pid 52098] execve("/bin/hostname", ["hostname"], 0x55910809ad80 /* 31 vars */) = 0
[pid 52098] +++ exited with 0 +++
[pid 52095] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=52098, si_uid=3001, si_status=0, si_utime=0, si_stime=0} ---
[pid 52095] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=52095, si_uid=3001, si_status=0, si_utime=0, si_stime=0} ---
调试trap
命令
trap "echo child exit" SIGCHLD
Posted on May 29, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.