久久久久久久视色,久久电影免费精品,中文亚洲欧美乱码在线观看,在线免费播放AV片

<center id="vfaef"><input id="vfaef"><table id="vfaef"></table></input></center>

    <p id="vfaef"><kbd id="vfaef"></kbd></p>

    
    
    <pre id="vfaef"><u id="vfaef"></u></pre>

      <thead id="vfaef"><input id="vfaef"></input></thead>

    1. 站長(zhǎng)資訊網(wǎng)
      最全最豐富的資訊網(wǎng)站

      什么是daemon?PHP中如何實(shí)現(xiàn)daemon?

      守護(hù)進(jìn)程(daemon)是一類在后臺(tái)運(yùn)行的特殊進(jìn)程,用于執(zhí)行特定的系統(tǒng)任務(wù)。本篇文章帶大家了解一下PHP中實(shí)現(xiàn)daemon的方法,介紹一下編程中需要注意的地方。

      什么是daemon?PHP中如何實(shí)現(xiàn)daemon?

      PHP實(shí)現(xiàn)守護(hù)進(jìn)程可以通過(guò) pcntlposix 擴(kuò)展實(shí)現(xiàn)。

      編程中需要注意的地方有:

      • 通過(guò)二次 pcntl_fork() 以及 posix_setsid 讓主進(jìn)程脫離終端
      • 通過(guò) pcntl_signal() 忽略或者處理 SIGHUP 信號(hào)
      • 多進(jìn)程程序需要通過(guò)二次 pcntl_fork() 或者 pcntl_signal() 忽略 SIGCHLD 信號(hào)防止子進(jìn)程變成 Zombie 進(jìn)程
      • 通過(guò) umask() 設(shè)定文件權(quán)限掩碼,防止繼承文件權(quán)限而來(lái)的權(quán)限影響功能
      • 將運(yùn)行進(jìn)程的 STDIN/STDOUT/STDERR 重定向到 /dev/null 或者其他流上

      如果要做的更好,還需要注意:

      • 如果通過(guò) root 啟動(dòng),運(yùn)行時(shí)更換到低權(quán)限用戶身份
      • 及時(shí) chdir() 防止操作錯(cuò)誤路徑
      • 多進(jìn)程程序考慮定時(shí)重啟,防止內(nèi)存泄露

      什么是daemon

      文章的主角守護(hù)進(jìn)程(daemon),Wikipedia 上的定義是:

      在一個(gè)多任務(wù)的電腦操作系統(tǒng)中,守護(hù)進(jìn)程(英語(yǔ):daemon,/?di?m?n/或/?de?m?n/)是一種在后臺(tái)執(zhí)行的電腦程序。此類程序會(huì)被以進(jìn)程的形式初始化。守護(hù)進(jìn)程程序的名稱通常以字母“d”結(jié)尾:例如,syslogd就是指管理系統(tǒng)日志的守護(hù)進(jìn)程。
      通常,守護(hù)進(jìn)程沒(méi)有任何存在的父進(jìn)程(即PPID=1),且在UNIX系統(tǒng)進(jìn)程層級(jí)中直接位于init之下。守護(hù)進(jìn)程程序通常通過(guò)如下方法使自己成為守護(hù)進(jìn)程:對(duì)一個(gè)子進(jìn)程運(yùn)行fork,然后使其父進(jìn)程立即終止,使得這個(gè)子進(jìn)程能在init下運(yùn)行。這種方法通常被稱為“脫殼”。

      UNIX環(huán)境高級(jí)編程(第二版)(以下使用簡(jiǎn)稱 APUE 指代) 13章有云:

      守護(hù)進(jìn)程也成精靈進(jìn)程( daemon )是生存周期較長(zhǎng)的一種進(jìn)程。它們常常在系統(tǒng)自舉時(shí)啟動(dòng),僅在系統(tǒng)關(guān)閉時(shí)才終止。因?yàn)樗麄儧](méi)有控制終端,所以說(shuō)他們是在后臺(tái)運(yùn)行的。

      這里注意到,daemon有如下特征:

      • 沒(méi)有終端
      • 后臺(tái)運(yùn)行
      • 父進(jìn)程 pid 為1

      想要查看運(yùn)行中的守護(hù)進(jìn)程可以通過(guò) ps -ax 或者 ps -ef 查看,其中 -x 表示會(huì)列出沒(méi)有控制終端的進(jìn)程。

      實(shí)現(xiàn)關(guān)注點(diǎn)

      二次 fork 與 setsid

      fork 系統(tǒng)調(diào)用

      fork 系統(tǒng)調(diào)用用于復(fù)制一個(gè)與父進(jìn)程幾乎完全相同的進(jìn)程,新生成的子進(jìn)程不同的地方在于與父進(jìn)程有著不同的 pid 以及有不同的內(nèi)存空間,根據(jù)代碼邏輯實(shí)現(xiàn),父子進(jìn)程可以完成一樣的工作,也可以不同。子進(jìn)程會(huì)從父進(jìn)程中繼承比如文件描述符一類的資源。

      PHP 中的 pcntl 擴(kuò)展中實(shí)現(xiàn)了 pcntl_fork() 函數(shù),用于在 PHP 中 fork 新的進(jìn)程。

      setsid 系統(tǒng)調(diào)用

      setsid 系統(tǒng)調(diào)用則用于創(chuàng)建一個(gè)新的會(huì)話并設(shè)定進(jìn)程組 id。

      這里有幾個(gè)概念:會(huì)話,進(jìn)程組

      在 Linux 中,用戶登錄產(chǎn)生一個(gè)會(huì)話(Session),一個(gè)會(huì)話中包含一個(gè)或者多個(gè)進(jìn)程組,一個(gè)進(jìn)程組又包含多個(gè)進(jìn)程。每個(gè)進(jìn)程組有一個(gè)組長(zhǎng)(Session Leader),它的 pid 就是進(jìn)程組的組 id。進(jìn)程組長(zhǎng)一旦打開(kāi)一個(gè)終端,這一個(gè)終端就被稱為控制終端。一旦控制終端發(fā)生異常(斷開(kāi)、硬件錯(cuò)誤等),會(huì)發(fā)出信號(hào)到進(jìn)程組組長(zhǎng)。

      后臺(tái)運(yùn)行程序(如 shell 中以&結(jié)尾執(zhí)行指令)在終端關(guān)閉之后也會(huì)被殺死,就是沒(méi)有處理好控制終端斷開(kāi)時(shí)發(fā)出的SIGHUP信號(hào),而SIGHUP信號(hào)對(duì)于進(jìn)程的默認(rèn)行為則是退出進(jìn)程。

      調(diào)用 setsid 系統(tǒng)調(diào)用之后,會(huì)讓當(dāng)前的進(jìn)程新建一個(gè)進(jìn)程組,如果在當(dāng)前進(jìn)程中不打開(kāi)終端的話,那么這一個(gè)進(jìn)程組就不會(huì)存在控制終端,也就不會(huì)出現(xiàn)因?yàn)殛P(guān)閉終端而殺死進(jìn)程的問(wèn)題。

      PHP 中的 posix 擴(kuò)展中實(shí)現(xiàn)了 posix_setsid() 函數(shù),用于在 PHP 中設(shè)定新的進(jìn)程組。

      孤兒進(jìn)程

      父進(jìn)程比子進(jìn)程先退出,子進(jìn)程就會(huì)變成孤兒進(jìn)程。

      init 進(jìn)程會(huì)收養(yǎng)孤兒進(jìn)程,即孤兒進(jìn)程的 ppid 變?yōu)?1。

      二次 fork 的作用

      首先,setsid 系統(tǒng)調(diào)用不能由進(jìn)程組組長(zhǎng)調(diào)用,會(huì)返回-1。

      二次 fork 操作的樣例代碼如下:

      $pid1 = pcntl_fork();  if ($pid1 > 0) {     exit(0); } else if ($pid1 < 0) {     exit("Failed to fork 1n"); }  if (-1 == posix_setsid()) {     exit("Failed to setsidn"); }  $pid2 = pcntl_fork();  if ($pid2 > 0) {     exit(0); } else if ($pid2 < 0) {     exit("Failed to fork 2n"); }

      假定我們?cè)诮K端中執(zhí)行應(yīng)用程序,進(jìn)程為 a,第一次 fork 會(huì)生成子進(jìn)程 b,如果 fork 成功,父進(jìn)程 a 退出。b 作為孤兒進(jìn)程,被 init 進(jìn)程托管。

      此時(shí),進(jìn)程 b 處于進(jìn)程組 a 中,進(jìn)程 b 調(diào)用 posix_setsid 要求生成新的進(jìn)程組,調(diào)用成功后當(dāng)前進(jìn)程組變?yōu)?b。

      此時(shí)進(jìn)程 b 事實(shí)上已經(jīng)脫離任何的控制終端,例程:

      <?php  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); }

      執(zhí)行程序之后:

      ?  ~ php56 2fork1.php ?  ~ ps ax | grep -v grep | grep -E 'process_|PID'   PID TTY      STAT   TIME COMMAND 28203 ?        Ss     0:00 process_b

      從 ps 的結(jié)果來(lái)看,process_b 的 TTY 已經(jīng)變成了 ,即沒(méi)有對(duì)應(yīng)的控制終端。

      代碼走到這里,似乎已經(jīng)完成了功能,關(guān)閉終端之后 process_b 也沒(méi)有被殺死,但是為什么還要進(jìn)行第二次 fork 操作呢?

      StackOverflow 上的一個(gè)回答寫(xiě)的很好:

      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.

      這是為了防止實(shí)際的工作的進(jìn)程主動(dòng)關(guān)聯(lián)或者意外關(guān)聯(lián)控制終端,再次 fork 之后生成的新進(jìn)程由于不是進(jìn)程組組長(zhǎng),是不能申請(qǐng)關(guān)聯(lián)控制終端的。

      綜上,二次 fork 與 setsid 的作用是生成新的進(jìn)程組,防止工作進(jìn)程關(guān)聯(lián)控制終端。

      SIGHUP 信號(hào)處理

      一個(gè)進(jìn)程收到 SIGHUP 信號(hào)的默認(rèn)動(dòng)作是結(jié)束進(jìn)程。

      SIGHUP 會(huì)在如下情況下發(fā)出:

      • 控制終端斷開(kāi),SIGHUP 發(fā)送到進(jìn)程組組長(zhǎng)
      • 進(jìn)程組組長(zhǎng)退出,SIGHUP 會(huì)發(fā)送到進(jìn)程組中的前臺(tái)進(jìn)程
      • SIGHUP 常被用于通知進(jìn)程重載配置文件(APUE 中提及,daemon 由于沒(méi)有控制終端,被認(rèn)為不可能會(huì)收到這一個(gè)信號(hào),所以選擇復(fù)用)

      由于實(shí)際的工作進(jìn)程不在前臺(tái)進(jìn)程組中,而且進(jìn)程組的組長(zhǎng)已經(jīng)退出并且沒(méi)有控制終端,不處理正常情況下當(dāng)然也沒(méi)有問(wèn)題,然而為了防止偶然的收到 SIGHUP 導(dǎo)致進(jìn)程退出,也為了遵循守護(hù)進(jìn)程程序設(shè)計(jì)的慣例,還是應(yīng)當(dāng)處理這一信號(hào)。

      Zombie 進(jìn)程處理

      何為 Zombie 進(jìn)程

      簡(jiǎn)單來(lái)說(shuō),子進(jìn)程先于父進(jìn)程退出,父進(jìn)程沒(méi)有調(diào)用 wait 系統(tǒng)調(diào)用處理,進(jìn)程變?yōu)?Zombie 進(jìn)程。

      子進(jìn)程先于父進(jìn)程退出時(shí),會(huì)向父進(jìn)程發(fā)送 SIGCHLD 信號(hào),如果父進(jìn)程沒(méi)有處理,子進(jìn)程也會(huì)變?yōu)?Zombie 進(jìn)程。

      Zombie 進(jìn)程會(huì)占用可 fork 的進(jìn)程數(shù),Zombie 進(jìn)程過(guò)多會(huì)導(dǎo)致無(wú)法 fork 新的進(jìn)程。

      此外,Linux 系統(tǒng)中 ppid 為 init 進(jìn)程的進(jìn)程,變?yōu)?Zombie 后會(huì)由 init 進(jìn)程回收管理。

      Zombie 進(jìn)程的處理

      從 Zombie 進(jìn)程的特點(diǎn),對(duì)于多進(jìn)程的daemon,可以通過(guò)兩個(gè)途徑解決這一問(wèn)題:

      • 父進(jìn)程處理 SIGCHLD 信號(hào)
      • 讓子進(jìn)程被 init 接管

      父進(jìn)程處理信號(hào)無(wú)需多說(shuō),注冊(cè)信號(hào)處理回調(diào)函數(shù),調(diào)用回收方法即可。

      對(duì)于讓子進(jìn)程被 init 接管,則可以通過(guò)2次 fork 的方法,讓第一次 fork 出的子進(jìn)程 a 再 fork 出實(shí)際的工作進(jìn)程 b,讓 a 先行退出,使得 b 成為孤兒進(jìn)程,這樣就能被 init 進(jìn)程托管了。

      umask

      umask 會(huì)從父進(jìn)程中繼承,影響創(chuàng)建文件的權(quán)限。

      PHP 手冊(cè)上提到:

      umask() 將 PHP 的 umask 設(shè)定為 mask & 0777 并返回原來(lái)的 umask。當(dāng) PHP 被作為服務(wù)器模塊使用時(shí),在每個(gè)請(qǐng)求結(jié)束后 umask 會(huì)被恢復(fù)。

      如果父進(jìn)程的 umask 沒(méi)有設(shè)定好,那么在執(zhí)行一些文件操作時(shí),會(huì)出現(xiàn)意想不到的效果:

      ?  ~ cat test_umask.php <?php         chdir('/tmp');         umask(0066);         mkdir('test_umask', 0777); ?  ~ php test_umask.php ?  ~ ll /tmp | grep umask drwx--x--x 2 root root 4.0K 8月  22 17:35 test_umask

      所以,為了保證每一次都能按照預(yù)期的權(quán)限操作文件,需要置0 umask 值。

      重定向0/1/2

      這里的0/1/2分別指的是 STDIN/STDOUT/STDERR,即標(biāo)準(zhǔn)輸入/輸出/錯(cuò)誤三個(gè)流。

      樣例

      首先來(lái)看一個(gè)樣例:

      <?php  // not_redirect_std_stream_daemon.php  $pid1 = pcntl_fork();  if ($pid1 > 0) {     exit(0); } else if ($pid1 < 0) {     exit("Failed to fork 1n"); }  if (-1 == posix_setsid()) {     exit("Failed to setsidn"); }  $pid2 = pcntl_fork();  if ($pid2 > 0) {     exit(0); } else if ($pid2 < 0) {     exit("Failed to fork 2n"); }  umask(0); declare(ticks = 1); pcntl_signal(SIGHUP, SIG_IGN);  echo getmypid() . "n";  while(true) {     echo time() . "n";     sleep(10); }

      上述代碼幾乎完成了文章最開(kāi)始部分提及的各個(gè)方面,唯一不同的是沒(méi)有對(duì)標(biāo)準(zhǔn)流做處理。通過(guò) php not_redirect_std_stream_daemon.php 指令也能讓程序在后臺(tái)進(jìn)行。

      sleep 的間隙,關(guān)閉終端,會(huì)發(fā)現(xiàn)進(jìn)程退出。

      通過(guò) strace 觀察系統(tǒng)調(diào)用的情況:

      ?  ~ strace -p 6723 Process 6723 attached - interrupt to quit restart_syscall(<... resuming interrupted call ...>) = 0 write(1, "1503417004n", 11)            = 11 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 nanosleep({10, 0}, 0x7fff71a30ec0)      = 0 write(1, "1503417014n", 11)            = -1 EIO (Input/output error) close(2)                                = 0 close(1)                                = 0 munmap(0x7f35abf59000, 4096)            = 0 close(0)                                = 0

      發(fā)現(xiàn)發(fā)生了 EIO 錯(cuò)誤,導(dǎo)致進(jìn)程退出。

      原因很簡(jiǎn)單,即我們編寫(xiě)的 daemon 程序使用了當(dāng)時(shí)啟動(dòng)時(shí)終端提供的標(biāo)準(zhǔn)流,當(dāng)終端關(guān)閉時(shí),標(biāo)準(zhǔn)流變得不可讀不可寫(xiě),一旦嘗試讀寫(xiě),會(huì)導(dǎo)致進(jìn)程退出。

      在信海龍的博文《一個(gè)echo引起的進(jìn)程崩潰》中也提到過(guò)類似的問(wèn)題。

      解決方案

      APUE 樣例

      APUE 13.3中提到過(guò)一條編程規(guī)則(第6條):

      某些守護(hù)進(jìn)程打開(kāi) /dev/null 時(shí)期具有文件描述符0、1和2,這樣,任何一個(gè)視圖讀標(biāo)準(zhǔn)輸入、寫(xiě)標(biāo)準(zhǔn)輸出或者標(biāo)準(zhǔn)錯(cuò)誤的庫(kù)例程都不會(huì)產(chǎn)生任何效果。因?yàn)槭刈o(hù)進(jìn)程并不與終端設(shè)備相關(guān)聯(lián),所以不能在終端設(shè)備上顯示器輸出,也無(wú)從從交互式用戶那里接受輸入。及時(shí)守護(hù)進(jìn)程是從交互式會(huì)話啟動(dòng)的,但因?yàn)槭刈o(hù)進(jìn)程是在后臺(tái)運(yùn)行的,所以登錄會(huì)話的終止并不影響守護(hù)進(jìn)程。如果其他用戶在同一終端設(shè)備上登錄,我們也不會(huì)在該終端上見(jiàn)到守護(hù)進(jìn)程的輸出,用戶也不可期望他們?cè)诮K端上的輸入會(huì)由守護(hù)進(jìn)程讀取。

      簡(jiǎn)單來(lái)說(shuō):

      • daemon 不應(yīng)使用標(biāo)準(zhǔn)流
      • 0/1/2 要設(shè)定成 /dev/null

      例程中使用:

      for (i = 0; i < rl.rlim_max; i++) 	close(i);  fd0 = open("/dev/null", O_RDWR); fd1 = dup(0); fd2 = dup(0);

      實(shí)現(xiàn)了這一個(gè)功能。dup() (參考手冊(cè))系統(tǒng)調(diào)用會(huì)復(fù)制輸入?yún)?shù)中的文件描述符,并復(fù)制到最小的未分配文件描述符上。所以上述例程可以理解為:

      關(guān)閉所有可以打開(kāi)的文件描述符,包括標(biāo)準(zhǔn)輸入輸出錯(cuò)誤; 打開(kāi)/dev/null并賦值給變量fd0,因?yàn)闃?biāo)準(zhǔn)輸入已經(jīng)關(guān)閉了,所以/dev/null會(huì)綁定到0,即標(biāo)準(zhǔn)輸入; 因?yàn)樽钚∥捶峙湮募枋龇麨?,復(fù)制文件描述符0到文件描述符1,即標(biāo)準(zhǔn)輸出也綁定到/dev/null; 因?yàn)樽钚∥捶峙湮募枋龇麨?,復(fù)制文件描述符0到文件描述符2,即標(biāo)準(zhǔn)錯(cuò)誤也綁定到/dev/null;復(fù)制代碼

      開(kāi)源項(xiàng)目實(shí)現(xiàn):Workerman

      Workerman 中的 Worker.php 中的 resetStd() 方法實(shí)現(xiàn)了類似的操作。

      /** * Redirect standard input and output. * * @throws Exception */ public static function resetStd() {    if (!self::$daemonize) {        return;    }    global $STDOUT, $STDERR;    $handle = fopen(self::$stdoutFile, "a");    if ($handle) {        unset($handle);        @fclose(STDOUT);        @fclose(STDERR);        $STDOUT = fopen(self::$stdoutFile, "a");        $STDERR = fopen(self::$stdoutFile, "a");    } else {        throw new Exception('can not open stdoutFile ' . self::$stdoutFile);    } }

      Workerman 中如此實(shí)現(xiàn),結(jié)合博文,可能與 PHP 的 GC 機(jī)制有關(guān),對(duì)于 fd 0 1 2來(lái)說(shuō),PHP 會(huì)維持對(duì)這三個(gè)資源的引用計(jì)數(shù),在直接 fclose 之后,會(huì)使得這幾個(gè) fd 對(duì)應(yīng)的資源類型的變量引用計(jì)數(shù)為0,導(dǎo)致觸發(fā)回收。所需要做的就是將這些變量變?yōu)槿肿兞?,保證引用的存在。

      推薦學(xué)習(xí):《PHP視頻教程》

      贊(0)
      分享到: 更多 (0)
      網(wǎng)站地圖   滬ICP備18035694號(hào)-2    滬公網(wǎng)安備31011702889846號(hào)