php-fpm源码浅析

php-src/sapi/fpm/fpm/fpm.c关于fpm初始化关键代码

        0 > fpm_php_init_main()           ||
	    0 > fpm_stdio_init_main()         ||
	    0 > fpm_conf_init_main(test_conf, force_daemon) ||
	    0 > fpm_unix_init_main()          ||
	    0 > fpm_scoreboard_init_main()    ||
	    0 > fpm_pctl_init_main()          ||
	    0 > fpm_env_init_main()           ||
	    0 > fpm_signals_init_main()       ||
	    0 > fpm_children_init_main()      ||
	    0 > fpm_sockets_init_main()       ||
	    0 > fpm_worker_pool_init_main()   ||
	    0 > fpm_event_init_main()

fpm_unix_init_main代码重点:

  • daemonize->fork新进程,setsid设置新会话;
  • php-fpm配置入fpm_worker_pool_s

fpm_scoreboard_init_main代码重点:

  • 共享内存中为每个pool维护一个fpm_scoreboard_s结构,其成员fpm_scoreboard_proc_s procs[] 指针数组保存该pool下所有工作进程信息的数据;每个fpm_scoreboard_proc_s指针(生效的)指向一个真实的fpm_scoreboard_proc_s*结构

fpm_worker_pool_init_main重点:

  • 清除fpm_worker_all_pools;如果是master进程,清除每个fpm_worker_pool_s中的fpm_scoreboard_s结构

关键!重点!

php-src/sapi/fpm/fpm/fpm.c中fpm_run

/*	children: return listening socket
	parent: never return */
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;

		is_parent = fpm_children_create_initial(wp);//TO-DO doing

		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 */
	fpm_event_loop(0);

run_child: /* only workers reach this point */

	fpm_cleanups_run(FPM_CLEANUP_CHILD);

	*max_requests = fpm_globals.max_requests;
	return fpm_globals.listening_socket;
}
/* }}} */

php-src/sapi/fpm/fpm/fpm.c:fpm_run->
php-src/sapi/fpm/fpm/fpm_children.c:fpm_children_create_initial

int fpm_children_create_initial(struct fpm_worker_pool_s *wp) /* {{{ */
{
	if (wp->config->pm == PM_STYLE_ONDEMAND) {//根据具体需求fork出worker进程,即:有请求来了,到wp->scoreboard查看有没有空闲的子进程,若满负荷运转,则fork 出新进程;否则让空闲子进程处理请求
		wp->ondemand_event = (struct fpm_event_s *)malloc(sizeof(struct fpm_event_s));

		if (!wp->ondemand_event) {
			zlog(ZLOG_ERROR, "[pool %s] unable to malloc the ondemand socket event", wp->config->name);
			// FIXME handle crash
			return 1;
		}

		memset(wp->ondemand_event, 0, sizeof(struct fpm_event_s));
		fpm_event_set(wp->ondemand_event, wp->listening_socket, FPM_EV_READ | FPM_EV_EDGE, fpm_pctl_on_socket_accept, wp);
		wp->socket_event_set = 1;
		fpm_event_add(wp->ondemand_event, 0);

		return 1;
	}
	return fpm_children_make(wp, 0 /* not in event loop yet */, 0, 1);
}

wp->config->pm==PM_STYLE_ONDEMAND:

  • 按需fork worker,即:有请求来了,到wp->scoreboard查看有没有空闲的子进程,若满负荷运转,则fork 出新进程;否则让空闲子进程处理请求

fpm_event_set重点:
给wp->ondemand_event结构赋值

	wp->ondemand_event->fd = wp->listening_socket;
	wp->ondemand_event->callback = fpm_pctl_on_socket_accept;
	wp->ondemand_event->arg = FPM_EV_READ | FPM_EV_EDGE;
	wp->ondemand_event->flags = wp;

fpm_event_add重点:

  • 把新wp->ondemand_event事件结构加入到事件链表中,排重,头插法,这里使用epoll举例:
/*
 * Add a FD to the fd set
 */
static int fpm_event_epoll_add(struct fpm_event_s *ev) /* {{{ */
{
	struct epoll_event e;

	/* fill epoll struct */
#if SIZEOF_SIZE_T == 4
	/* Completely initialize event data to prevent valgrind reports */
	e.data.u64 = 0;
#endif
	e.events = EPOLLIN;
	e.data.fd = ev->fd;
	e.data.ptr = (void *)ev;

	if (ev->flags & FPM_EV_EDGE) {
		e.events = e.events | EPOLLET;
	}

	/* add the event to epoll internal queue */
	if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ev->fd, &e) == -1) {
		zlog(ZLOG_ERROR, "epoll: unable to add fd %d", ev->fd);
		return -1;
	}

	/* mark the event as registered */
	ev->index = ev->fd;
	return 
  • 这里针对ev->fd(wp->listening_socket这个句柄,fpm pool的监听端口),新增(EPOLL_CTL_ADD)一个EPOLLIN事件,且边缘触发(EPOLLET),事件响应函数是fpm_pctl_on_socket_accept,放在epollfd对象中;

