PHP-FPM 生命周期

PHP-FPM是一种多进程模型,主要由Master进程以及Worker进程组成,所有的cgi请求都会交由Worker进程处理。Master进程主要维护worker进程。
而worker进程的工作方式是抢占/竞争的方式,当一个accept请求过来的时候,谁先拿到算谁的,拿到后转化为FastCGIRquest,交由脚本处理。
整个模型如下图:
model

worker pool

每个进程池负责监听一个端口,多个进程池之间用链表关联。在其中也定义了children,fpm_child_s是一个双向链表,其中包含了子进程的一些独有信息,包括自己的PID,自己所属的进程池,fpm事件,标准输出等等。 传送门:src-path/sapi/fpm/fpm/fpm_worker_pool.h

/*abel: FPM 进程池
 * link list实现*/
struct fpm_worker_pool_s {
	struct fpm_worker_pool_s *next; //Next worker
	struct fpm_worker_pool_config_s *config; //php-fpm.conf
	char *user, *home;					/* for setting env USER and HOME */
	enum fpm_address_domain listen_address_domain;
	int listening_socket;          //套接字
	int set_uid, set_gid;					/* config uid and gid */
	int socket_uid, socket_gid, socket_mode;

	/* runtime */
	struct fpm_child_s *children; //进程池中的全部子进程 doubly link list @ fpm_children.h line 20
	int running_children;
	int idle_spawn_rate;
	int warn_max_children;
#if 0
	int warn_lq;
#endif
	struct fpm_scoreboard_s *scoreboard; //worker pool 中worker的运行信息
	int log_fd;
	char **limit_extensions;

	/* for ondemand PM */
	struct fpm_event_s *ondemand_event;
	int socket_event_set;

#ifdef HAVE_FPM_ACL
	void *socket_acl;
#endif
};

src-path/sapi/fpm/fpm/fpm_children.h

/*abel: fpm child结构 doubly link list
 * 包含属于哪个worker_pool,自身pid,等信息*/
struct fpm_child_s {
	struct fpm_child_s *prev, *next;
	struct timeval started;
	struct fpm_worker_pool_s *wp;
	struct fpm_event_s ev_stdout, ev_stderr;
	int shm_slot_i;
	int fd_stdout, fd_stderr;
	void (*tracer)(struct fpm_child_s *);
	struct timeval slow_logged;
	int idle_kill;
	pid_t pid;
	int scoreboard_i;
	struct zlog_stream *log_stream;
};

启动

FPM启动函数在fpm_main.c中。源码比较长,其中比较重要的几个环节如下:

  ......
	//sapi startup @ line:1580
	php_printf("startup sapi_startup(&cgi_sapi_module)\n");
	sapi_startup(&cgi_sapi_module);
	
	......
	
	//sapi_module startup @ line:1770
	if (cgi_sapi_module.startup(&cgi_sapi_module) == FAILURE) {
#ifdef ZTS
		tsrm_shutdown();
#endif
		return FPM_EXIT_SOFTWARE;
	}
	
	......
	
	//fpm_init
	/*abel:FPM 初始化,处理php-fpm.conf*/
	ret = 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);
	
	......
	
	//FPM RUN
	fcgi_fd = fpm_run(&max_requests);
	
	//初始化fpm_request对象
	request = fpm_init_request(fcgi_fd);
	
	/*abel:
	 * zend engine 自行实现的try cache*/
	zend_first_try {
		/*abel:
		 * 进入抢占模型
		 * fcgi_accept_request 从Cgi中获得请求,一旦获得请求后,直接进行处理*/
	    while (EXPECTED(fcgi_accept_request(request) >= 0)) {
		    
		    ......
		    
		    /*request startup*/
				if (UNEXPECTED(php_request_startup() == FAILURE)) {
					fcgi_finish_request(request, 1);
					SG(server_context) = NULL;
					php_module_shutdown();
					return FPM_EXIT_SOFTWARE;
				}
				
				......
				
				/*abel:执行PHP脚本*/
				php_execute_script(&file_handle);
				
/*abel:cgi请求结束*/
fastcgi_request_done:
				
				......
				
				fpm_request_end();
				fpm_log_write(NULL);
				
				......
				
				fpm_request_executing();
				php_request_shutdown((void *) 0);
				
				......
				
		}//end while
		
		fcgi_destroy_request(request);
		fcgi_shutdown();
		
		......
		
	} zend_catch {
		exit_status = FPM_EXIT_SOFTWARE;
	} zend_end_try();

