PHP内核剖析之fpm(未完待续)

关于php-fpm

FPM(FastCGI Process Manager)是PHP FastCGI运行模式的一个进程管理器,从它的定义可以看出,FPM的核心功能是进程管理,那么它用来管理什么进程呢?这个问题就需要从FastCGI说起了。

FastCGI是Web服务器(如:Nginx、Apache)和处理程序之间的一种通信协议,它是与Http类似的一种应用层通信协议,注意:它只是一种协议!

PHP只是一个脚本解析器,你可以把它理解为一个普通的函数,输入是PHP脚本。输出是执行结果,假如我们想用PHP代替shell,在命令行中执行一个文件,那么就可以写一个程序来嵌入PHP解析器,这就是cli模式,这种模式下PHP就是普通的一个命令工具。接着我们又想:能不能让PHP处理http请求呢?这时就涉及到了网络处理,PHP需要接收请求、解析协议,然后处理完成返回请求。在网络应用场景下,PHP并没有像Golang那样实现http网络库,而是实现了FastCGI协议,然后与web服务器配合实现了http的处理,web服务器来处理http请求,然后将解析的结果再通过FastCGI协议转发给处理程序,处理程序处理完成后将结果返回给web服务器,web服务器再返回给用户

这里写图片描述

PHP实现了FastCGI协议的解析,但是并没有具体实现网络处理,常用的网络模型有以下两种

多进程模型 :多进程模型通常是主进程只负责管理子进程,而基本的网络事件由各个子进程处理,nginx、fpm就是这种模式

多线程模型:多线程模型与多进程类似,只是它是线程粒度,通常会由主线程监听、接收请求,然后交由子线程处理,memcached就是这种模式,有的也是采用多进程那种模式:主线程只负责管理子线程不处理网络事件,各个子线程监听、接收、处理请求,memcached使用udp协议时采用的是这种模式。

master进程的作用是什么?

fpm是一种多进程的模型,由一个master进程和若干worker进程组成(具体数量需要看php-fpm.conf的配置)master启动的时候回创建一个socket,但是不会接受,处理请求,而是fork出worker子进程去接受和处理请求

所以master进程的主要作用就是管理worker进程,负责fork或者kill掉子进程,当请求多worker处理不过来了这是master会fork新的worker进程处理,如果空闲的进程多的时候,就会kill掉一些worker进程,避免占用浪费系统资源(像一个工头,他不干活,监督大家搬砖,活多了找几个人来干活,活儿少了,空闲的工人多了就开除几个)

worker进程的作用是什么?

worker进程的主要工作是处理请求,每个worker进程都会**竞争地**accept请求,接受成功后会解析fastcgi,然后执行脚本,完成后关闭请求,继续等待新的连接,这是worker进程的声明周期。

概括来说,fpm的实现就是创建一个master进程,在master进程中创建并监听socket,然后fork出多个子进程,这些子进程各自accept请求,子进程的处理非常简单,它在启动后阻塞在accept上,有请求到达后开始读取请求数据,读取完成后开始处理然后再返回,在这期间是不会接收其它请求的,也就是说fpm的子进程同时只能响应一个请求,只有把这个请求处理完成后才会accept下一个请求,这一点与nginx的事件驱动有很大的区别,nginx的子进程通过epoll管理套接字,如果一个请求数据还未发送完成则会处理下一个请求,即一个进程会同时连接多个请求,它是非阻塞的模型,只处理活跃的套接字。

进程之间的通信有哪些呢? 1 管道,2命名管道,3队列,4信号量 5共享内存

fpm的master进程与worker进程之间不会直接进行通信,master通过共享内存获取worker进程的信息,比如worker进程当前状态、已处理请求数等,当master进程要杀掉一个worker进程时则通过发送信号的方式通知worker进程。

fpm可以同时监听多个端口,每个端口对应一个worker pool,而每个pool下对应多个worker进程,类似nginx中server概念,这些归属不同的pool的worker进程任由一个master管理。(是不是说fpm只能有一个master)

等不及了,上源码吧

worker pool的结构为 fpm_worker_pool_s,pool之间构成一个链表

//代码文件 php/sapi/fpm/fpm/fpm_worker_pool.h
struct fpm_worker_pool_s {
    struct fpm_worker_pool_s *next; //指向下一个fpm_worker_pool_s的指针
    struct fpm_worker_pool_config_s *config; //指向fpm配置信息
    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;  //当前pool的worker链表,每个worker链表对应一个fpm_child_s 结构
    int running_children; //当前pool的worker总数
    int idle_spawn_rate;
    int warn_max_children;
#if 0
    int warn_lq;
#endif
    struct fpm_scoreboard_s *scoreboard; //记录worker的运行信息,空闲忙碌的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
};

这个是当php-fpm.conf解析后,会解析到fpm_worker_pool_config_s。

接下来看下php-fpm的启动过程

