PHP-FPM由1個master進程和N個worker進程組成。其中,Worker進程由master進程fork而來。react
PHP-FPM有3種worker進程管理模式。linux
初始化時調用fpm_children_make(wp,0,0,1)函數fork出pm.max_children數量的worker進程,後續再也不動態增減worker進程數量nginx
初始化時調用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
初始化時不生成worker進程,但註冊事件ondemand_event監聽listening_socket。當listen_socket收到request,先檢查是否存在已生成的空閒的worker進程,若存在就使用這個空閒進程,不然fork一個新的進程。每隔1秒觸發的心跳事件fpm_pctl_perform_idle_server_maintenance()會kill掉空閒時間超過pm.process_idle_timeout的worker進程apache
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進行監聽。
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)隊列。