out:
	SG(server_context) = NULL;
	php_module_shutdown();

	if (parent) {
		sapi_shutdown();
	}
	
	

从源码中大致可以看出PHPFPM的生命周期如下
FPM-lifespan

fpm_run函数是整个main函数的核心,他负责通过复制自己创造子进程。创造的子进程数量符合php-fpm.conf中的设定的时候,主进程将进入一个fpm_event_loop的无限循环状态,监控并管理子进程的“生死”。

	//FPM RUN
	fcgi_fd = fpm_run(&max_requests);

所以以上代码的后续逻辑均是由子进程完成的。 来看一下php_run代码:

/*abel:
 * children: return listening socket
 *	parent: never return
 *  abel:子进程返回监听的socket
 *  父进程永远不返回  */
int fpm_run(int *max_requests) 
{
	struct fpm_worker_pool_s *wp;

	/* create initial children in all pools */
	for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
		int is_parent;

		/*abel:
		 * return 0:当前进程为子进程
		 * return 1:当前进程为父进程且fork子进程成功
		 * return 2:当前进程为父进程但fork失败*/
		is_parent = fpm_children_create_initial(wp);

		if (!is_parent) {
			goto run_child;
		}

		/* handle error */
		if (is_parent == 2) {
			fpm_pctl(FPM_PCTL_STATE_TERMINATING, FPM_PCTL_ACTION_SET);
			fpm_event_loop(1);
		}
	}

	/* run event loop forever */
	/*abel:
	 * 主进程陷入死循环*/
	fpm_event_loop(0);

/*abel:
 * 子进程需要做的事情
 * 1、初始化自身
 * 2、继承配置文件中的max_requests
 * 3、返回socket */
run_child: /* only workers reach this point */

	fpm_cleanups_run(FPM_CLEANUP_CHILD);
	*max_requests = fpm_globals.max_requests;
	return fpm_globals.listening_socket;
}

当程序进入到fpm_run阶段的时候,会根据不同的进程池fork出不同的子进程,方法fpm_children_create_initial为真正初始化子进程的方法,该方法主要返回三种状态:

  • 0:当前进程为子进程,直接进入run_child流程。
  • 1:当前进程为父进程且fork子进程成功,继续初始化下一个进程池。
  • 2:当前进程为父进程但fork失败,进程进入费撑场监听。

在return1的状态下,表明fork子进程成功,而创造子进程则主要靠fpm_children_make方法:

/*abel:根据进程池配置创建fpm子进程(根据Dynamic/ondemand/static关键字)
 * return 0 fork成功 (子进程)
 *        1 fork成功 (父进程)
 *        2 fork失败(父进程)
 * 在父进程中会得到1或者2结果
 * 1:
 *  1.1、父进程会将生成的child(fpm_child_s)对象的真实pid填充
 *  1.2、将该child放进自己的child对象池中
 * 在子进程中会得到0*/
