Linux -- PHP-FPM的源碼解析和模型

1.進程管理模式

PHP-FPM由1個master進程和N個worker進程組成。其中,Worker進程由master進程fork而來。react

 

PHP-FPM有3種worker進程管理模式。linux

1. Static

初始化時調用fpm_children_make(wp,0,0,1)函數fork出pm.max_children數量的worker進程,後續再也不動態增減worker進程數量nginx

2. Dynamic

初始化時調用fpm_children_make(wp,0,0,1)函數fork出pm.start_servers數量的worker進程,而後由每隔1秒觸發的心跳事件fpm_pctl_perform_idle_server_maintenance()來維護空閒woker進程數量:空閒worker進程數量若多於pm.max_spare_servers則kill進程,若少於pm.min_spare_servers則fork進程。web

3. Ondemand

初始化時不生成worker進程,但註冊事件ondemand_event監聽listening_socket。當listen_socket收到request,先檢查是否存在已生成的空閒的worker進程,若存在就使用這個空閒進程,不然fork一個新的進程。每隔1秒觸發的心跳事件fpm_pctl_perform_idle_server_maintenance()會kill掉空閒時間超過pm.process_idle_timeout的worker進程apache

 

2. 標準IO

FastCGI的典型流程以下:socket

(1)     web server(例如nginx或apache)接受到一個請求。而後,web server經過unix域socket或TCP socket鏈接到FastCGI應用。函數

(2)     FastCGI應用能夠選擇接受或拒絕這個鏈接。若是接受了鏈接,FastCGI應用會試圖從stream中讀取到一個packet性能

(3)     Web server發送的第一個packet是BEGIN_REQUEST packet。BEGIN_REQUEST packet包含一個獨一無二的request ID。全部該request的後續packet都被這個ID標記。ui

 

Unix系統中,標準輸入的文件描述符是0,標準輸出的文件描述符是1,標準錯誤輸出的文件描述符是2,宏定義以下:spa

#define STDIN_FILENO  0

#define STDOUT_FILENO  1

#define STDERR_FILENO  2

 

 

PHP-FPM重定向了這三個標準IO。

在master進程中,STDIN_FILENO(0)和STDOUT_FILENO(1)均重定向到」/dev/null」。 STDERR_FILENO(2)重定向到error_log。

在worker進程中,STDIN_FILENO(0)重定向到listening_socket。若是catch_workers_output爲no的話,STDOUT_FILENO(1)和STDERR_FILENO(2)均重定向到」/dev/null」。不然,STDOUT_FILENO(1)重定向到1個pipe的寫端,而這個pipe的fd讀端保存於master進程child鏈表對應的child節點結構的fd_stdout元素上。一樣的,STDERR_FILENO(2)也重定向到1個pipe的寫端,而這個pipe的讀端fd保存於master進程child鏈表對應的child節點結構的fd_stderr元素上。以上兩個位於master進程的pipe讀端由master進程的reactor進行監聽。

 

 

3. 進程間通訊模型

PHP-FPM中的進程間通訊主要分爲

1. Master進程和worker進程之間的通訊

前面講過master進程和worker進程間有兩條pipe。Worker進程向STDOUT_FILE或STDERR_FILENO中寫信息,master進程收到信息後寫入log。Master進程用reactor監聽兩個pipe

2. Web server與worker進程之間的通訊

Worker進程阻塞在accept(listening socket)監聽web server

3. Web server與master進程之間的通訊

當pm模式是ondemand時,master進程會在reactor註冊listening_socket的監聽事件。當有request到來,master進程將生成一個worker進程

 

 

PHP-FPM採用的進程模型是進程池。Worker進程繼承由master進程socket(),bind(),listen()的socket fd並直接阻塞在accept()上。當有一個request到來,進程池中的一個worker進程接受request。當這個worker進程完成執行,就會返回進程池等待新的request。這事實上是leader/follower模式。在leader/follower模式中,僅有leader阻塞等待,其餘進程都在sleep。一樣的,在FPM中,因爲linux內核解決了accept()的驚羣問題,新request一樣只會喚醒一個worker進程。在這裏,leader的繼任是由linux內核決定的(固然,你也能夠用mutex守衛accept代碼段來確保leader只有一位)。

Worker進程處理全部IO和邏輯。Master進程負責worker進程的生成和銷燬。Master進程的Reactor註冊了三個可讀事件和四個定時器事件。當pm是ondemand時,額外註冊一個可讀事件。三個可讀事件分別是1個信號事件,2個pipe事件。

 

 

Fpm_event_s 結構:

struct fpm_event_s {

         int fd;                

         struct timeval timeout;  

         struct timeval frequency;

         void (*callback)(struct fpm_event_s *, short, void *);

         void *arg;

         int flags;

         int index;              

         short which;          

};

 

Flags表明該事件的類型。FPM中flags的值有三種:

FPM_EV_READ : 可讀事件

FPM_EV_PERSIST : 心跳事件

FPM_EV_READ | FPM_EV_EDGE : 邊緣觸發的可讀事件

 

Which表明該事件位於哪一個事件隊列。其值有兩種:

FPM_EV_READ : 位於可讀事件隊列

FPM_EV_TIMEOUT : 位於定時器事件隊列

 

事件隊列的結構是雙向鏈表

typedef struct fpm_event_queue_s {

         struct fpm_event_queue_s *prev;

         struct fpm_event_queue_s *next;

         struct fpm_event_s *ev;

} fpm_event_queue;

 

static struct fpm_event_queue_s *fpm_event_queue_timer = NULL;

static struct fpm_event_queue_s *fpm_event_queue_fd = NULL;  