//sapi/fpm/fpm/fpm_main.c
int main(int argc, char *argv[])
{
    ...
    //注册SAPI:将全局变量sapi_module设置为cgi_sapi_module
    sapi_startup(&cgi_sapi_module);
    ...
    //执行php_module_starup() 第一个阶段
    if (cgi_sapi_module.startup(&cgi_sapi_module) == FAILURE) {
        return FPM_EXIT_SOFTWARE;
    }
    ...
    //初始化
    if(0 > fpm_init(...)){
        ...
    }
    ...
    fpm_is_running = 1;

    fcgi_fd = fpm_run(&max_requests);//后面都是worker进程的操作,master进程不会走到下面
    parent = 0;
    ...
    /* library is already initialized, now init our request */
    request = fpm_init_request(fcgi_fd);

    zend_first_try {
        //worker进程阻塞,等待请求
        while (EXPECTED(fcgi_accept_request(request) >= 0)) {
            ...
            fpm_request_executing(); //初始化计分板的信息
            php_execute_script(&file_handle); //执行脚本
            ...
fastcgi_request_done:
            php_request_shutdown((void *) 0);//关闭请求
            ...
            requests++;
            //当请求数达到php-fpm所设定的pm.max_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();
    }
}

里面涉及到了 两个主要的过程 php_init 和 php_run

php_init 这里干了什么 主要是进行了一些初始化操作

//sapi/fpm/fpm/fpm.c
int fpm_init(int argc, char **argv, char *config, char *prefix, char *pid, int test_conf, int run_as_root, int force_daemon, int force_stderr) /* {{{ */
{
    fpm_globals.argc = argc;
    fpm_globals.argv = argv;
    if (config && *config) {
        fpm_globals.config = strdup(config);
    }
    fpm_globals.prefix = prefix;
    fpm_globals.pid = pid;
    fpm_globals.run_as_root = run_as_root;
    fpm_globals.force_stderr = force_stderr;

    if (0 > fpm_php_init_main()           ||
        0 > fpm_stdio_init_main()         ||
        0 > fpm_conf_init_main(test_conf, force_daemon) //解析PHP-fpm配置文件 ||
        0 > fpm_unix_init_main() 启动进程创建一个管道pipe 用于和子进程(master进程)通信,子进程完成初始化后,会通过这个管道给启动进程发消息,         ||
        0 > fpm_scoreboard_init_main() 分配用于记录worker进程运行信息的结构,次结构分配在共享内存上,
           ||
        0 > fpm_pctl_init_main()          ||
        0 > fpm_env_init_main()           ||
        0 > fpm_signals_init_main()       //创建管道,注册信号量SIGTERMSIGINTSIGUSR1、SIGUSR2、SIGCHLDSIGQUIT这些信号时将调用sig_handler()处理||
        0 > fpm_children_init_main()      ||
        0 > fpm_sockets_init_main()      //创建每个worker poolsocket套接字,启动后讲监听次socket接受请求||
        0 > fpm_worker_pool_init_main()   //master的事件管理,用于管理IO,定时事件||
        0 > fpm_event_init_main()) {

        if (fpm_globals.test_successful) {
            exit(FPM_EXIT_OK);
        } else {
            zlog(ZLOG_ERROR, "FPM initialization failed");
            return -1;
        }
    }

    if (0 > fpm_conf_write_pid()) {
        zlog(ZLOG_ERROR, "FPM initialization failed");
        return -1;
    }

    fpm_stdio_init_final();
    zlog(ZLOG_NOTICE, "fpm is running, pid %d", (int) fpm_globals.parent_pid);

    return 0;
}

(1)fpm_conf_init_main():
解析php-fpm.conf配置文件,分配worker pool内存结构并保存到全局变量中:fpm_worker_pool_config_s *config,各worker pool配置解析到fpm_worker_pool_s->config中。看下fpm_worker_pool_config_s结构体里面的属性是不是很熟悉和
php-fpm.conf一样

PS关于fpm_worker_pool_config_s里面只是进程池的属性,全局的属性在fpm_global_config_s 结构体中

//sapi/fpm/fpm/fpm_conf.c
/*
 * Please keep the same order as in fpm_conf.c and in php-fpm.conf.in
 */
struct fpm_worker_pool_config_s {
    char *name;
    char *prefix;
    char *user;
    char *group;
    char *listen_address;
    int listen_backlog;
    /* Using chown */
    char *listen_owner;
    char *listen_group;
    char *listen_mode;
    char *listen_allowed_clients;
    int process_priority;
    int pm;
    int pm_max_children;
    int pm_start_servers;
    int pm_min_spare_servers;
    int pm_max_spare_servers;
    int pm_process_idle_timeout;
    int pm_max_requests;
    char *pm_status_path;
    char *ping_path;
    char *ping_response;
    char *access_log;
    char *access_format;
    char *slowlog;
    int request_slowlog_timeout;
    int request_slowlog_trace_depth;
    int request_terminate_timeout;
    int rlimit_files;
    int rlimit_core;
    char *chroot;
    char *chdir;
    int catch_workers_output;
    int clear_env;
    char *security_limit_extensions;
    struct key_value_s *env;
    struct key_value_s *php_admin_values;
    struct key_value_s *php_values;
#ifdef HAVE_APPARMOR
    char *apparmor_hat;
#endif
#ifdef HAVE_FPM_ACL
    /* Using Posix ACL */
    char *listen_acl_users;
    char *listen_acl_groups;
#endif
};
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值