进程组,就是一个或是多个进程的集合,每一个进程都有个标识「组ID(PGID)」,表示该进程属于哪个进程组。
bash 进程启动之后,它会自己 setsid 把自己设置为会话首进程,也会设置自己为组长进程。
进程:正在执行的程序,这个程序是在 bin/bash
进程里启动的。
进程启动之后(通过 execve 函数启动),它会继承一些属性比如说组ID,会话ID,同时也会继承父进程已经打开的文件描述符(伪终端里的):0/标准输入,1/标准输出,2/标准错误,通过 pts、ptmx 模拟出来的。
demo17.php
$pid = posix_getpid();
fprintf(STDOUT, "pid=%d,ppid=%d,pgid=%d,sid=%d\n",$pid,posix_getppid(),posix_getpgid($pid),posix_getsid($pid));
查看当前 bash 进程 PID
$ echo $$
1031235
另外一个 SSH 连接,追踪上一个 bash 进程
$ strace -f -s 65500 -o demo17.log -p 1031235
在 bash 进程 1031235 中,执行代码
$ php demo17.php
pid=1032568,ppid=1031235,pgid=1032568,sid=1031235
demo17.log
# bash 进程 clone 一个子进程 1032568
1031235 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f2c4847ba10) = 1032568
1032568 getpid() = 1032568
...
# 子进程将自己设置为组长进程
1031235 setpgid(1032568, 1032568) = 0
...
# 子进程执行 `demo17.php`
1032568 execve("/usr/bin/php", ["php", "-c", "/www/server/php/80/etc/php-cli.ini", "demo17.php"], 0x5558eda12830 /* 32 vars */) = 0
孤儿进程:指父进程先结束,但是子进程晚结束,这个时候子进程就是孤儿进程,它会被1号进程接管。
子进程继承父进程的组id(gpid)、会话id(sid)
demo18.php
<?php
function showPid()
{
$pid = posix_getpid();
fprintf(STDOUT, "pid=%d,ppid=%d,pgid=%d,sid=%d\n",$pid,posix_getppid(),posix_getpgid($pid),posix_getsid($pid));
}
showPid(); // ppid 和下边两个子进程是不一样的
$pid = pcntl_fork(); // 该子进程的 ppid、pgid、sid是一样的
$pidMap = [];
if ($pid > 0) {
$pidMap[$pid] = $pid;
$pid = pcntl_fork(); // 该子进程的 ppid、pgid、sid是一样的
if ($pid > 0) {
$pidMap[$pid] = $pid;
}
}
showPid();
if ($pid > 0) {
$i = 0;
while (1) {
$pid = pcntl_waitpid(-1, $status);
if ($pid > 0) {
echo "子进程 $pid 结束了\n";
$i++;
}
unset($pidMap[$pid]);
if (empty($pidMap)) {
break;
}
}
}
运行结果:
$ php demo18.php
pid=678,ppid=28,pgid=678,sid=28
pid=679,ppid=678,pgid=678,sid=28
pid=678,ppid=28,pgid=678,sid=28
pid=680,ppid=678,pgid=678,sid=28
子进程 679 结束了
子进程 680 结束了
设置子进程组id
<?php
function showPid()
{
$pid = posix_getpid();
fprintf(STDOUT, "pid=%d,ppid=%d,pgid=%d,sid=%d\n",$pid,posix_getppid(),posix_getpgid($pid),posix_getsid($pid));
}
showPid(); // ppid 和下边两个子进程是不一样的
$pid = pcntl_fork(); // 该子进程的 ppid、pgid、sid是一样的
$pidMap = [];
if ($pid > 0) {
$pidMap[$pid] = $pid;
$pid = pcntl_fork(); // 该子进程的 ppid、pgid、sid是一样的
if ($pid > 0) {
$pidMap[$pid] = $pid;
} else {
// 设置子进程组id
$pid = posix_getpid();
posix_setpgid($pid, $pid);
// fork 一个子进程、观察组id变化
$pid = pcntl_fork();
if ($pid > 0) {
$pidMap[$pid] = $pid;
}
}
}
showPid();
if ($pid > 0) {
$i = 0;
while (1) {
$pid = pcntl_waitpid(-1, $status);
if ($pid > 0) {
echo "子进程 $pid 结束了\n";
$i++;
}
unset($pidMap[$pid]);
if (empty($pidMap)) {
break;
}
}
}
$ php demo18.php
pid=463,ppid=28,pgid=463,sid=28
pid=464,ppid=463,pgid=463,sid=28
pid=463,ppid=28,pgid=463,sid=28
pid=465,ppid=463,pgid=465,sid=28
pid=466,ppid=465,pgid=465,sid=28
子进程 466 结束了
子进程 464 结束了