|
PHP内核分析-FPM进程管理
PHP内核分析-FPM进程管理
杨通
贝壳产品技术
贝壳产品技术 “贝壳产品技术公众号”作为贝壳官方产品技术号,致力打造贝壳产品、技术干货分享平台,面向互联网/O2O开发/产品从业者,每周推送优质产品技术文章、技术沙龙活动及招聘信息等。欢迎大家关注我们。 242篇内容
2018年06月01日 19:35
点击上方蓝字关注杨通,新房研发部研发工程师,负责新房Link研发工作。2015年加入链家网(现贝壳找房),曾在大数据、新房研发部等部门工作。1. 启动过程 启动模式从代码执行角度来讲,PHP是一个脚本解析器,我们可以把它理解为一个普通程序,输入的是PHP脚本,输出的是执行结果。了解PHP的内核之前,我们先看图来介绍PHP体系的整体架构。我们看到处于PHP架构内最顶层的就是SAPI层(SAPI是Server Application Programming Interface)的缩写。PHP通过实现SAPI接口来和外部交互。PHP的源码中集成了我们能用到的各种模式,比如:apache2handler,cgi,cli,embed,fpm,litespeed,phpdbg等。具体可以查看sapi目录。在我们实际工作中,最常见是的CLI和FPM模式,下面的内容我们将围绕FPM模式进行分析。注1:本博客中所有的代码都是基于PHP7.2.5注2:所有的目录都是相对于PHP代码的根目录 启动过程FPM(FastCGI Process Manager)是PHP FastCGI运行模式的一个进程管理器,它的核心功能就是管理PHP进程。概括来讲,FPM的实现就是创建一个Master进程,在Master进程中创建并监听socket,然后fork出多个Worker进程。各个Worker进程阻塞在accept方法处,有请求到达时开始读取请求数据,然后开始处理请求并返回。Worker进程处理当前请求时不会再接收其他请求,也就是说FPM的Worker进程同时只能响应一个请求,处理完当前请求后才开始accept下一个请求。详细来讲,FPM的Master启动过程可以分为如下几步:(具体可参考源码sapi/fpm/fpm/fpm_main.c:1570L)执行sapi_startup方法,实际上就是将全局变量sapi_module设置为cgi_sapi_module,方法细节参考:main/SAPI.c:78L执行cgi_sapi_module.startup方法(sapi/fpm/fpm/fpm_main.c:1810L),追踪下去我们发现执行的是php_module_startup方法(sapi/fpm/fpm/fpm_main.c:1810L),方法细节参考:main/main.c:2083L执行fpm_init方法,初始化当前的FPM配置,方法细节参考:sapi/fpm/fpm/fpm.c:46L,初始化过程有几个关键步骤:执行fpm_conf_init_main方法,其实就是解析配置文件,保存到fpm_worker_all_pools这个结构中,我们会在下节解析这个结构;执行fpm_scoreboard_init_main方法,为每个Worker Pool生成一个fpm_worker_pool_s结构,用来存储当前Worker Pool的运行时信息,同时为每个Pool里面的Worker进程创建一个scoreboard保存这个进程的运行时信息;执行fpm_signals_init_main方法,主要是使用socketpair方法创建一个双工Socket通道,用于处理外部发送给Master进程的信号,同时初始化信号的回调方法;执行fpm_sockets_init_main方法,为每个Worker Pool创建Socket;执行fpm_event_init_main方法,初始化事件管理机制,用于管理IO和定时事件;FPM的事件管理是基于kqueue、epoll、poll、select等实现的,在解析完日志文件后会调用fpm_event_pre_init方法确定使用哪种方式来管理。执行fpm_run方法,创建Worker进程,创建完成后Master进程阻塞fpm_event_loop,而Worker进程会退出fpm_run,然后阻塞在fcgi_accept_request方法,等待请求的到来。我们来看下fpm_run方法的具体实现: 1/* children: return listening socket 2 parent: never return */ 3int fpm_run(int *max_requests) /* {{{ */ 4{ 5 struct fpm_worker_pool_s *wp; 6 /* create initial children in all pools */ 7 for (wp = fpm_worker_all_pools; wp; wp = wp->next) { 8 int is_parent; 9 is_parent = fpm_children_create_initial(wp);10 //fpm_children_create_initial方法用来真正创建Worker进程,11 //逻辑是:如果是ONDEMAND模式管理,Master并不会创建Worker,而是监听Worker Pool的端口号,当端口可读时回调fpm_pctl_on_socket_accept方法创建Worker进程12 //否则调用fpm_children_make方法创建Worker进程,返回创建结果13 //Worker进程直接goto到下面14 if (!is_parent) {15 goto run_child;16 } 17 /* handle error */18 if (is_parent == 2) {19 fpm_pctl(FPM_PCTL_STATE_TERMINATING, FPM_PCTL_ACTION_SET);20 fpm_event_loop(1);21 } 22 } 23 /* run event loop forever */24 fpm_event_loop(0); //创建完成后Master进程阻塞在这里25run_child: /* only workers reach this point */26 fpm_cleanups_run(FPM_CLEANUP_CHILD);27 *max_requests = fpm_globals.max_requests;28 return fpm_globals.listening_socket; //Worker进程返回main方法,继续执行后面代码29}30/* }}} */FPM内部定义了Worker进程在处理请求的整个过程,具体可以分为以下六个阶段:FPM_REQUEST_ACCEPTING :等待请求阶段FPM_REQUEST_READING_HEADERS :读取fastcgi传递过来的请求头,比较重要FPM_REQUEST_INFO :将当前请求URI,METHOD,Query String,Auth User等信息写入Worker进程的ScoreboardFPM_REQUEST_EXECUTING :请求执行FPM_REQUEST_END :只有定义该状态,没有实际的方法FPM_REQUEST_FINISHED :请求结束阶段,同时更新Scoreboard的最后处理时间2. 进程管理Master进程在创建完所有的Worker进程后,并不会监听端口处理请求(Worker进程管理方式是ONDEMANDD模式除外),而是阻塞在事件循环中。在运行期间,Master进程会通过共享内存获取Worker进程的运行状态,比如当前状态,已经处理的请求数等。当Master要杀掉一个Worker进程时,会向Worker进程发送信号。 数据结构FPM能同时监听多个端口,在FPM内部每个端口对应一个Worker Pool,每个Worker Pool中可以创建多个Worker进程,其关系如下:Master进程管理Worker Pool和Worker进程主要是通过如下的数据结构来实现的: 1顶级结构: 2struct fpm_worker_pool_s { 3 struct fpm_worker_pool_s *next; 指向下一个Worker Pool 4 struct fpm_worker_pool_config_s *config; 指向当前Worker Pool的配置结构 5 char *user, *home; 6 enum fpm_address_domain listen_address_domain; 7 int listening_socket; 8 int set_uid, set_gid; 9 int socket_uid, socket_gid, socket_mode;10 /* runtime */11 struct fpm_child_s *children; 指向当前Pool的所有Child,双向链表,参考fpm_children.h12 int running_children; 当前正在执行请求的Worker的个数13 int idle_spawn_rate; Worker增加时的增加速度,Worker进程创建时用到14 int warn_max_children;15#if 016 int warn_lq;17#endif18 struct fpm_scoreboard_s *scoreboard; 当前Pool的scoreboard,见下面结构19 int log_fd;20 char **limit_extensions;21 /* for ondemand PM */22 //当Worker管理模式为ONDEMEND模式时Master进程关注的事件,见fpm_children_create_initial方法23 struct fpm_event_s *ondemand_event; 24 int socket_event_set;25#ifdef HAVE_FPM_ACL26 void *socket_acl;27#endif28};29Worker Pool的scoreboard,主要记录当前Pool的Worker运行状态30struct fpm_scoreboard_s {31 union {32 atomic_t lock; //因为有多个进程会写,所以需要有个锁保护33 char dummy[16];34 }; 35 char pool[32];36 int pm; 37 time_t start_epoch;38 int idle;39 int active;40 int active_max;41 unsigned long int requests;42 unsigned int max_children_reached;43 int lq; 44 int lq_max;45 unsigned int lq_len;46 unsigned int nprocs;47 int free_proc;48 unsigned long int slow_rq;49 struct fpm_scoreboard_proc_s *procs[]; //指向所有的Worker进程的scoreboard50};51Worker进程的scoreboard,主要记录当前Worker处理请求相关的信息52struct fpm_scoreboard_proc_s {53 union {54 atomic_t lock;55 char dummy[16];56 };57 int used;58 time_t start_epoch;59 pid_t pid;60 unsigned long requests;61 enum fpm_request_stage_e request_stage;62 struct timeval accepted;63 struct timeval duration;64 time_t accepted_epoch;65 struct timeval tv;66 char request_uri[128];67 char query_string[512];68 char request_method[16];69 size_t content_length; /* used with POST only */70 char script_filename[256];71 char auth_user[32];72#ifdef HAVE_TIMES73 struct tms cpu_accepted;74 struct timeval cpu_duration;75 struct tms last_request_cpu;76 struct timeval last_request_cpu_duration;77#endif78 size_t memory;79};所以用一张图来表示的话,所有的结构关系如下:3. 事件机制FPM的Master进程虽然从来不处理实际的请求,但需要要处理外部信号、定时任务等。在ONDEMAND模式下也需要监听Socket端口来创建新的Worker进程。所以FPM内部也实现了简单的事件机制来管理所有的内外部事件。具体结构如下:sapi/fpm/fpm/fpm_events.h:32L1struct fpm_event_module_s {2 const char *name; //Module名称:epoll、kqueue等3 int support_edge_trigger; //是否支持边缘触发,目前只有epoll和kqueue支持4 int (*init)(int max_fd); //监听的最大fd数量5 int (*clean)(void); //下面是几个回调方法,每个模块都有对应的实现6 int (*wait)(struct fpm_event_queue_s *queue, unsigned long int timeout);7 int (*add)(struct fpm_event_s *ev);8 int (*remove)(struct fpm_event_s *ev);9};FPM内部定义了两种事件,分别是Timer事件和FD事件,二者公用同一个结构存储。在Master进程开始之前,会定义两个队列,分别为存储Timer事件和FD事件,具体参考如下代码: 1struct fpm_event_s { 2 int fd; //只有在FD类型事件才使用 3 struct timeval timeout; //Timer到期时间 4 struct timeval frequency; 5 void (*callback)(struct fpm_event_s *, short, void *); //回调方法 6 void *arg; 7 int flags; 8 int index; 9 short which; //关注的事件10};11//事件队列12typedef struct fpm_event_queue_s {13 struct fpm_event_queue_s *prev;14 struct fpm_event_queue_s *next;15 struct fpm_event_s *ev;16} fpm_event_queue;17//初始化两个事件队列18static struct fpm_event_queue_s *fpm_event_queue_timer = NULL;19static struct fpm_event_queue_s *fpm_event_queue_fd = NULL;我们打开sapi/fpm/fpm/events目录,我们能看到里面实现了对各种事件管理机制的封装,以epoll.c为例: 1static int fpm_event_epoll_init(int max); 2static int fpm_event_epoll_clean(); 3static int fpm_event_epoll_wait(struct fpm_event_queue_s *queue, unsigned long int timeout); 4static int fpm_event_epoll_add(struct fpm_event_s *ev); 5static int fpm_event_epoll_remove(struct fpm_event_s *ev); 6//绑定回调方法 7static struct fpm_event_module_s epoll_module = { 8 .name = "epoll", 9 .support_edge_trigger = 1,10 .init = fpm_event_epoll_init,11 .clean = fpm_event_epoll_clean,12 .wait = fpm_event_epoll_wait,13 .add = fpm_event_epoll_add,14 .remove = fpm_event_epoll_remove,15};我们之前提过,在FPM启动阶段读取完配置后,会调用fpm_event_pre_init方法初始化Master进程使用的实际的事件机制。具体参考:sapi/fpm/fpm/fpm_events.c:237L 外部信号Master在fpm_signals_init_main阶段会使用socketpair(参考:socketpair)方法创建一个通道,并且设置了对外部信号的处理方法,参考下面代码: 1int fpm_signals_init_main() /* {{{ */ 2{ 3 struct sigaction act; 4 //创建socket通道 5 if (0 > socketpair(AF_UNIX, SOCK_STREAM, 0, sp)) { 6 zlog(ZLOG_SYSERROR, "failed to init signals: socketpair()"); 7 return -1; 8 } 9 memset(&act, 0, sizeof(act));10 act.sa_handler = sig_handler;11 sigfillset(&act.sa_mask);12 //设置handler的处理方法13 if (0 > sigaction(SIGTERM, &act, 0) ||14 0 > sigaction(SIGINT, &act, 0) ||15 0 > sigaction(SIGUSR1, &act, 0) ||16 0 > sigaction(SIGUSR2, &act, 0) ||17 0 > sigaction(SIGCHLD, &act, 0) ||18 0 > sigaction(SIGQUIT, &act, 0)) {19 zlog(ZLOG_SYSERROR, "failed to init signals: sigaction()");20 return -1; 21 } 22 return 0;23}24具体的处理方法逻辑如下:25static void sig_handler(int signo) /* {{{ */26{27 static const char sig_chars[NSIG + 1] = {28 [SIGTERM] = 'T',29 [SIGINT] = 'I',30 [SIGUSR1] = '1',31 [SIGUSR2] = '2',32 [SIGQUIT] = 'Q',33 [SIGCHLD] = 'C'34 };35 char s;36 int saved_errno;37 if (fpm_globals.parent_pid != getpid()) {38 /* prevent a signal race condition when child process39 have not set up it's own signal handler yet */40 return;41 }42 saved_errno = errno;43 s = sig_chars[signo];44 zend_quiet_write(sp[1], &s, sizeof(s)); //收到信号,并将信号转化为T/I等指令写入之前创建的通道。write(sp[1])45 errno = saved_errno;46}在Master进程调用fpm_event_loop进入事件循环时,会将上面创建的sp[0]fd上面的可读事件加入到事件监听模块,参考:sapi/fpm/fpm/fpm_events.c:355L1 fpm_event_set(&signal_fd_event, fpm_signals_get_fd(), FPM_EV_READ, &fpm_got_signal, NULL);2 fpm_event_add(&signal_fd_event, 0); 本来进程是可以处理外部信号的,但是为什么非要搞这么一个通道来将信号转化为socket事件?我猜是为了在事件处理这一侧打平,也就是说统一用FPM实现的事件机制来管理所有的事件。 Socket事件只有当FPM管理Worker进程使用ONDEMAND模式时Master进程才会监听外部来的请求,具体看代码: 1int fpm_children_create_initial(struct fpm_worker_pool_s *wp) 2{ 3 if (wp->config->pm == PM_STYLE_ONDEMAND) { //如果是ONDEMAND模式 4 //初始化事件 5 wp->ondemand_event = (struct fpm_event_s *)malloc(sizeof(struct fpm_event_s)); 6 if (!wp->ondemand_event) { 7 zlog(ZLOG_ERROR, "[pool %s] unable to malloc the ondemand socket event", wp->config->name); 8 // FIXME handle crash 9 return 1;10 } 11 memset(wp->ondemand_event, 0, sizeof(struct fpm_event_s));12 //将时间的回调方法设置为fpm_pctl_on_socket_accept,并且加入到事件管理机制里面13 //fpm_pctl_on_socket_accept方法比较简单,无非是创建Worker进程,设置scoreboard,然后去处理请求14 fpm_event_set(wp->ondemand_event, wp->listening_socket, FPM_EV_READ | FPM_EV_EDGE, fpm_pctl_on_socket_accept, wp);15 wp->socket_event_set = 1;16 fpm_event_add(wp->ondemand_event, 0); 17 return 1;18 } 19 //否则直接创建Worker进程20 return fpm_children_make(wp, 0 /* not in event loop yet */, 0, 1); 21} 定时任务FPM运行期间会不断地执行定时任务,具体来讲分为两种:当管理Worker进程方式是DYNAMIC时定期会fork或者是kill Worker进程,对应:fpm_pctl_perform_idle_server_maintenance_heartbeat方法当Worker进程处理请求超时时会向该Worker进程发送TERM信号,对应:fpm_pctl_heartbeat方法这两个方法的处理逻辑基本相似,处理过程基本上是第一次调用时会添加一个Timer事件,后续每当事件触发时会调用处理方法。我们看下fpm_pctl_heartbeat方法的源码: 1void fpm_pctl_heartbeat(struct fpm_event_s *ev, short which, void *arg) /* {{{ */ 2{ 3 static struct fpm_event_s heartbeat; 4 struct timeval now; 5 if (fpm_globals.parent_pid != getpid()) { 6 return; /* sanity check */ 7 } 8 //执行检查请求处理是否超时的办法,注意第一次which参数传入的是0,不会执行这段 9 if (which == FPM_EV_TIMEOUT) {10 fpm_clock_get(&now);11 fpm_pctl_check_request_timeout(&now);12 return;13 } 14 //确定心跳频率15 /* ensure heartbeat is not lower than FPM_PCTL_MIN_HEARTBEAT */16 fpm_globals.heartbeat = MAX(fpm_globals.heartbeat, FPM_PCTL_MIN_HEARTBEAT);17 /* first call without setting to initialize the timer */18 //添加一个Timer事件,回调方法仍然是自身,注意只有第一次才会添加,因为后续传入的which是FPM_EV_TIMEOUT,在上面就return了19 zlog(ZLOG_DEBUG, "heartbeat have been set up with a timeout of %dms", fpm_globals.heartbeat);20 fpm_event_set_timer(&heartbeat, FPM_EV_PERSIST, &fpm_pctl_heartbeat, NULL);21 fpm_event_add(&heartbeat, fpm_globals.heartbeat);22}我们来追踪下fpm_pctl_check_request_timeout方法的执行过程: 1static void fpm_pctl_check_request_timeout(struct timeval *now) /* {{{ */ 2{ 3 struct fpm_worker_pool_s *wp; 4 //轮询所有的worker_pool,找出request_terminate_timeout和request_slowlog_timeout,然后对每个Child执行fpm_request_check_timed_out方法 5 for (wp = fpm_worker_all_pools; wp; wp = wp->next) { 6 int terminate_timeout = wp->config->request_terminate_timeout; 7 int slowlog_timeout = wp->config->request_slowlog_timeout; 8 struct fpm_child_s *child; 9 if (terminate_timeout || slowlog_timeout) {10 for (child = wp->children; child; child = child->next) {11 fpm_request_check_timed_out(child, now, terminate_timeout, slowlog_timeout);12 }13 }14 }15}16void fpm_request_check_timed_out(struct fpm_child_s *child, struct timeval *now, int terminate_timeout, int slowlog_timeout) /* {{{ */17{18 //核心就在这里,如果超时,则通过fpm_pctl_kill方法向Worker进程发送TERM信号19 if (terminate_timeout & tv.tv_sec >= terminate_timeout) {20 str_purify_filename(purified_script_filename, proc.script_filename, sizeof(proc.script_filename));21 fpm_pctl_kill(child->pid, FPM_PCTL_TERM);22 zlog(ZLOG_WARNING, "[pool %s] child %d, script '%s' (request: \"%s %s%s%s\") execution timed out (%d.%06d sec), terminating",23 child->wp->config->name, (int) child->pid, purified_script_filename, proc.request_method, proc.request_uri,24 (proc.query_string[0] "" : ""), proc.query_string,25 (int) tv.tv_sec, (int) tv.tv_usec);26 }27}fpm_pctl_perform_idle_server_maintenance_heartbeat方法的执行流程和fpm_pctl_heartbeat比较相似,只不过处理逻辑比较复杂,具体逻辑大家可以自己查看。PHP管理Worker进程的DYNAMIC模式就是通过该方法来管理的。 EventLoop我们之前提到过,Master进程创建完所有的Worker进程后就进入到了fpm_event_loop方法,然后一直阻塞在这个方法里面,下面我们来分析下这个方法的逻辑。 1void fpm_event_loop(int err) /* {{{ */ 2{ 3 static struct fpm_event_s signal_fd_event; 4 //fpm_signals_get_fd这个方法返回的就是之前创建的Socketpaire的0号Socket,回调方法是fpm_got_signal 5 fpm_event_set(&signal_fd_event, fpm_signals_get_fd(), FPM_EV_READ, &fpm_got_signal, NULL); 6 fpm_event_add(&signal_fd_event, 0); 7 //添加做心跳检查的Timer事件 8 if (fpm_globals.heartbeat > 0) { 9 fpm_pctl_heartbeat(NULL, 0, NULL);10 }11 //添加做Worker管理的Timer事件12 if (!err) {13 fpm_pctl_perform_idle_server_maintenance_heartbeat(NULL, 0, NULL);14 }15 //进入主循环16 while (1) {17 struct fpm_event_queue_s *q, *q2;18 struct timeval ms;19 struct timeval tmp;20 struct timeval now;21 unsigned long int timeout;22 int ret;23 //查找最近要过期的Timer事件,然后设置epoll或select的超时时间为该时间与当前的时间差。24 //也就是最多等待timeout时间就退出等待,然后执行Timer事件的回调方法,(如果提前退出等待会进行Timer的校验,如果已经到达Timer事件要过期的时间,然后才会执行回调)。这种方式在主流的服务里面比较流行,redis和nginx也是这么实现的。25 q = fpm_event_queue_timer;26 while (q) {27 if (!timerisset(&ms)) {28 ms = q->ev->timeout;29 } else {30 if (timercmp(&q->ev->timeout, &ms, ev->timeout;32 }33 }34 q = q->next;35 }36 //阻塞在这里37 ret = module->wait(fpm_event_queue_fd, timeout);38 //等待超时或者是有socket事件到达,开始处理Timer事件队列39 q = fpm_event_queue_timer;40 while (q) {41 fpm_clock_get(&now);42 if (q->ev) {43 //如果到达过期时间44 if (timercmp(&now, &q->ev->timeout, >) || timercmp(&now, &q->ev->timeout, ==)) {45 //触发回调46 fpm_event_fire(q->ev);47 /* sanity check */48 if (fpm_globals.parent_pid != getpid()) {49 return;50 }51 //如果是FPM_EV_PERSIST类型的事件,那么更新下一次过期时间,否则删除该事件52 if (q->ev->flags & FPM_EV_PERSIST) {53 fpm_event_set_timeout(q->ev, now);54 } else { /* delete the event */55 q2 = q;56 if (q->prev) {57 q->prev->next = q->next;58 }59 if (q->next) {60 q->next->prev = q->prev;61 }62 if (q == fpm_event_queue_timer) {63 fpm_event_queue_timer = q->next;64 if (fpm_event_queue_timer) {65 fpm_event_queue_timer->prev = NULL;66 }67 }68 q = q->next;69 free(q2);70 continue;71 }72 }73 }74 q = q->next;75 }76 }77}分析完fpm_event_loop的执行过程,我们会有个疑问,我们只看到了Timer事件回调方法的触发,Socket事件的回调是在什么时候触发的呢?我们再看下wait方法,以epoll为例: 1static int fpm_event_epoll_wait(struct fpm_event_queue_s *queue, unsigned long int timeout) /* {{{ */ 2{ 3 int ret, i; 4 //进入epoll_wait 5 ret = epoll_wait(epollfd, epollfds, nepollfds, timeout); 6 //触发所有回调 7 for (i = 0; i running_children pid = pid;18 fpm_clock_get(&child->started);19 fpm_parent_resources_use(child);20 }21 }22}我们再继续跟踪下fpm_child_init方法的行为,参考源码:sapi/fpm/fpm/fpm_children.c:146L 1static void fpm_child_init(struct fpm_worker_pool_s *wp) /* {{{ */ 2{ 3 fpm_globals.max_requests = wp->config->pm_max_requests; 4 if (0 > fpm_stdio_init_child(wp) || 5 0 > fpm_log_init_child(wp) || 6 0 > fpm_status_init_child(wp) || 7 0 > fpm_unix_init_child(wp) || 8 重点关注该方法: 9 0 > fpm_signals_init_child() ||10 0 > fpm_env_init_child(wp) ||11 0 > fpm_php_init_child(wp)) {12 zlog(ZLOG_ERROR, "[pool %s] child failed to initialize", wp->config->name);13 exit(FPM_EXIT_SOFTWARE);14 } 15}16int fpm_signals_init_child() /* {{{ */17{18 struct sigaction act, act_dfl;19 memset(&act, 0, sizeof(act));20 memset(&act_dfl, 0, sizeof(act_dfl));21 act.sa_handler = &sig_soft_quit;22 act.sa_flags |= SA_RESTART;23 act_dfl.sa_handler = SIG_DFL;24 //关闭Master进程创建的,因为Worker进程不需要这个通道25 close(sp[0]);26 close(sp[1]);27 //注意这里的act_dfl并不是不处理,而是使用系统默认的处理方法,可以手动执行kill -15(TERM) pid执行,看下效果28 if (0 > sigaction(SIGTERM, &act_dfl, 0) ||29 0 > sigaction(SIGINT, &act_dfl, 0) ||30 0 > sigaction(SIGUSR1, &act_dfl, 0) ||31 0 > sigaction(SIGUSR2, &act_dfl, 0) ||32 0 > sigaction(SIGCHLD, &act_dfl, 0) ||33 //我们看Worker进程只对SIGQUIT做了特殊处理,追踪下去我们看到其实是进行了些soft quit相关动作,做退出前处理34 0 > sigaction(SIGQUIT, &act, 0)) {35 zlog(ZLOG_SYSERROR, "failed to init child signals: sigaction()");36 return -1; 37 } 38 zend_signal_init();39 return 0;40}另外我们还没有涉及到的一个分支是当进程管理是ONDEMEND模式时,Master进程会监听对应Worker Pool的端口号,如果发现有连接到来,那么回调fpm_pctl_on_socket_accept方法。其逻辑就是创Worker进程,Worker进程创建完成后会跳出Master进程当前的fpm_event_loop方法,转而去监听端口,读数据,处理请求,Master方法继续进入fpm_event_loop。具体可以看下fpm_pctl_on_socket_accept方法的实现:sapi/fpm/fpm/fpm_process_ctl.c:496L,逻辑比较简单,我们就不在这里展开。至此,我们已经梳理完了FPM所有进程管理有关的结构和分支。4. 共享内存我们之前提到,所有Worker Pool和Worker进程的状态是保存在唯一的变量fpm_worker_all_pools中的,所以这就涉及到一个问题,Master和Worker进程都会访问这块变量。那么Master进程和Worker进程是怎么进行共享内存的呢,我们来看下源码:(sapi/fpm/fpm/fpm_scoreboard.c:25L) 1int fpm_scoreboard_init_main() /* {{{ */ 2{ 3 struct fpm_worker_pool_s *wp; 4 unsigned int i; 5 //外部执行worker pool数量次 6 for (wp = fpm_worker_all_pools; wp; wp = wp->next) { 7 size_t scoreboard_size, scoreboard_nprocs_size; 8 void *shm_mem; 9 if (wp->config->pm_max_children config->name);11 return -1; 12 } 13 if (wp->scoreboard) {14 zlog(ZLOG_ERROR, "[pool %s] Unable to create scoreboard SHM because it already exists", wp->config->name);15 return -1; 16 } 17 //计算该Pool的scoreboard需要的空间18 //fpm_scoreboard_s大小+N个fpm_scoreboard_proc_s指针大小,DYNAMIC或者是ONDEMEND模式按照最大Worker数分配19 scoreboard_size = sizeof(struct fpm_scoreboard_s) + (wp->config->pm_max_children) * sizeof(struct fpm_scoreboard_proc_s *); 20 //N个fpm_scoreboard_proc_s结构的大小21 scoreboard_nprocs_size = sizeof(struct fpm_scoreboard_proc_s) * wp->config->pm_max_children;22 //真正分配23 shm_mem = fpm_shm_alloc(scoreboard_size + scoreboard_nprocs_size);24 }25 return 0;26}看来我们需要追踪fpm_shm_alloc方法来看具体什么实现的共享内存,具体查看:sapi/fpm/fpm/fpm_shm.c:20L 1void *fpm_shm_alloc(size_t size) /* {{{ */ 2{ 3 void *mem; 4 //实际调用系统调用mmap,开辟一个匿名的共享区域, 5 mem = mmap(0, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0); 6#ifdef MAP_FAILED 7 if (mem == MAP_FAILED) { 8 zlog(ZLOG_SYSERROR, "unable to allocate %zu bytes in shared memory: %s", size, strerror(errno)); 9 return NULL;10 } 11#endif12 if (!mem) {13 zlog(ZLOG_SYSERROR, "unable to allocate %zu bytes in shared memory", size);14 return NULL;15 } 16 fpm_shm_size += size;17 return mem;18}mmap是一个系统调用,一般用来申请大块内存,关于mmap的使用,参考:mmap。Master进程在fpm_scoreboard_init_main方法中初始化了这些共享内存,并将指针放在了全局变量fpm_worker_all_pools中。这样当fork发生后,Worker进程仍然拥有该指针,所以也能在Worker进程中访问这些共享内存。5. 生命周期在代码执行角度来看,FPM的完整周期可以分为以下五个步骤:module_startuprequest_startupexecute_scriptrequest_shutdownmodule_shutdown其中module_start_up和module_shutdown只有在FPM服务启动和关闭时执行,其他三个步骤都是在每个请求之间完成。关于这几个方法的具体逻辑,大家可以参考:main/main.c,因为里面都是些串行逻辑,没有什么复杂步骤,我们这里就不展开介绍。至此,FPM的主干分析流程已经梳理完了,下一章我们将分析PHP的变量存储和内存管理部分。作者:杨 通监审:程天亮编辑:钟 艳网址:tech.lianjia.com更多精彩请猛戳右边二维码关注我们的公众号产品技术先行
预览时标签不可点
关闭更多小程序广告搜索「undefined」网络结果
|
|