php萬字碼出完美守護進程詳解
前事提要
上期我們詳細學(xué)習(xí)了會話的概念以及用法,會話,進程組,終端的理解對本篇講述的守護進程極其重要,如還不理解相關(guān)概念建議翻看我往期關(guān)于會話,進程組,終端文章。
基本概念
守護進程(Daemon Process),也就是通常說的 Daemon 進程(精靈進程),是 Linux 中的后臺服務(wù)進程。通常獨立于控制終端并且周期性地執(zhí)行某種任務(wù)或等待處理某些發(fā)生的事件。并且不跟任何的控制終端關(guān)聯(lián),如果想讓某個進程不因為用戶或中斷或其他變化而影響,那么就必須把這個進程變成一個守護進程。
常見的守護進程包括系統(tǒng)日志進程syslogd、 web服務(wù)器httpd、任務(wù)規(guī)劃守護進程crond,數(shù)據(jù)庫服務(wù)器mysqld等。一般采用以 d 結(jié)尾的名字。
查看系統(tǒng)守護進程命令 ps -efj
基本特點
生存周期長[非必須],一般操作系統(tǒng)啟動的時候就啟動,關(guān)閉的時候關(guān)閉。
守護進程和終端無關(guān)聯(lián),也就是他們沒有控制終端,所以當(dāng)控制終端退出,也不會導(dǎo)致守護進程退出。
守護進程是在后臺運行,不會占著終端,終端可以執(zhí)行其他命令
守護進程的父進程是1號進程,也就是init進程;
- 在Linux中 , 大概有三種方式實現(xiàn)腳本后臺化 :
1 . 在命令后添加一個&符號 , 比如 php task.php & . 這個方法的缺點在于 如果terminal終端關(guān)閉 , 無論是正常關(guān)閉還是非正常關(guān)閉 , 這個php進程都會隨著終端關(guān)閉而關(guān)閉 , 其次是代碼中如果有echo或者print_r之類的輸出文本 , 會被輸出到當(dāng)前的終端窗口中 .
2 . 使用nohup命令 , 比如 nohup php task.php & . 默認情況下 , 代碼中echo或者print_r之類輸出的文本會被輸出到php代碼同級目錄的nohup.out文件中 . 如果你用exit命令或者關(guān)閉按鈕等正常手段關(guān)閉終端 , 該進程不會被關(guān)閉 , 依然會在后臺持續(xù)運行 . 但是如果終端遇到異常退出或者終止 , 該php進程也會隨即退出 . 本質(zhì)上 , 也并非穩(wěn)定可靠的daemon方案 .
3 . 使用fork和setsid , 我暫且稱之為 : *nix解決方案
創(chuàng)建守護進程要求
- 1. 設(shè)置文件創(chuàng)建屏蔽字
umask(0)
文件創(chuàng)建屏蔽字是指屏蔽掉文件創(chuàng)建時的對應(yīng)位(umask() 控制系統(tǒng)文件和目錄默認權(quán)限
)。由于使用fork系統(tǒng)調(diào)用新建的子進程繼承了父進程的文件創(chuàng)建掩碼,這就給該子進程使用文件帶來了諸多的不便。因此,把文件創(chuàng)建掩碼設(shè)置為0,可以大大增強該守護進程的靈活性。
- 2. 調(diào)用fork,父進程退出(exit);
如果該守護進程是作為一條簡單的shell命令啟動的,那么父進程終止使得shell認為該命令已經(jīng)執(zhí)行完畢;保證子進程不是一個進程組的組長進程,為什么要保證不是進程組組長呢? 因為進程組組長調(diào)用setsid創(chuàng)建會話會報錯;
- 3. 子進程調(diào)用setsid 函數(shù)來創(chuàng)建會話
先介紹一下Linux中的進程與控制終端,登錄會話和進程組之間的關(guān)系:進程屬于一個進程組,進程組號(GID)就是進程組長的進程號(PID)。登錄會話可以包含多個進程組。這些進程組共享一個控制終端。這個控制終端通常是創(chuàng)建進程的登錄終端。
控制終端,登錄會話和進程組通常是從父進程繼承下來的。我們的目的就是要擺脫它們,使之不受它們的影響。方法是在第2點的基礎(chǔ)上,調(diào)用setsid()使進程成為會話組長:
setsid()調(diào)用成功后,進程成為新的會話組長和新的進程組長,并與原來的登錄會話和進程組脫離。由于會話過程對控制終端的獨占性,進程同時與控制終端脫離。
調(diào)用setsid有3個作用:
讓進程擺脫原會話的控制;
讓進程擺脫原進程組的控制;
讓進程擺脫原控制終端的控制
- 4. 把守護進程工作目錄設(shè)置為根目錄 chdir(“/”);
從父進程繼承過來的工作目錄可能在一個掛載的文件系統(tǒng)中。由于守護進程通常在系統(tǒng)再引導(dǎo)之前是一直存在的,所以如果守護進程的當(dāng)前工作目錄在一個掛載的文件系統(tǒng)中,會導(dǎo)致該文件系統(tǒng)不能被卸載。
- 5.把一些文件描述符關(guān)閉 【標(biāo)準輸入,標(biāo)準輸出,標(biāo)準錯誤】
文件描述符:用來標(biāo)識一個文件。當(dāng)你打開一個存在的文件或者創(chuàng)建一個新文件,操作系統(tǒng)都會返回這個文件描述符。后續(xù)對這個文件的操作的一些函數(shù),都會用到這個文件描述符作為參數(shù)。
Linux中三個特殊的文件描述符,數(shù)字分別為0,1,2:
0:標(biāo)準輸入[鍵盤],對應(yīng)的符號常量叫 STDIN_FILENO
1:標(biāo)準輸出[屏幕],對應(yīng)的符號常量叫 STDOUT_FILENO
2:標(biāo)準錯誤[屏幕],對應(yīng)的符號常量叫STDERR_FILENO
進程從創(chuàng)建它的父進程那里繼承了打開的文件描述符。如不關(guān)閉,將會浪費系統(tǒng)資源,造成進程所在的文件系統(tǒng)無法卸下以及引起無法預(yù)料的錯誤。
- 6. 當(dāng)調(diào)用setsid函數(shù)后,一般會在創(chuàng)建一個子進程,讓會話首進程退出,確保該進程不會再獲得控制終端
(1)調(diào)用一次fork的作用:
第一次fork的作用是讓shell認為這條命令已經(jīng)終止,不用掛在終端輸入上,還有就是為了后面的setsid服務(wù),因為調(diào)用setsid函數(shù)的進程不能是進程組組長,如果不fork出子進程,則此時的父進程是進程組組長,就無法調(diào)用setsid。當(dāng)子進程調(diào)用完setsid函數(shù)之后,子進程是會話組長也是進程組組長,并且脫離了控制終端,此時,不管控制終端如何操作,新的進程都不會收到一些信號使得進程退出。
(2)第二次fork的作用:
雖然當(dāng)前關(guān)閉了和終端的聯(lián)系,但是后期可能會誤操作打開了終端。
只有會話首進程能打開終端設(shè)備, 也就是再fork一次,再把父進程退出,再次fork的子進程作為守護進程繼續(xù)運行,保證了該守護進程不再是會話的首進程。
第二次不是必須的,是可選的。
- 7.編寫一個守護進程
<?php // 1. 設(shè)置文件創(chuàng)建屏蔽字 umask(0); // 2. fork 子進程 $pid = pcntl_fork(); if($pid > 0){ print("父進程退出\n"); exit(0); } //3. 設(shè)置當(dāng)前子進程為會話首進程,進程組長,斷開與終端的連接,成為后臺進程 if(-1 === posix_setsid()){ print("sid err \n"); } // 4. 把守護進程工作目錄設(shè)置為根目錄 chdir("/"); //已經(jīng)成為守護進程~\(≧▽≦)/~啦 while(1){ echo "test".PHP_EOL; sleep(2); }
將文件保存為daemon.php,然后php daemon.php執(zhí)行文件,嗯,執(zhí)行結(jié)果卻有些奇怪,大概類似于下圖:
即便你按Ctrl+C
都沒用,終端在不斷輸出test,唯一辦法就是關(guān)閉當(dāng)前終端窗口然后重新開一個,為什么會這樣,這就涉及到我們上面提到的第5點,需要關(guān)閉繼承過來的標(biāo)準輸出,輸入,錯誤,這樣我們的daemon程序不可以再將終端窗口當(dāng)作默認的標(biāo)準輸出了。
<?php // 設(shè)置文件創(chuàng)建屏蔽字 umask(0); // 第一次fork 子進程 $pid = pcntl_fork(); if($pid > 0){ print("父進程退出\n"); exit(0); } //設(shè)置當(dāng)前子進程為會話首進程,進程組長,斷開與終端的連接,成為后臺進程 if(-1 === posix_setsid()){ print("sid err \n"); } //第二次fork 徹底斷開控制終端 $pid = pcntl_fork(); if($pid > 0){ exit(0);//讓會話首進程退出 } // 把守護進程工作目錄設(shè)置為根目錄 chdir("/"); // 關(guān)閉標(biāo)準輸入,標(biāo)準輸出,標(biāo)準錯誤,linux 中使用數(shù)字表示文件描述符也就是 0,1,2 fclose(STDIN);//0 fclose(STDOUT);//1 fclose(STDERR);//2 //當(dāng)關(guān)掉以上標(biāo)準輸出,標(biāo)準輸入,標(biāo)準錯誤之后,如果后面要對文件操作(比如打開一個文件,寫入,創(chuàng)建)它返回的文件描述符從0開始,這樣可能造成未知異常 //為了避免問題,我們使用輸出從定向到 /dev/null 空設(shè)備文件解決這個問題,重新設(shè)置0,1,2 文件描述符用來代替標(biāo)準輸入,標(biāo)準輸出,標(biāo)準錯誤,往 /dev/null 寫入數(shù)據(jù)會被丟棄,這樣就不會向終端輸出數(shù)據(jù)了。 $stdin = fopen("/dev/null",'a'); $stdout = fopen("/dev/null",'a'); $stderr = fopen("/dev/null", 'a'); //已經(jīng)成為守護進程~\(≧▽≦)/~啦 while(1){ echo "test".PHP_EOL; sleep(2); }
空設(shè)備
/dev/null : 是一個特殊的設(shè)備文件,它丟棄一切寫入其中的數(shù)據(jù)(像黑洞一些)例如:echo “大雷編程” > /dev/null 輸出重定向文件到黑洞(無任何輸出)。
我們一般把守護進程的標(biāo)準輸入、標(biāo)準輸出重定向到空設(shè)備(黑洞),從而確保守護進程不從鍵盤接收任何東西,也不把輸出結(jié)果打印到屏幕。
到此這篇關(guān)于php萬字碼出完美守護進程詳解的文章就介紹到這了,更多相關(guān)php守護進程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
fleaphp crud操作之findByField函數(shù)的使用方法
fleaphp crud操作之findByField函數(shù)的用法分享,需要的朋友可以參考下。2011-04-04php自定義urlencode,urldecode函數(shù)實例
這篇文章主要介紹了php自定義urlencode,urldecode函數(shù),實例分析了php字符串轉(zhuǎn)碼的技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-03-03使用PHP連接多種數(shù)據(jù)庫的實現(xiàn)代碼(mysql,access,sqlserver,Oracle)
我們今天為大家介紹的PHP連接數(shù)據(jù)庫的方法包括在MYSQL數(shù)據(jù)庫、ACCESS數(shù)據(jù)庫、MS SQL數(shù)據(jù)庫和Oracle數(shù)據(jù)庫中實現(xiàn)2016-12-12淺析php與數(shù)據(jù)庫代碼開發(fā)規(guī)范
以下是對php與數(shù)據(jù)庫代碼開發(fā)規(guī)范進行了簡單的分析介紹。需要的朋友可以過來參考下2013-08-08