获取进程的子进程
1. 概述
作为进程管理的一部分,我们可能需要在 Linux 中获取进程的子进程。
在本教程中,我们将讨论实现此目标的几种方法。在我们的讨论中,我们将提到进程标识符 (PID)、线程标识符 (TID) 和*/proc*文件系统。
2. 设置测试场景
我们将设置一个场景作为我们教程的示例。在这种情况下,将同时存在父进程和子进程。
让我们从创建一个名为child.sh的 shell 脚本开始:
#!/bin/bash
sleep infinity
我们这个 shell 脚本的目标是拥有一个永远运行的进程,因为运行这个脚本产生的进程将在sleep infinity命令中等待。除了这个脚本之外,我们还有另一个名为parent.sh的 shell 脚本:
#!/bin/bash
./child.sh &
sleep infinity
此 shell 脚本首先将脚本child.sh作为后台进程启动。然后,它在自己的sleep infinity命令中永远等待。 现在,让我们运行脚本parent.sh:
$ parent.sh &
[1] 6245
通过运行脚本parent.sh生成的进程的进程 ID (PID) 为 6245。我们将在本教程的其余部分中尝试查找该进程的子进程。
3. 使用pgrep命令
查找子进程的一种简单方法是使用pgrep 命令。pgrep命令根据提供给它的搜索条件列出当前正在运行的进程。
这些搜索条件随几个命令选项一起提供,-P选项就是其中之一。-P选项需要一个 PID 。此 PID 是我们要查找其子进程的父进程的 PID。让我们使用pgrep命令检查 PID 为 6245 的进程的子进程:
$ pgrep -P 6245
6246
6247
如我们所见,我们为 PID 6245 的进程获得了两个 PID。我们可以使用*-l*选项列出进程名称:
$ pgrep -lP 6245
6246 child.sh
6247 sleep
从我们执行的命令的输出可以明显看出,** pgrep命令只是列出了父进程的直接子进程**。它没有列出子进程的子进程,即间接子进程。在我们的示例中,还有一个 PID 为 6246 的进程的直接子进程——该进程通过在脚本child.sh中执行sleep infinity命令生成。但是,我们在输出中看不到它,因为它不是 PID 6245 进程的直接子进程。
4. 使用pstree命令
pstree 命令也可用于显示父进程的子进程。它需要一个带有*-p选项的 PID。提供给-p选项的 PID 用作父进程的 PID。它将父进程的子进程显示为树。让我们使用pstree*命令检查 PID 为 6245 的进程的子进程:
$ pstree -p 6245
parent.sh(6245)---child.sh(6246)---sleep(6248)
|-sleep(6247)
** pstree命令不仅将直接子进程显示为树,还将间接子进程显示为树**。PID 为 6247 和 6248的sleep进程是通过分别在脚本parent.sh和child.sh中运行sleep infinity语句创建的进程。
我们也可以使用*–parent选项而不是-p* 选项。它们完全相同。
5. 使用ps命令
ps 命令列出当前正在运行的进程及其 PID。我们可以将*–ppid选项传递给ps命令以查找子进程。这个选项需要一个 PID,就像pgrep命令一样。此 PID 是我们要查找其子进程的父进程的 PID。因此,让我们使用ps*命令查找 PID 为 6245 的进程的子进程:
$ ps --ppid 6245
PID TTY TIME CMD
6246 pts/1 00:00:00 child.sh
6247 pts/1 00:00:00 sleep
我们可以格式化ps命令的输出并仅打印 PID、父 PID 和命令:
$ ps --ppid 6245 -o pid,ppid,cmd
PID PPID CMD
6246 6245 /bin/bash ./child.sh
6247 6245 sleep infinity
** ps命令的输出类似于pgrep命令的输出——它不列出间接子进程,而只列出直接子进程。**在我们的示例中,虽然有一个 PID 为 6246 的进程的子进程,但我们在输出中看不到它。这是脚本child.sh通过执行sleep infinity命令生成的进程。
6. 使用*/proc*文件系统
我们还可以使用/proc 目录找到父进程的子进程。实际上,这不是一个目录,而是一个由系统自动挂载在*/proc*的虚拟文件系统。它包含有关内核、系统和进程的信息。
我们可以在位于*/proc/[pid]/task/[tid]目录的子*文件中找到父进程的子进程的 PID 。让我们稍微解释一下目录结构。
让我们从路径的*[pid]部分开始。/proc*目录包含一个用于每个正在运行的进程的子目录。每个子目录的名字就是对应进程的PID。
在*/proc/[pid]/task目录下,每个进程的线程都有一个[tid]子目录。因此,我们将此子目录的名称称为[tid]*(线程 id)。主线程对应的子目录与进程的PID相同。
记住到目前为止的解释,让我们研究一下示例中的目录结构:
$ ls /proc/6245/task
6245
$ cat /proc/6245/task/6245/children
6246 6247
我们拥有通过运行parent.sh脚本获得的进程的目录*/proc/6245* 。只有一个目录,/proc/6245/task/6245,因为我们的进程不是多线程的。PID为6245的父进程的子进程的PID在*/proc/6245/task/6245/children*文件中,即6246和6247。
我们可以用同样的方式继续检查:
$ ls /proc/6246/task
6246
$ cat /proc/6246/task/6246/children
6248
$ ls /proc/6248/task
6248
$ cat /proc/6248/task/6248/children
$ ls /proc/6247/task
6247
$ cat /proc/6247/task/6247/children
从上述命令的执行输出可以看出,PID 为 6246 的进程只有一个子进程。它的 PID 是 6248。PID 为 6247 和 6248 的进程没有任何子进程。这些进程对应的子文件是空的。所以,我们得到的子进程和运行pstree命令得到的子进程是一样的。
我们可以使用 shell 脚本自动执行上述递归过程,即find_children.sh:
#!/bin/bash
# root_pid: The PID passed to the script
# That is the PID of the process whose child processes we want to find
root_pid=$1
# all_children: Array that shall store the child processes
declare -A all_children
all_children=()
# Recursive function to find the child processes
iterate_children()
{
local pid=$1
local tids=$(ls /proc/$pid/task)
# Iterate over all [tid]s in /proc/$pid/task directory
for tid in $tids
do
if [ -e /proc/$pid/task/$tid/children ]
then
# Get the child processes in /proc/$pid/task/$tid/children
local children=$(cat /proc/$pid/task/$tid/children)
# Iterate over all child processes
for p in $children
do
# Add the found child process to all_children array
all_children[${#all_children[@]}]=$p
# Find the child processes of process $p
iterate_children $p
done
fi
done
}
iterate_children $root_pid
echo "All child processes of process with PID $root_pid:"
echo ${all_children[*]}
当我们运行这个脚本时,我们得到相同的结果:
$ ./find_children 6245
All child processes of process with PID 6245:
6246 6248 6247