我不需要等待他们,我只需要一个是或否。
我正在执行一个我知道会分叉的程序(孩子们也可能会分叉)。如果运行正确,它应该照顾所有的子级,并在完成时退出它们。我需要编写一个测试来检查这是否属实。孤立进程被分配给 init 进程,因此我无法再在 pstree 中看到它们。
如果我可以获得他们的 PID 列表,这样我就可以向他们发送 SIGKILL,那就加分了。理想情况下,如果它是 POSIX-y 的东西,那么它可以在 *BSD 上工作。
答案1
找到这些的一种方法是使用ps -ef
,查找parent-id 为“1”的行,例如,
#!/bin/sh
orphans="$(ps -ef | awk '$3 == 1{ print $2; }')"
echo "Processes which might be orphans: $orphans"
然而,许多进程以“1”作为其父进程。确定您感兴趣的最好方法是记住您的程序创建的子进程。
如果你碰巧知道登录名(和/或用户身份)在创建这些进程的情况下,您可以消除一些可能性。第一列ps
可以显示(取决于系统类型)登录名或相应的用户身份。POSIX此处提供了一些帮助,但很容易找到不同的系统 - 并且文档反映了这一点:
- 例如,FreeBSD 10 不显示登录名的
-f
选项。使用 justps -ef
,它会在第一列中显示进程 ID,而不显示父进程的进程 ID。它需要-l
选项来显示(而不是)用户身份。 - OSX 在任一情况下都会给出用户 ID(
ps -ef
或ps -efl
)。 - 如果有该
-l
选项,Solaris 10 将在第一列和第二列中显示进程标志。 POSIX 中提到了这一点,尽管内容标志的数量是未指定的(因为内容不同Unix平台)。 - Linux 按照 POSIX 提供登录名和进程标志。
正如您所看到的,对于可用系统的某些子集,ps -efl
前三列将给出“相同”的结果。对于更一般的内容,您必须查看标题(第一行)并确定哪一列包含与流程所有者相对应的信息(登录名或者用户身份) 和进程 ID 和它是父进程的进程 ID。
对于给定的系统(知道 的可用选项ps
,并知道是否匹配登录名或者用户身份),你可以用来awk
匹配那字段也是如此,例如
#!/bin/sh
orphans="$(ps -ef | awk -v user=$LOGNAME '($1 == user && $3 == 1){ print $2; }')"
echo "Processes which might be orphans: $orphans"
这里我使用$LOGNAME
, 来解释 POSIX 对这个术语的使用登录名,这可能会产生误导(因为原则上流程可以通过sudo
,而 POSIX 使用该术语意味着它们是通过“登录”来的)。
进一步阅读:
答案2
当进程终止时,其子进程的 PPID 被设置为 1(由 init 采用),但 PGID(进程组标识符)和 SID(会话标识符)不会更改。
该进程的子进程可能不会更改其进程组,除非它们打算成为守护进程。假设没有,则在其自己的进程组中启动要测试的进程。在分叉之后和调用执行正在测试的程序setpgid(getpid(), getpid())
之前,从测试框架调用。execve
调用kill(-test_program_pid, 0)
(kill
使用负参数pid
和信号值 0)来测试是否有具有 PGID 的正在运行的进程test_program_pid
。作为信号参数传递SIGKILL
来杀死它们。
test_program_pid = fork();
if (test_program_pid) {
waitpid(test_program_pid, &status, 0);
if (kill(-test_program_pid, 0)) {
record_failue("some child processes were not terminated properly");
}
kill(-test_program_pid, SIGKILL);
} else {
setpgid(getpid(), getpid());
execve("/program/to/test", …);
}
另一种方法是创建一个临时文件并在您正在测试的程序中打开它,而不是在其他地方打开它。如果程序调用execve
,请确保打开的文件描述符没有标志O_CLOEXEC
(或调用fcntl(fd, FD_CLOEXEC, 0)
)。此方法假设程序不会关闭未显式使用的文件描述符。然后,您可以运行fuser /temp/file
以列出打开该文件的进程,并fuser -k /temp/file
杀死它们。这种方法的一个变体甚至可以用于关闭不使用的文件描述符的程序,但假设程序不更改其当前目录,即创建一个临时目录并更改为该目录来运行程序。