一、进程信息
当你运行一个PHP脚本,那么就会创建一个进程。
<?php
sleep(300);
?>
运行 php demo1.php
查看进程ID
ps -ef | grep demo1
21952
查看进程的全部内容
ls /proc/21952
fd
目录保存了进程打开的文件描述符
status
文件存储了进程的状态信息
cat /proc/21952/status
可见每创建一个PHP进程,操作系统就要创建这么多文件,以及分配18M左右的内存,所以创建进程的开销还是比较大的。
二、父子进程
很好奇,为什么它有父进程呢,进程1622是啥?
因为我是用xshell连的虚拟机,每个窗口都是一个独立的连接,sshd 服务会为每一个连接创建一个单独的进程,用来处理用户的交互,进程名为 root@pts/xx
,xx
为编号(从0开始),代表root账户登录打开的第几个窗口,我这里打开了4个窗口,所以你可以看到 root@pts/0,root@pts/1,root@pts/2,root@pts/3
四个进程。然后ssh进程会启动bash进程,用来解析并执行命令,我发送的每个命令都是由bash进程根据需要
来forl / clone 出子进程(21952)来执行的,进程ID=1是操作系统初始化的进程,是所有进程的祖先 。
过程如下:
1、ID=1 --> sshd进程 = 997
2、客户端建立ssh连接后,sshd = 997 --> ssh进程 = 1614
3、ssh进程启动bash进程,ssh 进程 = 1614 --> bash进程 = 1622
3、bash解析并执行用户发送的命令(比如 php demo1.php),bash = 1622 --> php进程 = 21952
三、孤儿进程
那么,如果我关闭了这个终端会怎样?
修改代码
<?php
while(true){
sleep(1);
}
?>
启动后台运行:nohup php demo1.php > /dev/null 2>&1 &
关闭运行demo1.php
的终端后
可见,关闭终端窗口后,就意味着断开ssh连接,对应的ssh进程就会结束。如果程序正确设置了后台运行
,ssh进程先于PHP进程结束了,那么PHP进程就会成为孤儿进程。而孤儿进程会被ID=1的进程接管,成为它的子进程。
最后别忘了 kill -9 22225
不要被此处的例子误导了,正常情况下,断开ssd连接之后,该连接运行的前台进程都会收到SIGHUP
信号被终止。
四、僵尸进程
当一个父进程以fork / clone
系统调用建立一个子进程后,如果父进程晚于子进程结束,那么在子进程结束的时候会通过操作系统向父进程发送信号,父进程要等着这个信号并接收它,如果父进程没有这么做的话,那么这个子进程就变成僵尸进程。
僵尸子进程已经放弃了几乎所有的内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态信息供其他进程收集,除此之外,僵尸进程不再占有任何存储空间
。他需要他的父进程来为他收尸,如果他的父进程没有安装SIGCHLD信号处理函数调用wait 或 waitpid() 等待子进程结束,也没有显式忽略该信号,那么它就一直保持僵尸状态,如果这时候父进程结束了,那么init进程会自动接手这个子进程,为他收尸,他还是能被清除掉的。但是如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是系统中为什么有时候会有很多的僵尸进程。
演示
<?php
$pid = pcntl_fork();
if ($pid > 0) {
cli_set_process_title('php father demo1');
echo '子进程ID:', $pid, PHP_EOL;
sleep(600);
} else if (0 == $pid) {
cli_set_process_title('php child demo1');
sleep(10);
} else {
exit('fork error.' . PHP_EOL);
}
?>
运行 php demo1.php
子进程ID:22612
ps -ef | grep demo1
10秒之后
已经看不见子进程了,说好的僵尸进程去哪了?
直接使用 ps -aux 查看,找到了
root 22612 0.0 0.0 0 0 pts/3 Z+ 18:50 0:00 [php] <defunct>
原来,一个进程变成僵尸进程后,其进程名称会变成 defunct
,父进程ID变成0
查看父进程ID
[root@localhost proc]# ps -ef | grep 22612
root 22612 22611 0 18:50 pts/3 00:00:00 [php] <defunct>
综上所述,查看僵尸进程的方式
[root@localhost proc]# ps -ef | grep defunct
root 22756 22755 0 19:11 pts/3 00:00:00 [php] <defunct>
或者使用命令 ps -A -ostat,ppid,pid,cmd |grep -e '^[Zz]'
使用top命令也可以查看僵尸进程的数量,查看 zomble 的数量
如何杀死僵尸进程?
事实证明 kill -9 是没办法杀死的。
需要找到该defunct僵尸进程的父进程,将该进程的父进程杀掉,则此defunct进程将自动消失。
如何预防僵尸进程?
以上介绍的只是在发现了僵尸进程之后,如何去杀死它。那么,如何预防僵尸进程的产生呢?。
- 在父进程创建子进程之前,就向系统申明自己并不会对这个子进程的exit动作进行任何关注行为,这样的话,子进程一旦退出后,系统就不会去等待父进程的操作,而是直接将该子进程的资源回收掉,也就不会出现僵尸进程了。具体的办法就是,在父进程的初始化函数中,调用这个函数:signal(SIGCHLD,SIG_IGN)。
- 如果上述语句没来得及调用,也有另外一个办法。那就是在创建完子进程后,用waitpid等待子进程返回,也能达到上述效果,如果你需要等待子进程的执行结果,那么只能使用waitpid操作。
- 如果上述两个办法都不愿意采用,并且
子进程可能需要长期运行的时候
,可以使用此方法:在父进程创建子进程的时候,连续调用两次fork / clone,而且使紧跟的子进程直接退出(该子进程还是需要被回收),使其孙子进程成为孤儿进程,从而init进程将代替父进程来接手,负责清除这个孤儿进程。于是,父进程就无需进行任何的清理行为,系统会自动处理。