进程组,就是一个或是多个进程的集合,每一个进程都有个标识「组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 结束了