PHP-FPM源码分析浏览器请求PHP脚本执行,中间有一个必须的模块就是网络处理模块,FPM是这个模块的一部分,配合fastcgi协议实现请求从监听到转发到PHP中进行处理,并将结果返回给这个进程。FPM采用多进程模型,即创建一个master进程,在master进程中创建并监听socket,然后fork多个子进程,然后子进程分别接受请求,子进程阻塞在启动后接受,并在请求到达后开始读取。获取请求数据,读取后开始处理,然后返回。在此期间,不会接收到其他请求,也就是说fpm的子进程同时只能响应一个请求,处理完请求才会接受。一个请求,这是一个同步阻塞模型。master进程负责管理子进程,监控子进程的状态,控制子进程的数量。master进程和worker进程之间通过共享变量进行信息同步。从主函数开始intmain(intargc,char*argv[]){zend_signal_startup();//设置全局变量sapi_module为cgi_sapi_modulesapi_startup(&cgi_sapi_module);fcgi_init();//获取命令行参数,这里解析php-fpm-D、-i等参数//...cgi_sapi_module.startup(&cgi_sapi_module);fpm_init(argc,argv,fpm_config?fpm_config:CGIG(fpm_config),fpm_prefix,fpm_pid,test_conf,php_allow_to_run_as_root,force_daemon,force_stderr);//master进程会在这一步陷入死循环,后面的进程全部由子进程执行。fcgi_fd=fpm_run(&max_requests);fcgi_fd=fpm_run(&max_requests);request=fpm_init_request(fcgi_fd);//acceptrequest//....}main()函数展示了这次fpm运行的完整框架,我们可以看到整个fpm的主要组成部分分为三部分:1.运行前的fpm_init();2、运行函数fpm_run();3.子流程接受请求处理。FPM中的事件监听机制在详细了解fpm的工作过程之前,首先要了解fpm中的事件机制。fpm中事件的监听默认是通过kqueue实现的。kqueue的介绍可以参考我之前整理的这篇文章。kqueue用法简介。//fpm中的事件结构structfpm_event_s{//事件句柄intfd;//下一个触发事件structtimevaltimeout;//frequency:多久执行一次structtimevalfrequency;//事件触发时调用的函数void(*callback)(structfpm_event_s*,short,void*);无效*参数;//调用回调时的参数//FPM_EV_READ:read;FPM_EV_TIMEOUT:;FPM_EV_PERSIST:;FPM_EV_EDGE:;内部标志;整数索引;//在fd句柄数组中的索引中//事件类型FPM_EV_READ:read;FPM_EV_TIMEOUT:定时器;FPM_EV_PERSIST:;FPM_EV_EDGE:;短哪个;};//事件队列typedefstructfpm_event_queue_s{structfpm_event_queue_s*prev;结构fpm_event_queue_s*下一个;结构fpm_event_s*ev;}fpm_event_queue;以master进程在fpm_run()中注册的一个sp[0]可读事件为例:voidfpm_event_loop(interr){staticstructfpm_event_ssignal_fd_event;//创建事件:pipelinesp[0]Triggerfpm_event_set(&signal_fd_event,fpm_signals_get_fd(),FPM_EV_READ,&fpm_got_signal,NULL)whenreadable;//添加事件到队列fpm_event_add(&signal_fd_event,0);//进程定时器等逻辑//以阻塞方式获取事件//module->wait()是接口定义的方法签名,kqueue的实现如下所示ret=module->wait(fpm_event_queue_fd,timeout);}intfpm_event_add(structfpm_event_s*ev,unsignedlongintfrequency){//...//如果事件是触发事件,则加入队列//对于定时器事件,先设置事件的触发频率和下一次触发event根据事件发生的频率if(fpm_event_queue_add(&fpm_event_queue_timer,ev)!=0){return-1;}return0;}staticintfpm_event_queue_add(structfpm_event_queue_s**queue,structfpm_event_s*ev){//...//构造并插入当前事件到事件队列中if(*queue==fpm_event_queue_fd&&module->add){//module->add(ev)是一个接口定义的方法签名,kqueue的实现如下所示module->add(ev);}return0;}//kqueue向kqueue添加事件的实现staticintfpm_event_kqueue_add(structfpm_event_s*ev)/*{{{*/{structkeventk;intflags=EV_ADD;如果(ev->flags&FPM_EV_EDGE){flags=flags|EV_CLEAR;}EV_SET(&k,ev->fd,EVFILT_READ,flags,0,0,(void*)ev);if(kevent(kfd,&k,1,NULL,0,NULL)<0){zlog(ZLOG_ERROR,"kevent:无法添加事件");返回-1;}/*将事件标记为已注册*/ev->index=ev->fd;return0;}FPM中关于kqueue的实际//kqueue关于从kqueue中监听器的实际staticintfpm_event_kqueue_wait(structfpm_event_queue_s*queue,unsignedlonginttimeout)/*{{{*/{structtimespect;诠释,我;/*在调用kevent()之前确保我们有一个干净的kevents*/memset(kevents,0,sizeof(structkevent)*nkevents);/*将ms转换为timespec结构*/t.tv_sec=timeout/1000;t.tv_nsec=(超时%1000)*1000*1000;/*等待传入事件或超时*/ret=kevent(kfd,NULL,0,kevents,nkevents,&t);if(ret==-1){/*触发错误,除非信号中断*/if(errno!=EINTR){zlog(ZLOG_WARNING,"epoll_wait()returns%d",errno);返回-1;}}/*触发红色事件*/for(i=0;iconfig。所谓workerpool就是fpm可以同时监控多个端口,每个端口对应一个workerpool。fpm_scoreboard_init_main为每个workerpool分配一个fpm_scoreboard_s结构的内存空间scoreboard,用于记录worker进程运行信息。//fpm_scoreboard_s结构structfpm_scoreboard_s{union{atomic_tlock;字符虚拟[16];};字符池[32];国际下午;//进程管理方法static,dynamic,ondemandtime_tstart_epoch;内部闲置;//空闲工作进程数intactive;//繁忙的工作进程数intactive_max;//最大繁忙进程数unsignedlongintrequests;unsignedintmax_children_reached;内部lq;intlq_max;无符号整数lq_len;无符号整数nprocs;intslow_rq;structfpm_scoreboard_proc_s*procs[];};fpm_signals_init_mainfpm注册自己的信号量,设置监听函数的处理逻辑。intfpm_signals_init_main()/*{{{*/{structsigactionact;//创建全双工套接字//全双工套接字是一个套接字通道[0]和[1可读写],每个进程固定一个管道。//写入数据时:管道未满,不会阻塞;whenreadingdata:nodatainthepipelinewillbeblocked(configurable)//写数据到sp[0]时,sp[0]的读会被阻塞,sp[1]的写pipeline会被阻塞,read此时sp[1]中sp[0]的数据if(0>socketpair(AF_UNIX,SOCK_STREAM,0,sp)){zlog(ZLOG_SYSERROR,"failedtoinitsignals:socketpair()");返回-1;}if(0>fd_set_blocked(sp[0],0)||0>fd_set_blocked(sp[1],0)){zlog(ZLOG_SYSERROR,“初始化信号失败:fd_set_blocked()”);返回-1;}if(0>fcntl(sp[0],F_SETFD,FD_CLOEXEC)||0>fcntl(sp[1],F_SETFD,FD_CLOEXEC)){zlog(ZLOG_SYSERROR,"初始化信号失败:fcntl(F_SETFD,FD_CLOEXEC)");返回-1;}memset(&act,0,sizeof(act));act.sa_handler=sig_handler;//监听信号调用这个函数sigfillset(&act.sa_mask);如果(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,"初始化信号失败:sigaction()");返回-1;}return0;}//所有信号共享同一个处理程序staticvoidsig_handler(intsigno)/*{{{*/{staticconstcharsig_chars[NSIG+1]={[SIGTERM]='T',[SIGINT]='I',[SIGUSR1]='1',[SIGUSR2]='2',[SIGQUIT]='Q',[SIGCHLD]='C'};字符;intsaved_errno;if(fpm_globals.parent_pid!=getpid()){返回;}saved_errno=errno;s=sig_chars[signo];zend_quiet_write(sp[1],&s,sizeof(s));//将信息对应的字节写入管道的sp[1]端,此时sp[1]端读取的数据会被阻塞;可以从sp[0]读取数据enderrno=saved_errno;}fpm_sockets_init_main每个workerpool打开一个socketsocketfpm_event_init_main这里启动master的事件管理器。它用于管理IO和时序事件。IO事件由kqueue、epoll、poll、select等管理。定时事件是定时器,在一定时间后触发事件。同样,我们以kqueue的实现为例来看一下源码。intfpm_event_init_main(){//...if(module->init(max)<0){zlog(ZLOG_ERROR,"无法初始化事件模块%s",module->name);返回-1;}//...}//max用于指定kqueue事件数组的大小staticintfpm_event_kqueue_init(intmax)/*{{{*/{if(max<1){return0;}kfd=kqueue();if(kfd<0){zlog(ZLOG_ERROR,"kqueue:无法初始化");返回-1;}kevents=malloc(sizeof(structkevent)*max);if(!kevents){zlog(ZLOG_ERROR,"epoll:无法分配%d个事件",max);返回-1;}memset(kevents,0,sizeof(structkevent)*max);nkevents=max;return0;}fpm_run这是fpm_init的结束,下面进入fpm_run阶段,在这个阶段,master进程会根据配置fork出多个子进程,然后master进程会进入fpm_event_loop(0)函数,而这个函数内部会出现死循环,也就是说master进程不会再执行后面的代码,后面的逻辑就是所有子进程要执行的动作。在fpm_event_loop中,master进程通过pipesp监听子进程的各种事件,同时也处理自己产生的一些事件、定时器等任务,以响应子进程的管理。内部逻辑在介绍事件监听机制时已经详细描述。intfpm_run(int*max_requests)/*{{{*/{structfpm_worker_pool_s*wp;/*在所有池中创建初始孩子*/for(wp=fpm_worker_all_pools;wp;wp=wp->next){intis_parent;is_parent=fpm_children_create_initial(wp);如果(!is_parent){gotorun_child;}}/*永远运行事件循环*/fpm_event_loop(0);run_child:/*只有worker到达这个点*/fpm_cleanups_run(FPM_CLEANUP_CHILD);*max_requests=fpm_globals.max_requests;returnfpm_globals.listening_socket;}子进程处理请求并返回主函数。fpm_run背后的逻辑是子进程正在运行。首先会初始化fpm的request结构体的一个变量,然后子进程会阻塞在fcgi_accept_request(request)函数上等待请求。fcgi_accept_request函数是一个无限循环的socket编程accept函数,用于接收请求并取出所有请求数据。...//初始化requestrequest=fpm_init_request(fcgi_fd);zend_first_try{//acceptacceptrequestwhile(EXPECTED(fcgi_accept_request(request)>=0)){init_request_info();fpm_request_info();if(UNEXPECTED(php_request_startup()==FAILURE)){//...}if(UNEXPECTED(fpm_status_handle_request())){gotofastcgi_request_done;}...//打开配置文件中DOCUMENT_ROOT设置的脚本if(UNEXPECTED(php_fopen_primary_script(&file_handle)==FAILURE)){...}fpm_request_executing();//执行脚本php_execute_script(&file_handle);...}//销毁请求requestfcgi_destroy_request(request);//fcgi退出fcgi_shutdown();如果(cgi_sapi_module.php_ini_path_override){免费(cgi_sapi_module.php_ini_path_override);}如果(cgi_sapi_module.ini_entries){免费(cgi_sapi_module.ini_entries);}}zend_catch{...}zend_end_try();