守护进程(daemon)就是一直在后台运行的进程(daemon)。守护进程通常有以下几个特点:
后台运行。
没有控制终端,终端名设置为?号:也就意味着没有 stdin 0 、stdout 1、stderr 2。
父进程不是用户创建的进程,init进程或者systemd(pid=1)以及用户人为启动的用户层进程一般以pid=1的进程为父进程,而以kthreadd内核进程创建的守护进程以kthreadd为父进程。
守护进程一般是会话首进程、组长进程。
工作目录为 \ (根目录),主要是为了防止占用磁盘导致无法卸载磁盘。
创建守护进程:
要创建一个守护进程,一般进行如下步骤:
如果是单例守护进程,结合锁文件和kill函数检测是否有进程已经运行。
umask取消进程本身的文件掩码设置,也就是设置Linux文件权限,一般设置为000,这是为了防止子进程创建一个不能访问的文件(没有正确分配权限)。此过程并非必须,如果守护进程不会创建文件,也可以不修改。
fork出子进程,父进程退出。这样子进程一定不是组长进程(进程id不等于进程组id)。
子进程调用setsid新建会话(使子进程变为会话首进程、组长进程,并断开终端)。
如果是单例守护进程,将pid写入到记录锁文件,一般为/var/run/xxx.pid。
切换工作目录到根目录,这是为了防止占用磁盘造成磁盘不能卸载。所以也可以改到别的目录,只要保证目录所在磁盘不会中途卸载。
重定向输入输入错误文件句柄,将其指向/dev/null。
下面以PHP的 pcntl_*系列函数 实现方式为例来说明。如下:
pcntl_fork:在当前进程内创建一个子进程。成功时,在父进程执行线程内返回产生的子进程的PID,在子进程执行线程内返回0。 失败时,在 父进程上下文返回-1,不会创建子进程,并且会引发一个PHP错误。 posix_setuid:设置当前进程的操作用户 posix_setgid:设置当前进程的操作用户所属分组 getmypid:获取当前进程id posix_kill:向指定进程发送进程信号 pcntl_signal:安装一个信号处理器 system:执行外部程序,并且显示输出
1、启动守护进程、
2、停止守护进程、
停止守护进程,只需读取守护进程的pid文件,然后调用PHP函数posix_kill($pid, 9),最后将该文件删除,即可。如下:
3、重启守护进程
重启守护进程,就是给守护进程发送SIGHUP信号。发送SIGHUP信号,既可以通过Linux命令kil发送 kill -s SIGHUP 64881,也可以通过PHP库函数 posix_kill(posix_getpid(), SIGUSR1)实现。如下:
4、完整代码
根据守护进程的规则和特点通过代码来实现,守护进程最大的特点就是脱离了用户终端和会话,下面是实现的代码,关键地方进行了注释。
<?php $pid = pcntl_fork(); if ($pid == -1) { throw new Exception('fork子进程失败'); } elseif ($pid > 0) { //父进程退出,子进程变成孤儿进程被1号进程收养,进程脱离终端; //子进程不是进程组长,以便接下来顺利创建新会话 exit(0); } // 最重要的一步,创建一个新的会话,脱离原来的控制终端 if (posix_setsid() == -1) { die("could not detach from terminal"); } // 修改当前进程的工作目录,由于子进程会继承父进程的工作目录,修改工作目录以释放对父进程工作目录的占用。 chdir('/'); /* * 通过上一步,我们创建了一个新的会话组长,进程组长,且脱离了终端,但是会话组长可以申请重新打开一个终端,为了避免 * 这种情况,我们再次创建一个子进程,并退出当前进程,这样运行的进程就不再是会话组长。 */ $pid = pcntl_fork(); if ($pid == -1) { throw new Exception('fork子进程失败'); } elseif ($pid > 0) { // 再一次退出父进程,子进程成为最终的守护进程 exit(0); } // 由于守护进程用不到标准输入输出,关闭标准输入,输出,错误输出描述符 fclose(STDIN); fclose(STDOUT); fclose(STDERR); /* * 处理业务代码 */ while(TRUE) { file_put_contents('log.txt', time().PHP_EOL, FILE_APPEND); sleep(5); }
参考:
本文为崔凯原创文章,转载无需和我联系,但请注明来自冷暖自知一抹茶ckhttp://www.cksite.cn