php-src/sapi/fpm/fpm/fpm.c:fpm_run->
php-src/sapi/fpm/fpm/fpm_children.c:fpm_children_create_initial->
php-src/sapi/fpm/fpm/fpm_process_ctl.c:fpm_pctl_on_socket_accept:
wp->listening_socket监听可读事件的响应函数:

void fpm_pctl_on_socket_accept(struct fpm_event_s *ev, short which, void *arg) /* {{{ */
{
	struct fpm_worker_pool_s *wp = (struct fpm_worker_pool_s *)arg;
	struct fpm_child_s *child;


	if (fpm_globals.parent_pid != getpid()) {
		/* prevent a event race condition when child process
		 * have not set up its own event loop */
		return;
	}

	wp->socket_event_set = 0;

/*	zlog(ZLOG_DEBUG, "[pool %s] heartbeat running_children=%d", wp->config->name, wp->running_children);*/

	if (wp->running_children >= wp->config->pm_max_children) {
		if (!wp->warn_max_children) {
			fpm_scoreboard_update(0, 0, 0, 0, 0, 1, 0, FPM_SCOREBOARD_ACTION_INC, wp->scoreboard);
			zlog(ZLOG_WARNING, "[pool %s] server reached max_children setting (%d), consider raising it", wp->config->name, wp->config->pm_max_children);
			wp->warn_max_children = 1;
		}

		return;
	}

	for (child = wp->children; child; child = child->next) {
		/* if there is at least on idle child, it will handle the connection, stop here */
		if (fpm_request_is_idle(child)) {
			return;
		}
	}

	wp->warn_max_children = 0;
	fpm_children_make(wp, 1, 1, 1);

	if (fpm_globals.is_child) {
		return;
	}

	zlog(ZLOG_DEBUG, "[pool %s] got accept without idle child available .... I forked", wp->config->name);
}
  • 参数列表,是在php-src/sapi/fpm/fpm/events/epoll.c->fpm_event_epoll_wait中fpm_event_fire(epollfds[i].data.ptr)中传入;
  • 从之前的分析得出,该指针指向的是fpm_event_s结构,取值为
	wp->ondemand_event->fd = wp->listening_socket;
	wp->ondemand_event->callback = fpm_pctl_on_socket_accept;
	wp->ondemand_event->arg = FPM_EV_READ | FPM_EV_EDGE;
	wp->ondemand_event->flags = wp;

所以参数列表值分别为:fpm_event_s *ev = wp->ondemand_event, short which=FPM_EV_READ(php-src/sapi/fpm/fpm/fpm_events.c->fpm_event_add()), void *arg = FPM_EV_READ | FPM_EV_EDGE

  • 整体流程:监听到绑定的端口上有新的请求时,若当前运行的子进程数量(running_children)不小于pool中配置的上限(wp->config->pm_max_children), 则给该pool的记分板(fpm_scoreboard_s)中的max_children_reached数值加1,表示又到达了进程数量上限;若未到达上限,则查找出空闲的子进程对应的fpm_scoreboard_proc_s结构,设置其request_stage值为FPM_REQUEST_ACCEPTING(TODO);如果没有到上限,并且没有空闲的子进程,则fork出新的worker进程;

php-src/sapi/fpm/fpm/fpm.c:fpm_run->
php-src/sapi/fpm/fpm/fpm_children.c:fpm_children_create_initial->
php-src/sapi/fpm/fpm/fpm_children.c:fpm_children_make
由新的daemon master 进程 fork出worker进程

