什么是守護進程?
一個守護進程通常補認為是一個不對終端進行控制的后台任務。它有三個很顯著的特征:在后台運行,與啟動他的進程脫離,無須控制終端。常用的實現方式是fork() -> setsid() -> fork()
在glibc里有一個函數daemon。調用此函數,就可使當前進程脫離終端變成一個守護進程,具體內容參見man daemon。PHP中暫時沒有此函數,PHP程序實現守護進程化有2種方法:
1.使用系統命令nohup
nohup php myprog.php > log.txt &
&
,這樣執行程序雖然也是轉為后台運行,但實際上是依賴終端的,當用戶退出終端時進程就會被殺掉。需要使用nohup來實現
2.使用supervisor工具 (推薦此方案)
http://www.cnblogs.com/loveyouyou616/p/7028257.html
3.當然也可以用程序實現(不建議生產環境使用)
C程序實現:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <sys/types.h> 5 #include <sys/stat.h> 6 #include <fcntl.h> 7 8 //實現守護進程步驟 9 void crete_daemon(void) 10 { 11 pid_t pid = 0; 12 pid = fork(); 13 if (pid<0) 14 { 15 perror("fork"); 16 exit(-1); 17 } 18 19 if (pid > 0) 20 { 21 //1.父進程直接退出 22 exit(0); 23 } 24 25 //2. 26 //執行到這里就是子進程 27 //setsid 將當前進程設置為一個新的會話期session,目的就是 28 //讓當前進程脫離控制台,成為守護進程。 29 pid = setsid(); 30 if (pid < 0) 31 { 32 perror("setsid"); 33 exit(-1); 34 } 35 36 //3.設置當前進程的工作目錄為根目錄,不依賴於其他 37 chdir("/"); 38 39 //4.umask設置為0確保將來進程有最大的文件操作權限 40 umask(0); 41 42 //5.關閉文件描述符 43 //先要獲取當前系統中所允許打開的最大文件描述符數目 44 int i = 0; 45 int cnt = sysconf(_SC_OPEN_MAX); 46 for (i=0;i<cnt;i++) 47 { 48 close(i); 49 } 50 51 //將0,1,2定位到 /dev/null 52 open("/dev/null",O_RDWR); 53 open("/dev/null",O_RDWR); 54 open("/dev/null",O_RDWR); 55 56 } 57 58 int main(void) 59 { 60 61 crete_daemon(); 62 63 while(1) 64 { 65 printf("I am running.\n"); 66 sleep(1); 67 } 68 69 return 0; 70 }
PHP腳本函數實現:
/* *根據c語言的實現思路即可。 * 因為需要關閉 標准io,所以這里使用redis方便測試。 */ <?php //php代碼實現守護進程 function daemon(){ $pid = pcntl_fork(); if($pid < 0){ die("fork(1) failed!\n"); }elseif($pid > 0){ //1.父進程直接退出 exit; } //執行到這里就是子進程 //2.建立一個有別於終端的新session以脫離終端 $sid = posix_setsid(); if (!$sid) { die("setsid failed!\n"); } //這一部不是必須的 $pid = pcntl_fork(); if($pid < 0){ die("fork(1) failed!\n"); }elseif($pid > 0){ exit; //父進程退出, 剩下子進程成為最終的獨立進程 } //3.設置當前進程的工作目錄為根目錄,不依賴於其他 chdir("/"); //4.umask設置為0確保將來進程有最大的文件操作權限 umask(0); //5.關閉標准I/O流 if (defined('STDIN')) fclose(STDIN); if (defined('STDOUT')) fclose(STDOUT); if (defined('STDERR')) fclose(STDERR); } daemon(); $redis = new Redis(); $redis->connect('127.0.0.1', 6379); while (true) { //echo 1; 不要任何輸出echo 因為標准輸入流關閉了,會異常導致進程終止 $redis->set("name", "lemon".mt_rand()); sleep(3); }
測試結果:
守護進程:
這里較為關鍵的二個php函數是pcntl_fork()和posix_setsid()
- fork()一個進程,則表示創建了一個運行進程的副本,副本被認為是子進程,而原始進程被認為是父進程。當fork()運行之后,則可以脫離啟動他的進程與終端控制等,也意味着父進程可以自由退出。
- setsid(),它首先使新進程成為一個新會話的“領導者”,最后使該進程不再控制終端,這也是成為守護進程最關鍵的一步,這意味着,不會隨着終端關閉而強制退出進程。對於一個不會被中斷的常駐進程來說,這是很關鍵的一步。
- 進行最后一次fork(),這一步不是必須的,但通常都這么做,它最大的意義是防止獲得控制終端。(在直接打開一個終端設備,而且沒有使用O_NOCTTY標志的情況下, 會獲得控制終端)
其它事項說明:
- chdir() 守護進程默認繼承了父進程的當前工作目錄,當系統磁盤發生umount時將造成諸多的麻煩,通常將”/” 作為守護進程的當前工作目錄,可以避免上述的問題
- umask() 守護進程默認繼承了父進程的文件權限掩碼,這就給該子進程使用文件帶來了諸多的麻煩。因此,把文件權限掩碼設置為0,可以大大增強該守護進程的靈活性
- fclose(STDIN), fclose(STDOUT), fclose(STDERR) 關閉標准I/O流。用fork函數新建的子進程會從父進程那里繼承一些已經打開了的文件。這些被打開的文件可能永遠不會被守護進程讀寫,但它們一樣消耗系統資源,而且可能導致所在的文件系統無法卸下。