在一个多使命的计算机操纵体系中,保卫历程是一种在背景实行的计算机顺序。此类顺序会被以历程的情势初始化。保卫历程顺序的称号一般以字母“d”末端:比方,syslogd就是指治理体系日记的保卫历程。
daemon 顺序是一向运转的效劳端顺序,又称为保卫历程。一般在体系背景运转,没有掌握终端不与前台交互,daemon 顺序平常作为体系效劳运用。daemon 是长时间运转的历程,一般在体系启动后就运转,在体系封闭时才完毕。平常说Daemon顺序在背景运转,是因为它没有掌握终端,没法和前台的用户交互。daemon顺序平常都作为效劳顺序运用,守候客户端顺序与它通讯。我们也把运转的daemon顺序称作保卫历程。
一般,保卫历程没有任何存在的父历程(即PPID=1),且在UNIX体系历程层级中直接位于init之下。保卫历程顺序一般经由过程以下要领使本身成为保卫历程:对一个子历程运转fork,然后使其父历程马上停止,使得这个子历程能在init下运转。这类要领一般被称为“脱壳”。
体系一般在启动时一同起动保卫历程。保卫历程为对收集请求,硬件运动等举行相应,或其他经由过程某些使命对其他应用顺序的请求举行回应供应支撑。保卫历程也可以对硬件举行设置(如在某些Linux体系上的devfsd),运转计划使命(比方cron),以及运转其他使命。每一个历程都有一个父历程,子历程退出,父历程能获得子历程退出的状况。
保卫历程简朴地说就是可以离开终端而在背景运转的历程 . 这在Linux中黑白经常见的一种历程 , 比方apache或许mysql等效劳启动后 , 就会以保卫历程的体式格局进驻在内存中 。保卫顺序是在背景运转的应用顺序,而不是由用户直接操纵。保卫历程的例子是Cron和MySQL。 运用PHP保卫历程非常简朴,而且须要运用PHP 4.1或更高版本编译参数:--enable-pcntl
假如有个耗时间的使命须要跑在背景 : 将一切mysql中user表中的2000万用户悉数导入到redis中做预热缓存 , 那末这个使命预计一时半会是不会完毕的 , 这个时刻就须要编写一个php剧本以daemon情势运转在体系中 , 完毕后自动推出。
在Linux中 , 有三种体式格局完成剧本背景化 :
1 . 在敕令后增加一个&标记
比方 php task.php & . 这个要领的瑕玷在于 假如terminal终端封闭 , 无论是一般封闭还黑白一般封闭 , 这个php历程都邑跟着终端封闭而封闭 , 其次是代码中假如有echo或许print_r之类的输出文本 , 会被输出到当前的终端窗口中 。
2 . 运用nohup敕令
比方 nohup php task.php & . 默许情况下 , 代码中echo或许print_r之类输出的文本会被输出到php代码同级目次的nohup.out文件中 . 假如你用exit敕令或许封闭按钮等一般手腕封闭终端 , 该历程不会被封闭 , 依旧会在背景延续运转 . 然则假如终端碰到非常退出或许停止 , 该php历程也会随即退出 . 本质上 , 也并不是稳固牢靠的daemon计划 。
3 . 经由过程 pcntl
与 posix
扩大完成
编程中须要注重的处所有:
- 经由过程二次
pcntl_fork()
以及posix_setsid
让主历程离开终端 - 经由过程
pcntl_signal()
疏忽或许处置惩罚SIGHUP
信号 - 多历程顺序须要经由过程二次
pcntl_fork()
或许pcntl_signal()
疏忽SIGCHLD
信号防备子历程变成 Zombie 历程 - 经由过程
umask()
设定文件权限掩码,防备继续文件权限而来的权限影响功用 - 将运转历程的
STDIN/STDOUT/STDERR
重定向到/dev/null
或许其他流上
daemon有以下特性:
- 没有终端
- 背景运转
- 父历程 pid 为1
想要检察运转中的保卫历程可以经由过程 ps -ax
或许 ps -ef
检察,个中 -x
示意会列出没有掌握终端的历程。
fork 体系挪用
fork 体系挪用用于复制一个与父历程险些完全相同的历程,新生成的子历程差别的处所在于与父历程有着差别的 pid 以及有差别的内存空间,依据代码逻辑完成,父子历程可以完成一样的事情,也可以差别。子历程会从父历程中继续比方文件描述符一类的资本。
PHP 中的 pcntl
扩大中完成了 pcntl_fork()
函数,用于在 PHP 中 fork 新的历程。
setsid 体系挪用
setsid 体系挪用则用于建立一个新的会话并设定历程组 id。这里有几个观点:会话
,历程组
。
在 Linux 中,用户登录发作一个会话(Session),一个会话中包括一个或许多个历程组,一个历程组又包括多个历程。每一个历程组有一个组长(Session Leader),它的 pid 就是历程组的组 id。历程组长一旦翻开一个终端,这一个终端就被称为掌握终端。一旦掌握终端发作非常(断开、硬件毛病等),会发出信号到历程组组长。
背景运转顺序(如 shell 中以&
末端实行指令)在终端封闭以后也会被杀死,就是没有处置惩罚好掌握终端断开时发出的SIGHUP
信号,而SIGHUP
信号关于历程的默许行动则是退出历程。
挪用 setsid 体系挪用以后,会让当前的历程新建一个历程组,假如在当前历程中不翻开终端的话,那末这一个历程组就不会存在掌握终端,也就不会涌现因为封闭终端而杀死历程的题目。
PHP 中的 posix
扩大中完成了 posix_setsid()
函数,用于在 PHP 中设定新的历程组。
二次 fork 的作用
起首,setsid
体系挪用不能由历程组组长挪用,会返回-1。
二次 fork 操纵的样例代码以下:
$pid1 = pcntl_fork(); if ($pid1 > 0) {
// 父历程会获得子历程号,所以这里是父历程实行的逻辑 exit('parent process. 1'."\n"); } else if ($pid1 < 0) { exit("Failed to fork 1\n"); } if (-1 == posix_setsid()) { exit("Failed to setsid\n"); } $pid2 = pcntl_fork(); if ($pid2 > 0) { exit('parent process. 2'."\n"); } else if ($pid2 < 0) { exit("Failed to fork 2\n"); }
pcntl_fork() 函数建立一个子历程,这个子历程仅PID(历程号) 和PPID(父历程号)与其父历程差别。
返回值
胜利时,在父历程实行线程内返回发作的子历程的PID,在子历程实行线程内返回 0,失利时,在 父历程上下文返回 -1,不会建立子历程,而且会激发一个PHP毛病。
假定我们在终端中实行应用顺序,历程为 a,第一次 fork 会生成子历程 b,假如 fork 胜利,父历程 a 退出。b 作为孤儿历程,被 init 历程托管。
此时,历程 b 处于历程组 a 中,历程 b 挪用 posix_setsid
请求生成新的历程组,挪用胜利后当前历程组变成 b。
php fork2.php parent process. 1 parent process. 2
此时历程 b 事实上已离开任何的掌握终端,例程:
cli_set_process_title('process_a'); $pidA = pcntl_fork(); if ($pidA > 0) { exit(0); } else if ($pidA < 0) { exit(1); } cli_set_process_title('process_b'); if (-1 === posix_setsid()) { exit(2); } while(true) { sleep(1); }
实行顺序以后:
$ php cli-title.php $ ps ax | grep -v grep | grep -E 'process_|PID' PID TTY STAT TIME COMMAND 15725 ? Ss 0:00 process_b
从新翻开一个shell窗口,效果一样,都在呢
从 ps 的效果来看,process_b 的 TTY 已变成了 ?
,即没有对应的掌握终端。
代码走到这里,好像已完成了功用,封闭终端以后 process_b 也没有被杀死,然则为何还要举行第二次 fork 操纵呢?
StackOverflow 上的一个回覆写的很好:
The second fork(2) is there to ensure that the new process is not a session leader, so it won’t be able to (accidentally) allocate a controlling terminal, since daemons are not supposed to ever have a controlling terminal.
这是为了防备现实的事情的历程主动关联或许不测关联掌握终端,再次 fork 以后生成的新历程因为不是历程组组长,是不能请求关联掌握终端的。
综上,二次 fork 与 setsid 的作用是生成新的历程组,防备事情历程关联掌握终端。
写一个demo测试下
<?php // 第一次fork体系挪用 $pid_A = pcntl_fork(); // 父历程 和 子历程 都邑实行下面代码 if ($pid_A < 0) { // 毛病处置惩罚: 建立子历程失利时返回-1. exit('A fork error '); } else if ($pid_A > 0) { // 父历程会获得子历程号,所以这里是父历程实行的逻辑 exit("A parent process exit \n"); } // B 作为孤儿历程,被 init 历程托管,此时,历程 B 处于历程组 A 中 // 子历程获得的$pid为0, 所以以下是子历程实行的逻辑,受掌握终端的影响,掌握终端封闭则这里也会退出 // [子历程] 掌握终端未封闭前,将当前子历程提拔会会话组组长,及历程组的leader // 历程 B 挪用 posix_setsid 请求生成新的历程组,挪用胜利后当前历程组变成 B if (-1 == posix_setsid()) { exit("Failed to setsid\n"); } // 此时历程 B 已离开任何的掌握终端 // [子历程] 这时刻在【历程组B】中,从新fork体系挪用(二次fork) $pid_B = pcntl_fork(); if ($pid_B < 0) { exit('B fork error '); } else if ($pid_B > 0) { exit("B parent process exit \n"); } // [新子历程] 这里是新生成的历程组,不受掌握终端的影响,写写本身的营业逻辑代码 for ($i = 1; $i <= 100; $i++) { sleep(1); file_put_contents('daemon.log',$i . "--" . date("Y-m-d H:i:s", time()) . "\n",FILE_APPEND); }
Window 下跑回直接抛出非常
php runtime\daemon.php PHP Fatal error: Uncaught Error: Call to undefined function pcntl_fork() in D:\phpStudy\PHPTutorial\WWW\notes\runtime\daemon.php:13 Stack trace: #0 {main} thrown in D:\phpStudy\PHPTutorial\WWW\notes\runtime\daemon.php on line 13
Linux 下实行,输出效果
php daemon.php
... 97--2018-09-07 03:50:09 98--2018-09-07 03:50:10 99--2018-09-07 03:50:11 100--2018-09-07 03:50:12
所以,如今纵然封闭了终端,改剧本任然在背景保卫历程运转
相干教程:PHP视频教程
以上就是PHP7完成daemon保卫历程详解的细致内容,更多请关注ki4网别的相干文章!