/*
	 * 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;
		child = fpm_resources_prepare(wp);

		if (!child) {
			return 2;
		}

		pid = fork();

		switch (pid) {

			case 0 :
				fpm_child_resources_use(child);
				fpm_globals.is_child = 1;
				fpm_child_init(wp);
				return 0;

			case -1 :
				zlog(ZLOG_SYSERROR, "fork() failed");

				fpm_resources_discard(child);
				return 2;

			default :
				child->pid = pid;
				fpm_clock_get(&child->started);
				fpm_parent_resources_use(child);

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

	}

**child = fpm_resources_prepare(wp)**重点:

  • fpm_scoreboard_proc_alloc函数获取fpm_scoreboard_proc_s *procs[]数组中没被使用的元素下标,赋值给即将fork的worker进程的scoreboard_i;

fpm_child_resources_use重点(已fork):

  • 根据worker进程的scoreboard_i,从procs数数组中获取对应的元素结构,并赋值新worker进程的pid(进程pid)和start_epoch(开始时间)

fpm_child_init重点:

if (0 > fpm_stdio_init_child(wp)  ||
	    0 > fpm_log_init_child(wp)    ||
	    0 > fpm_status_init_child(wp) ||
	    0 > fpm_unix_init_child(wp)   ||
	    0 > fpm_signals_init_child()  ||
	    0 > fpm_env_init_child(wp)    ||
	    0 > fpm_php_init_child(wp)) {

		zlog(ZLOG_ERROR, "[pool %s] child failed to initialize", wp->config->name);
		exit(FPM_EXIT_SOFTWARE);
	}

php-src/sapi/fpm/fpm/fpm.c:fpm_run->
php-src/sapi/fpm/fpm/fpm_events.c:fpm_event_loop:

	fpm_event_set(&signal_fd_event, fpm_signals_get_fd(), FPM_EV_READ, &fpm_got_signal, NULL);
	fpm_event_add(&signal_fd_event, 0);

	/* add timers */
	if (fpm_globals.heartbeat > 0) {
		fpm_pctl_heartbeat(NULL, 0, NULL);
	}

	if (!err) {
		fpm_pctl_perform_idle_server_maintenance_heartbeat(NULL, 0, NULL);

		zlog(ZLOG_DEBUG, "%zu bytes have been reserved in SHM", fpm_shm_get_size_allocated());
		zlog(ZLOG_NOTICE, "ready to handle connections");

#ifdef HAVE_SYSTEMD
		fpm_systemd_heartbeat(NULL, 0, NULL);
#endif
	}

	while (1) {
		struct fpm_event_queue_s *q, *q2;
		struct timeval ms;
		struct timeval tmp;
		struct timeval now;
		unsigned long int timeout;
		int ret;

		/* sanity check */
		if (fpm_globals.parent_pid != getpid()) {
			return;
		}

		fpm_clock_get(&now);
		timerclear(&ms);

		/* search in the timeout queue for the next timer to trigger */
		q = fpm_event_queue_timer;
		while (q) {
			if (!timerisset(&ms)) {
				ms = q->ev->timeout;
			} else {
				if (timercmp(&q->ev->timeout, &ms, <)) {
					ms = q->ev->timeout;
				}
			}
			q = q->next;
		}

		/* 1s timeout if none has been set */
		if (!timerisset(&ms) || timercmp(&ms, &now, <) || timercmp(&ms, &now, ==)) {
			timeout = 1000;
		} else {
			timersub(&ms, &now, &tmp);
			timeout = (tmp.tv_sec * 1000) + (tmp.tv_usec / 1000) + 1;
		}

		ret = module->wait(fpm_event_queue_fd, timeout);

		/* is a child, nothing to do here */
		if (ret == -2) {
			return;
		}

		if (ret > 0) {
			zlog(ZLOG_DEBUG, "event module triggered %d events", ret);
		}

		/* trigger timers */
		q = fpm_event_queue_timer;
		while (q) {
			fpm_clock_get(&now);
			if (q->ev) {
				if (timercmp(&now, &q->ev->timeout, >) || timercmp(&now, &q->ev->timeout, ==)) {
					fpm_event_fire(q->ev);
					/* sanity check */
					if (fpm_globals.parent_pid != getpid()) {
						return;
					}
					if (q->ev->flags & FPM_EV_PERSIST) {
						fpm_event_set_timeout(q->ev, now);
					} else { /* delete the event */
						q2 = q;
						if (q->prev) {
							q->prev->next = q->next;
						}
						if (q->next) {
							q->next->prev = q->prev;
						}
						if (q == fpm_event_queue_timer) {
							fpm_event_queue_timer = q->next;
							if (fpm_event_queue_timer) {
								fpm_event_queue_timer->prev = NULL;
							}
						}
						q = q->next;
						free(q2);
						continue;
					}
				}
			}
			q = q->next;
		}
	}
  • 添加信号事件监听;
  • 添加定时器,定期遍历所有的fpm_worker_pool_s结构,及其fpm_child_s结构(即遍历所有worker进程对应的结构),并获取对应的子进程信息(空闲/繁忙),进而管理子进程的数量(fork/kill),更新对应的fpm_scoreboard_s结构(共享内存中),以此维护管理fpm进程

request = fpm_init_request(fcgi_fd);

  • fpm监听的端口,封装成fcgi_request结构;
  • 针对该请求体,添加hook,响应函数:on_accept,on_read,on_close;

php-src/sapi/fpm/fpm/fpm_main.c:main
worker进程监听请求,解析请求并处理,处理结果输出并结束此次请求,循环处理

