Apache中预创建Preforking MPM机制剖析(二)


6.3.3.3主服务进程管理
6.3.3.3.1主服务进程概述
所有的MPM都是从ap_mpm_run()函数开始执行,对此预创建MPM也不例外。 ap_mpm_run()函数通常由Apache核心在main()中进行调用,一旦调用,运行服务器的职责就从Apache核心移交给了MPM。这个函数是所有的MPM都必须实现的。通常情况下,ap_mpm_run的实现会比较复杂。对于Preforking MPM,它的执行流程可以用下图描述:

图 主进程工作流程
从上图中可以看出,主服务进程的功能主要包括下面几部分:
1)、接受进程外部信号进行重启,关闭以及平稳启动等等操作。外部进程通过发送信号给主服务进程以实现控制主服务进程的目的。
2)、在启动的时候创建子进程或者在平稳启动的时候用新进程替代原有进程。
3)、监控子进程的运行状态,并根据运行负载自动调节空闲子进程的数目:在存在过多空闲子进程的时候终止部分空闲进程;在空闲子进程较少的时候创建更多的空闲进程以便将空闲进程的数目维持在一定的数目之内。
上图的上半部分主要处理子进程的终止以及平稳启动的一些内容,而标有”维护空闲子进程数目”的下半部分则主要处理平稳启动的一些内容。在该循环中,主进程统计空闲子进程的数目并从记分板中获得详细的空闲进程列表,然后将统计得到的空闲进程数目idle_count与系统设置的极限ap_daemons_max_free和ap_daemons_min_free)进行对比。如果有过多的空闲子进程,那么它将每次循环中终止一个子进程;反之它将一次性创建足够的空闲进程。
下面的部分我们概要的描述一下主服务进程是如何处理平稳启动,子进程终止以及如何维持空闲子进程数目的。
平稳启动以及处理子进程终止
u设置remaining_children_to_start(以后简称 rem.child2start)。变量rem.child2start只有在平稳启动的时候才会用到。它记录的是服务器启动后需要启动的子进程的数目。需要注意的是主服务进程并没有使用startup_children过程创建子进程。对于每一个被终止的子进程,主服务进程通过调用wait()可以得到终止通知。如果初始的子进程的数目在配置文件中被更改了,那么仅仅用新进程替代终止的子进程肯定会出错,因此主服务进程用rem.child2start 控制这个数目。
vpid = wait 或者超时。对于使用fork创建的子进程,主服务进程调用wait等待它们的终止,这种做法确保没有僵尸进程的产生。如果在指定的时间内进程仍然没有终止,那么主服务进程算超时处理,即使没有等到终止通知,主服务进程将继续执行它的循环。
w等待成功的情况。如果在指定的时间内子进程终止,此时主服务器进程将完成下面的各项工作:
-                 process_child_status:获取子进程终止的原因。子进程的终止可能有很多情况,比如正常终止,异常终止等等。正常终止对于主服务进程到无所谓,异常终止主进程则必须知道具体原因。
-                 find_child_by_pid:当进程终止后,必须在记分板中更新它的状态信息,因此首先必须在记分板中查找该进程对应的插槽信息。如果查找,则将进程的状态信息设置为 SERVER_DEAD。如果remaining_children_to_start不为零的话,创建一个新的子进程来代替终止的子进程。
-                 如果没有在记分板中没有找到终止进程的对应插槽,那么检查该进程是否是“其余子进程”。一些情况下,主服务进程会创建一些非子服务进程的进程,它们称之为“ 其余进程”,并用一个单独的列表进行登记。比如,一般情况下,Apache会将日志写入到文件中,但是有的时候Apache则希望将数据写入到一个给定的应用程序中。因此主服务器进程必须为该应用程序创建该进程,并且将该进程的标准输入STDIN关联道主服务进程的日志流中。这种进程并不是用来执行处理 HTTP请求的子服务进程,因此称之为“其余进程”。任何时候只要服务器重启,日志应用进程都会接受到SIGHU和SIGUSR1信号,然后终止退出,对应的模块必须重新创建这种进程。如果进程既不是“其余进程”,在记分板中也找不到对应的插槽,并且设置了平稳启动模式,那么肯定发生了下面的情况:管理员减少了允许的子进程数目同时进行了平稳启动或者。。。
x等待超时:如果所有的终止了的进程被新创建的新进程代替之后rem.child2start变量的值仍然不为零,那么这意味着必须创建更多的子进程。创建由startup_children()函数完成。
yz
空闲子进程维护
下面的一节我们将详细的分析主进程以及子进程相关的源代码。
6.3.3.3.2主服务进程概述
int ap_mpm_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s)
{
    int index;
    int remaining_children_to_start;
    apr_status_t rv;
 
    ap_log_pid(pconf, ap_pid_fname);
对于所有的Apache MPM而言,其应该首先完成的工作就是在文件pidfile中记录进程的ID。因为启动和终止Apache的默认脚本通常会读取pidfile文件,从中查找所有记录的进程然后逐个终止它。因此如果不进行记录的话,启动的这些进程可能无法通过脚本进行终止,这项操作进行的越快越好。
    first_server_limit = server_limit;
    if (changed_limit_at_restart) {
        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
                     "WARNING: Attempt to change ServerLimit "
                     "ignored during restart");
        changed_limit_at_restart = 0;
    }
