依旧讲解PHP进程的相关知识。

Q:什么是孤儿进程呢?
A:孤儿进程就是父进程作为老爹已经挂掉了,但是子进程还在跑着,这个时候呢,子进程就成为孤儿了。

在linux系统中,这个时候呢,就有进程pid为1的老祖宗收留这个孤儿进程,直到孤儿进程运行结束或者直接干掉TA。

下面,我们来演示下,孤儿进程是如何产生的:

<?php

$pid = pcntl_fork();
if ($pid === -1) {
    exit('无法创建子进程');
} elseif ($pid > 0) {
    // 父进程
    $pid = getmypid();
    echo "我是父进程:{$pid}\n";
    echo "老子退出咯\n";
} else {
    // 这里是子进程
    $pid = getmypid();
    echo "我是子进程:{$pid}\n";
    // 休眠20秒
    sleep(20);
    echo "我结束了\n";
}

上述代码中,在 fork 创建子进程之后,父进程直接over了。子进程执行了 sleep(20) 休眠20秒再退出。这样,就导致了孤儿进程的出现,我们用 pstree 命令看下:

pstree -p 17026
-+= 00001 root /sbin/launchd
 \--- 17026 xiaoteng php process4.php

可以看到子进程被1号进程收养了。

Q:那如何避免孤儿进程的出现呢?
A:父进程要负点责任,不能自己直接撒手不管,必须回收子进程。

我们来看下下面的代码:

<?php

$pid = pcntl_fork();
if ($pid === -1) {
    exit('无法创建子进程');
} elseif ($pid > 0) {
    // 父进程
    $pid = getmypid();
    echo "我是父进程:{$pid}\n";
    pcntl_wait($status);
    echo "儿子们都成功结束了,我也可以走了。";
} else {
    // 这里是子进程
    $pid = getmypid();
    echo "我是子进程:{$pid}\n";
    // 休眠20秒
    sleep(20);
    echo "我结束了\n";
}

我们对代码进行了一些修改。在父进程的代码逻辑里面,增加了 pcntl_wait() 这个函数,该函数有个参数 $status ,这是个引用传参。

该函数调用成功之后,$status 的值会变成子进程退出的状态(int类型)。有关该函数的使用可以到看这里 https://www.php.net/manual/zh/function.pcntl-wait.php

利用孤儿进程创建daemon进程

我们可以利用孤儿进程这个特性,创建一个常驻内存的守护进程。在 fork 出子进程之后,父进程直接over了,剩下来的子进程被1号接管,依旧在后台运行。我们上面的第一个程序差不多就是创建daemon进程的意思了。不过其中还有一点没有做,那就是未替换掉会话id。上一篇文章我们说过,fork 出来的子进程与父进程是共享的,也就是子进程用的会话id是父进程的。要想真正的独立成为daemon进程,还需要在子进程中替换掉继承来的父进程会话id,将自己升级为会话组长。具体可以见下面的代码:

<?php

$pid = pcntl_fork();
if ($pid === -1) {
    exit('无法创建子进程');
} elseif ($pid > 0) {
    // 父进程
    $pid = getmypid();
    echo "我是父进程:{$pid}\n";
    echo "父进程:拜拜咯\n";
} else {
    // 这里是子进程
    $pid = getmypid();
    echo "我是子进程:{$pid}\n";
    // 升级到会话组长
    $sid = posix_setsid();
    if ($sid < 0) {
        exit('无法升级到会话组长');
    }
    while (1) {
        file_put_contents('./output.md', date('Y-m-d H:i:s')."\n", FILE_APPEND);
        sleep(5);
    }
}

然后执行:

tail -f output.md
2020-06-14 02:17:37
2020-06-14 02:17:42
2020-06-14 02:17:47
2020-06-14 02:17:52
2020-06-14 02:17:57
2020-06-14 02:18:02

执行:

pstree -p 17307
-+= 00001 root /sbin/launchd
 \--= 17307 xiaoteng php process4.php

进程被老祖宗收养,稳定在后台运行。

关于daemon进程,网上有的文章说需要进行二次 fork 也就是在第一次从父进程 fork 之后,在子进程中再次进行一个 fork 操作,其实,这个并不是必须的。在 unix高级环境编程 这本书中,是这样解释二次 fork 的:

Under System V–based systems, some people recommend calling fork again at this point and having the parent terminate. The second child continues as the daemon. This guarantees that the daemon is not a session leader, which prevents it from acquiring a controlling terminal under the System V rules (Section 9.6). Alternatively, to avoid acquiring a controlling terminal, be sure to specify O_NOCTTY
whenever opening a terminal device.