zend_first_try {
		while (EXPECTED(fcgi_accept_request(request) >= 0)) {
			char *primary_script = NULL;
			request_body_fd = -1;
			SG(server_context) = (void *) request;
			init_request_info();

			fpm_request_info();

			/* request startup only after we've done all we can to
			 *            get path_translated */
			if (UNEXPECTED(php_request_startup() == FAILURE)) {
				fcgi_finish_request(request, 1);
				SG(server_context) = NULL;
				php_module_shutdown();
				return FPM_EXIT_SOFTWARE;
			}

			/* check if request_method has been sent.
			 * if not, it's certainly not an HTTP over fcgi request */
			if (UNEXPECTED(!SG(request_info).request_method)) {
				goto fastcgi_request_done;
			}

			if (UNEXPECTED(fpm_status_handle_request())) {
				goto fastcgi_request_done;
			}

			/* If path_translated is NULL, terminate here with a 404 */
			if (UNEXPECTED(!SG(request_info).path_translated)) {
				zend_try {
					zlog(ZLOG_DEBUG, "Primary script unknown");
					SG(sapi_headers).http_response_code = 404;
					PUTS("File not found.\n");
				} zend_catch {
				} zend_end_try();
				goto fastcgi_request_done;
			}

			if (UNEXPECTED(fpm_php_limit_extensions(SG(request_info).path_translated))) {
				SG(sapi_headers).http_response_code = 403;
				PUTS("Access denied.\n");
				goto fastcgi_request_done;
			}

			/*
			 * have to duplicate SG(request_info).path_translated to be able to log errrors
			 * php_fopen_primary_script seems to delete SG(request_info).path_translated on failure
			 */
			primary_script = estrdup(SG(request_info).path_translated);

			/* path_translated exists, we can continue ! */
			if (UNEXPECTED(php_fopen_primary_script(&file_handle) == FAILURE)) {
				zend_try {
					zlog(ZLOG_ERROR, "Unable to open primary script: %s (%s)", primary_script, strerror(errno));
					if (errno == EACCES) {
						SG(sapi_headers).http_response_code = 403;
						PUTS("Access denied.\n");
					} else {
						SG(sapi_headers).http_response_code = 404;
						PUTS("No input file specified.\n");
					}
				} zend_catch {
				} zend_end_try();
				/* we want to serve more requests if this is fastcgi
				 * so cleanup and continue, request shutdown is
				 * handled later */

				goto fastcgi_request_done;
			}

			fpm_request_executing();

			php_execute_script(&file_handle);

fastcgi_request_done:
			if (EXPECTED(primary_script)) {
				efree(primary_script);
			}

			if (UNEXPECTED(request_body_fd != -1)) {
				close(request_body_fd);
			}
			request_body_fd = -2;

			if (UNEXPECTED(EG(exit_status) == 255)) {
				if (CGIG(error_header) && *CGIG(error_header)) {
					sapi_header_line ctr = {0};

					ctr.line = CGIG(error_header);
					ctr.line_len = strlen(CGIG(error_header));
					sapi_header_op(SAPI_HEADER_REPLACE, &ctr);
				}
			}

			fpm_request_end();
			fpm_log_write(NULL);

			efree(SG(request_info).path_translated);
			SG(request_info).path_translated = NULL;

			php_request_shutdown((void *) 0);

			requests++;
			if (UNEXPECTED(max_requests && (requests == max_requests))) {
				fcgi_request_set_keep(request, 0);
				fcgi_finish_request(request, 0);
				break;
			}
			/* end of fastcgi loop */
		}
		fcgi_destroy_request(request);
		fcgi_shutdown();

		if (cgi_sapi_module.php_ini_path_override) {
			free(cgi_sapi_module.php_ini_path_override);
		}
		if (cgi_sapi_module.ini_entries) {
			free(cgi_sapi_module.ini_entries);
		}
	} zend_catch {
		exit_status = FPM_EXIT_SOFTWARE;
	} zend_end_try();

out:

	SG(server_context) = NULL;
	php_module_shutdown();

	if (parent) {
		sapi_shutdown();
	}

#ifdef ZTS
	tsrm_shutdown();
#endif

	return exit_status;
}

php-src/sapi/fpm/fpm/fpm_main.c:main->
php-src/main/fastcgi.c:fcgi_accept_request
子进程从监听端口获取数据,使用了锁,并执行了请求体的hook响应函数:on_accept,on_read,on_close;;

php-src/sapi/fpm/fpm/fpm_main.c:main->
php-src/main/fastcgi.c:fcgi_accept_request->
php-src/main/fastcgi.c:fcgi_read_request
读取请求数据,根据fastcgi协议来解析数据

init_request_info()
fpm_request_info()
php_request_startup
fpm_request_executing();
php_execute_script(&file_handle)
fpm_request_end()
php_request_shutdown
fcgi_request_set_keep
fcgi_finish_request

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值