在我們?nèi)粘>幊讨校R姷睦颖热?php think 需要不斷執(zhí)行的任務(wù),比如 php arts… 和 php yii…,都會(huì)通過 nohup 掛載到后臺(tái),以保持長(zhǎng)期運(yùn)行狀態(tài)。同樣,在workerman中,使用類似php index.php start的命令來(lái)啟動(dòng)進(jìn)程,但不同的是,它不需要使用nohup來(lái)掛載并在后臺(tái)運(yùn)行。有的朋友可能會(huì)好奇是如何實(shí)現(xiàn)的呢?為了解決小伙伴們關(guān)心的問題,今天我們重點(diǎn)深入分析workerman守護(hù)進(jìn)程的實(shí)現(xiàn)原理。
我們先來(lái)了解一些流程相關(guān)的知識(shí):
父進(jìn)程:父進(jìn)程是生成其他進(jìn)程的進(jìn)程。當(dāng)一個(gè)進(jìn)程創(chuàng)建另一個(gè)進(jìn)程時(shí),創(chuàng)建者稱為父進(jìn)程,創(chuàng)建的進(jìn)程成為子進(jìn)程。父進(jìn)程可以通過進(jìn)程標(biāo)識(shí)符(pid)來(lái)識(shí)別它創(chuàng)建的子進(jìn)程。
子進(jìn)程:子進(jìn)程是父進(jìn)程創(chuàng)建的新進(jìn)程。子進(jìn)程繼承了父進(jìn)程的一些屬性,比如環(huán)境變量、文件描述符等。子進(jìn)程獨(dú)立于父進(jìn)程運(yùn)行,可以執(zhí)行自己的代碼,擁有自己的資源和內(nèi)存空間。
進(jìn)程組:進(jìn)程組是關(guān)聯(lián)進(jìn)程的集合。每個(gè)進(jìn)程組都有一個(gè)唯一的進(jìn)程組 id (pgid),用于標(biāo)識(shí)該進(jìn)程組。進(jìn)程組通常由父進(jìn)程創(chuàng)建,并包括與父進(jìn)程具有相同會(huì)話 id (sid) 的所有子進(jìn)程。
會(huì)話:會(huì)話是相關(guān)進(jìn)程的集合,通常在用戶登錄系統(tǒng)時(shí)開始,在用戶注銷或關(guān)閉終端時(shí)結(jié)束。會(huì)話中的進(jìn)程共享同一個(gè)控制終端。每個(gè)會(huì)話都有一個(gè)唯一的會(huì)話 id (sid),用于標(biāo)識(shí)該會(huì)話。一個(gè)會(huì)話通常包含一個(gè)或多個(gè)進(jìn)程組,第一個(gè)進(jìn)程組成為會(huì)話的主進(jìn)程組。
這些概念俗稱八足文,從來(lái)都不容易理解。讓我們看一個(gè)例子。執(zhí)行命令“php index.html”后php’,生成了進(jìn)程61052。該進(jìn)程的父進(jìn)程是bash進(jìn)程8243,所以這里無(wú)需擔(dān)心。然后,通過fork創(chuàng)建了一個(gè)子進(jìn)程61053,其父進(jìn)程為61052。這兩個(gè)進(jìn)程有共同的進(jìn)程組61052和會(huì)話8243。調(diào)用posix_setsid函數(shù)會(huì)為子進(jìn)程打開一個(gè)新的進(jìn)程組61053和一個(gè)新的會(huì)話61053進(jìn)程61053,其中的會(huì)話可以理解為新的命令窗口終端。最后,子進(jìn)程61053通過fork創(chuàng)建了子進(jìn)程61054,進(jìn)程61053升級(jí)為父進(jìn)程。再次fork的原因是為了避免與終端控制的進(jìn)程相關(guān)聯(lián)。這個(gè)進(jìn)程61052是在終端模式下創(chuàng)建的,從此以后,進(jìn)程61054就形成了守護(hù)進(jìn)程。
[ manongsen@root phpwork]$ php index.php [parent] process id: 61052, parent process id: 8243, process group id: 61052, session id: 8243 [parent1] process id: 61052, parent process id: 8243, process group id: 61052, session id: 8243 exited the process [child1] process id: 61053, parent process id: 61052, process group id: 61052, session id: 8243 [child1] process id: 61053, parent process id: 61052, process group id: 61053, session id: 61053 [parent2] process id: 61053, parent process id: 61052, process group id: 61053, session id: 61053 exited the process [child2] process id: 61054, parent process id: 61053, process group id: 61053, session id: 61053. this process is retained [ manongsen@root phpwork]$ ps aux | grep index.php root 66064 0.0 0.0 408105040 1472 s080 s+10:00 pm 00:00 grep index.php root 61054 0.0 438073488 280?? s 10:00 pm 00:00 php index.php
上面提到的進(jìn)程信息就是通過這段代碼的執(zhí)行而生成的。如果你仔細(xì)閱讀這段代碼,你會(huì)發(fā)現(xiàn)為什么 posix_setsid 函數(shù)不是在第一個(gè) fork 之前調(diào)用,而是在第二個(gè) fork 之前調(diào)用。這樣就不用fork兩次了?原因是leader進(jìn)程無(wú)法創(chuàng)建會(huì)話,而進(jìn)程組id 61052與進(jìn)程id 61052相同,即當(dāng)前進(jìn)程是leader進(jìn)程。因此需要一個(gè)子進(jìn)程來(lái)創(chuàng)建新的session,這一點(diǎn)需要特別注意。
php function echomsg($prefix, $suffix="") { //process id $pid = getmypid(); //process group id $pgid = posix_getpgid($pid); //session id $sid = posix_getsid($pid); //parent process id $ppid = posix_getppid(); echo "[{$prefix}] process id: {$pid}, parent process id: {$ppid}, process group id: {$pgid}, session id: {$sd} {$suffix}". php_eol; } //[parent] process id: 61052, parent process id: 8243, process group id: 61052, session id: 8243 echomsg("parent"); //the first fork process $pid = pcntl_fork(); if ( $pid 0 ) { //[parent1] process id: 61052, parent process id: 8243, process group id: 61052, session id: 8243 exited the process echoes ("parent1", "exited the process"); exit; } //the child process id created is 61053, but the process group, session, and parent process are still the same //[child1] process id: 61053, parent process id: 61052, process group id: 61052, session id: 8243 echomsg("child1"); //calling the posix_setsid function will create a new session and process group, and set the process group id and session id to that process id if (-1 === \posix_setsid()) { throw new exception("setsid fail"); } //now you will find that both the process group id and session id have changed to 61053, which is equivalent to starting a session window similar to a linux terminal //[child1] process id: 61053, parent process id: 61052, process group id: 61053, session id: 61053 echomsg("child1"); //second fork process //the reason for requiring a secondary fork process here is to avoid being associated with terminal controlled processes. this process 61052 was created in terminal mode //need to detach from process 61052 to ensure the stability of the daemon process $pid = pcntl_fork(); if ( $pid 0 ) { //[parent2] process id: 61053, parent process id: 61052, process group id: 61053, session id: 61053 exited the process echoes ("parent2", "exited the process"); exit; } //at this point, the process has broken free from the control of the terminal process and formed a daemon process //[child2] process id: 61054, parent process id: 61053, process group id: 61053, session id: 61053. this process is retained echoes ("child2", "this process is reserved"); sleep(100);
最好有時(shí)間的朋友自己執(zhí)行代碼分析一下,會(huì)有不同的獎(jiǎng)勵(lì)。這里假裝你已經(jīng)實(shí)踐過了,我們看一下workerman的worker.php文件第554行runall方法中的static::daemon()函數(shù)。實(shí)現(xiàn)的流程邏輯與上面的例子幾乎相同。不過這里也用到了umask函數(shù),它的主要作用是給進(jìn)程創(chuàng)建的文件或目錄授予相應(yīng)的權(quán)限,保證有對(duì)文件或目錄進(jìn)行操作的權(quán)限。
// workerman/Worker.php:554 /** * Run all worker instances. *Run process * @return void */ public static function runAll() { static::checkSapiEnv(); static::init(); static::parseCommand(); static::lock(); //Create a process and form a daemon process static::daemonize(); static::initWorkers(); static::installSignal(); static::saveMasterPid(); static::lock(\LOCK_UN); static::displayUI(); static::forkWorkers(); static::resetStd(); static::monitorWorkers(); } // workerman/Worker.php:1262 /** * Run as daemon mode. *Run in daemon mode * @throws Exception */ protected static function daemonize() { //Determine whether it is already in a guarded state and whether the current system is in a Linux environment if (! static::$daemonize || static::$_OS !== \OS_TYPE_LINUX) { return; } //If umask is set to 0, the file permissions created by the current process will be 777, which has the highest permission \umask(0); //The first time creating a process $pid = \pcntl_fork(); if (-1 === $pid) { //Process creation failed throw new Exception('Fork fail'); } elseif ($pid > 0) { //Main process exits exit(0); } //The child process continues to execute //By calling the posix_setsid function, a process can detach from its parent process and transform into a daemon process if (-1 === \posix_setsid()) { throw new Exception("Setsid fail"); } //The second creation process, in a System V based system, exits by forking the parent process again //Ensure that the formed daemon process does not become the first session process and does not have control terminals $pid = \pcntl_fork(); if (-1 === $pid) { //Process creation failed throw new Exception("Fork fail"); } elseif (0 !== $pid) { //Main process exits exit(0); } //The child process continues to execute }
守護(hù)進(jìn)程也是workerman的重要組成部分,保證了workerman進(jìn)程的穩(wěn)定性。與我們通過nohup啟動(dòng)的命令不同,這些命令有時(shí)會(huì)在后臺(tái)掛起而沒有人注意到,朋友們可能已經(jīng)經(jīng)歷過這一點(diǎn)。當(dāng)然,市面上也有一些開源的守護(hù)進(jìn)程管理軟件,比如supervisors,也有人使用screen、tmux等會(huì)話終端來(lái)實(shí)現(xiàn)。事實(shí)上,守護(hù)進(jìn)程的實(shí)現(xiàn)方式有很多種。這里僅介紹一個(gè)php中實(shí)現(xiàn)守護(hù)進(jìn)程模式的例子來(lái)分析workerman中守護(hù)進(jìn)程的實(shí)現(xiàn)原理。希望本內(nèi)容能夠?qū)δ兴鶐椭?/p>
如果您想體驗(yàn)新版本php或者切換php版本,歡迎使用servbay部署php開發(fā)環(huán)境。這是我正在開發(fā)的產(chǎn)品,我會(huì)長(zhǎng)期維護(hù)這個(gè)工具。
立即學(xué)習(xí)“PHP免費(fèi)學(xué)習(xí)筆記(深入)”;
以上就是深入理解PHP高性能框架中Workerman守護(hù)進(jìn)程原理的詳細(xì)內(nèi)容,更多請(qǐng)關(guān)注愛掏網(wǎng) - it200.com其它相關(guān)文章!