fpm_event_queue_timer是定時器事件隊列,fpm_event_queue_fd是可讀事件隊列。定時器事件隊列並無採用最小堆,紅黑樹或事件輪等結構,由於這個隊列很是小,沒有必要使用這些複雜結構。可是若是把定時器事件隊列改成升序鏈表,對性能應該會有提高。

 

Fd和index僅在可讀事件中使用。fd表示被監聽的文件描述符。Index的值與使用哪一個IO複用API有關。在epoll和select中,index的值等於fd的值。在poll中,index是該fd在描述符集fds[]中位置的下標。在心跳事件中,fd == -1,index == -1。

 

Struct timeval timeout和struct timeval frequency僅在心跳事件中使用。frequency表示每隔多少時間觸發一次心跳事件,Timeout表示下一次觸發心跳事件的時刻,一般由now與frequency相加而得。在可讀事件中,這兩個結構不設置。

 

Signal_fd_event事件

先來看fpm中的信號處理。

int fpm_signals_init_main() /* {{{ */

{

         struct sigaction act;

 

        /* create socketpair*/

         if (0 > socketpair(AF_UNIX, SOCK_STREAM, 0, sp)) {

                   zlog(ZLOG_SYSERROR, "failed to init signals: socketpair()");

                   return -1;

         }

 

        /*將兩個socket設爲NONBLOCK*/

         if (0 > fd_set_blocked(sp[0], 0) || 0 > fd_set_blocked(sp[1], 0)) {

                   zlog(ZLOG_SYSERROR, "failed to init signals: fd_set_blocked()");

                   return -1;

         }

 

        /*若是程序成功地運行完畢,則自動關閉這兩個fd*/

         if (0 > fcntl(sp[0], F_SETFD, FD_CLOEXEC) || 0 > fcntl(sp[1], F_SETFD, FD_CLOEXEC)) {

                   zlog(ZLOG_SYSERROR, "falied to init signals: fcntl(F_SETFD, FD_CLOEXEC)");

                   return -1;

         }

 

         memset(&act, 0, sizeof(act));

        /* 將信號處理函數設爲sig_handler*/

         act.sa_handler = sig_handler;

        /* 將全部信號加入信號集*/

         sigfillset(&act.sa_mask);

 

        /* 更改指定信號的action */

         if (0 > sigaction(SIGTERM,  &act, 0) ||

             0 > sigaction(SIGINT,   &act, 0) ||

             0 > sigaction(SIGUSR1,  &act, 0) ||

             0 > sigaction(SIGUSR2,  &act, 0) ||

             0 > sigaction(SIGCHLD,  &act, 0) ||

             0 > sigaction(SIGQUIT,  &act, 0)) {

 

                   zlog(ZLOG_SYSERROR, "failed to init signals: sigaction()");

                   return -1;

         }

         return 0;

}

 

master進程註冊了SIGTERM,SIGINT,SIGUSR1,SIGUSR2,SIGCHLD,SIGQUIT,並建立了socketpair sp[]。當收到這些信號時,master進程將向sp[1]中寫一個表明該信號的字符。

                   [SIGTERM] = 'T',

                   [SIGINT]  = 'I',

                   [SIGUSR1] = '1',

                   [SIGUSR2] = '2',

                   [SIGQUIT] = 'Q',

                   [SIGCHLD] = 'C'

Sp[0]就是Signal_fd_event事件監聽的fd。該事件的回調函數對不一樣的信號(從sp[0]讀到的表明信號的字符)作出不一樣的反應。

信號SIGCHLD:

調用fpm_children_bury();該函數調用waitpid()分析子進程的status,根據狀況決定是否重啓子進程。

信號SIGINT , SIGTERM:

調用fpm_pctl(FPM_PCTL_STATE_TERMINATING, FPM_PCTL_ACTION_SET);把fpm的狀態改成terminating

信號SIGQUIT

調用fpm_pctl(FPM_PCTL_STATE_FINISHING, FPM_PCTL_ACTION_SET); 把fpm的狀態改成finishing

信號SIGUSR1

調用fpm_stdio_open_error_log(1)重啓error log file

調用fpm_log_open(1)重啓access log file 並重啓全部子進程

信號SIGUSR2

調用fpm_pctl(FPM_PCTL_STATE_RELOADING, FPM_PCTL_ACTION_SET); 把fpm的狀態改成reloading

 

子進程的信號處理

子進程關閉了socketpair,並把SIGTERM,SIGINT,SIGUSR1,SIGUSR2,SIGHLD從新設爲默認動做,把SIGQUIT設爲soft quit。

int fpm_signals_init_child() /* {{{ */

{

         struct sigaction act, act_dfl;

 

         memset(&act, 0, sizeof(act));

         memset(&act_dfl, 0, sizeof(act_dfl));

 

         act.sa_handler = &sig_soft_quit;

         // 當system call或library function阻塞時一個信號到來。系統默認會返回錯誤並設置errno爲EINTR.這裏設爲自動重啓

         act.sa_flags |= SA_RESTART;

 

         act_dfl.sa_handler = SIG_DFL;

 

         close(sp[0]);

         close(sp[1]);

 

         if (0 > sigaction(SIGTERM,  &act_dfl,  0) ||

             0 > sigaction(SIGINT,   &act_dfl,  0) ||

             0 > sigaction(SIGUSR1,  &act_dfl,  0) ||

             0 > sigaction(SIGUSR2,  &act_dfl,  0) ||

             0 > sigaction(SIGCHLD,  &act_dfl,  0) ||

             0 > sigaction(SIGQUIT,  &act,      0)) {

 

                   zlog(ZLOG_SYSERROR, "failed to init child signals: sigaction()");

                   return -1;

         }

 

         zend_signal_init();

         return 0;

}

 

 

其一是可讀事件隊列。其二是定時器(timer)隊列。

相關文章
相關標籤/搜索