理解上面这段代码的关键在于理解两个变量first_server_limit和changed_limit_at_restart的作用。
server_limit变量用以记录服务器内允许同时存在的子服务进程的数目,用过通过配置文件中的 ServerLimit指令可以修改这个值。当每次Apache启动的时候,通过读取配置文件这个指令的参数值最终保存到了server_limit变量中并影响服务器的进程产生。由于当Apache重新启动(restart)的时候也会读取配置文件,因此如果服务器重新启动之前修改了配置文件中的 ServerLimit指令参数,那么毫无疑问,这种变化Apache重启的时候肯定会看到的。那么Apache该如何处理这种变化呢?是使用新的 server_limit还是使用原有的server_limit?Apache的做法是不允许在重启的时候修改server_limit值,即使你修改了Apache也会忽略。
为了能够检查出这种修改,在Apache第一次启动的时候,ServerLimit的值就被记录在 first_server_limit变量中,在整个Apache运行期间即使重新启动,这个值也不会变化。 first_server_limit=server_limit就是保存初始的值。
按正常的处理策略,对于每次重启后都应该把server_limit与first_server_limit的值进行比较判断是否发生变化,如果发生变化就给出警告,但是上面的代码中并没有这种比较。那么比较在哪儿发生的呢?钥匙在 change_limit_at_restart变量上。当重新启动后读取配置文件的时候,遇到ServerLimit指令会调用函数 set_server_limit处理该指令,该函数中会将指令参数后面的值与first_server_limit进行比较:
    int tmp_server_limit;
    tmp_server_limit = atoi(arg);//ServerLimit指令后的参数值
    if (first_server_limit &&tmp_server_limit != server_limit) {
        changed_limit_at_restart = 1;
        return NULL;
    }
    server_limit = tmp_server_limit;
从上面的代码中可以看出,changed_limit_at_restart反映了ServerLimit在重启期间是否发生了更改。对于这种更改,Apache并不理会,只是简单的警告,并将changed_limit_at_restart设置为零,这样下次重启就不要进行判断了。
    /* Initialize cross-process accept lock */
    ap_lock_fname = apr_psprintf(_pconf, "%s.%" APR_PID_T_FMT,
                                 ap_server_root_relative(_pconf, ap_lock_fname),
                                 ap_my_pid);
 
    rv = apr_proc_mutex_create(&accept_mutex, ap_lock_fname,
                               ap_accept_lock_mech, _pconf);
    if (rv != APR_SUCCESS) {
        ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s,
                     "Couldn't create accept lock (%s) (%d)",
                     ap_lock_fname, ap_accept_lock_mech);
        mpm_state = AP_MPMQ_STOPPING;
        return 1;
    }
 
#if APR_USE_SYSVSEM_SERIALIZE
    if (ap_accept_lock_mech == APR_LOCK_DEFAULT ||
        ap_accept_lock_mech == APR_LOCK_SYSVSEM) {
#else
    if (ap_accept_lock_mech == APR_LOCK_SYSVSEM) {
#endif
        rv = unixd_set_proc_mutex_perms(accept_mutex);
        if (rv != APR_SUCCESS) {
            ap_log_error(APLOG_MARK, APLOG_EMERG, rv, s,
                         "Couldn't set permissions on cross-process lock; "
                         "check User and Group directives");
            mpm_state = AP_MPMQ_STOPPING;
            return 1;
        }
    }

本文作者:
« 
» 
快速导航

Copyright © 2016 phpStudy | 豫ICP备2021030365号-3