int fpm_children_make(struct fpm_worker_pool_s *wp, int in_event_loop, int nb_to_spawn, int is_debug)
{
	pid_t pid;
	struct fpm_child_s *child;
	int max;
	static int warned = 0;

	if (wp->config->pm == PM_STYLE_DYNAMIC) {
		if (!in_event_loop) { /* starting */
			max = wp->config->pm_start_servers;
		} else {
			max = wp->running_children + nb_to_spawn;
		}
	} else if (wp->config->pm == PM_STYLE_ONDEMAND) {
		if (!in_event_loop) { /* starting */
			max = 0; /* do not create any child at startup */
		} else {
			max = wp->running_children + nb_to_spawn;
		}
	} else { /* PM_STYLE_STATIC */
		max = wp->config->pm_max_children;
	}

	/*
	 * fork children while:
	 *   - fpm_pctl_can_spawn_children : FPM is running in a NORMAL state (aka not restart, stop or reload)
	 *   - wp->running_children < max  : there is less than the max process for the current pool
	 *   - (fpm_global_config.process_max < 1 || fpm_globals.running_children < fpm_global_config.process_max):
	 *     if fpm_global_config.process_max is set, FPM has not fork this number of processes (globaly)
	 */
	while (fpm_pctl_can_spawn_children() && wp->running_children < max && (fpm_global_config.process_max < 1 || fpm_globals.running_children < fpm_global_config.process_max)) {

		warned = 0;
		/*abel:准备好一个child(fpm_child_s:doubly link list)结构*/
		child = fpm_resources_prepare(wp);

		/*abel:不具备条件直接return错误*/
		if (!child) {
			return 2;
		}

		/*abel: self clone*/
		pid = fork();

		switch (pid) {
            /*abel:fork success
             * 标记自己为子进程 并return成功*/
			case 0 :
				fpm_child_resources_use(child);
				fpm_globals.is_child = 1;
				fpm_child_init(wp);
				return 0;
			/*abel:fork failed
			 * */
			case -1 :
				zlog(ZLOG_SYSERROR, "fork() failed");

				fpm_resources_discard(child);
				return 2;

			default :
				child->pid = pid;
				/*abel:
				 *  tv->tv_sec = ts.tv_sec;
				 *  tv->tv_usec = ts.tv_nsec / 1000;*/
				fpm_clock_get(&child->started);
				/*??abel:
				 * 将child装在进父进程监控中
				 * fpm_stdio_parent_use_pipes(child);
				 * fpm_child_link(child);*/
				fpm_parent_resources_use(child);

				zlog(is_debug ? ZLOG_DEBUG : ZLOG_NOTICE, "[pool %s] child %d started", wp->config->name, (int) pid);
		}

	}

	if (!warned && fpm_global_config.process_max > 0 && fpm_globals.running_children >= fpm_global_config.process_max) {
               if (wp->running_children < max) {
                       warned = 1;
                       zlog(ZLOG_WARNING, "The maximum number of processes has been reached. Please review your configuration and consider raising 'process.max'");
               }
	}

	return 1; /* we are done */
}

再循环完所有的进程池对象链之后,主进程直接进入fpm_event_loop,所以如果是当前是主进程的时候,整个进程会阻塞在fpm_run这一步。之后的处理请求逻辑全部在子进程中完成。

处理请求

子进程会进入到一个阻塞的等待请求的状态中:

while (EXPECTED(fcgi_accept_request(request) >= 0)) {
}

每当请求到来的时候,将会按照如下步骤处理请求:

  • php_request_startup 准备好SAPI的各种资源
  • php_execute_script 执行PHP脚本
  • php_request_shutdown 回收资源,并输出内容

填补一下上面的生命周期:
FPM-Lifespan

请求自身的生命周期

在获取到请求后,请求本身会转化成一个fpm_request,其本身也会存在几种状态

void fpm_request_accepting();				/* hanging in accept() */
void fpm_request_reading_headers();			/* start reading fastcgi request from very first byte */
void fpm_request_info();					/* not a stage really but a point in the php code, where all request params have become known to sapi */
void fpm_request_executing();				/* the script is executing */
void fpm_request_end(void);				/* request ended: script response have been sent to web server */
void fpm_request_finished();				/* request processed: cleaning current request */

注释已经解释的很明白了,除了fpm_request_info这个函数外,其他都是fpm_request的各个阶段,也就是fpm request的生命周期:

  • request accepting 等待请求阶段
  • request reading headers 读取头信息阶段
  • request executing 执行阶段
  • request end 请求结束阶段:发送内容到web容器
  • request finished 请求完成,清理request

在初始化fpm_request时,会将其中的三个执行阶段hook到对象中:

  • accepting
  • reading headers
  • finished
request = fpm_init_request(fcgi_fd);
static fcgi_request *fpm_init_request(int listen_fd) {
	fcgi_request *req = fcgi_init_request(listen_fd,
		fpm_request_accepting,
		fpm_request_reading_headers,
		fpm_request_finished);
	return req;
}

所以,一个fastCGI请求的生命周期如下:
FastCGI request lifespan

Categories:

Updated: