标题: Apache中预创建Preforking MPM机制剖析(三) [打印本页] 作者: ajax 时间: 2008-8-12 09:42 标题: Apache中预创建Preforking MPM机制剖析(三) 在预创建MPM中由于存在多个子进程侦听指定的套接字,因此如果不加以控制可能会出现几个子进程同时对一个连接进行处理的情况,这是不允许的。因此我们必须采取一定的措施确保在任何时候一个客户端连接请求只能由一个子进程进程处理。为此Apache中引入了接受互斥锁(Accept Mutex)的概念。接受互斥锁是控制访问TCP/IP服务的一种手段,它能够确保在任何时候只有一个进程在等待TCP/IP的连接请求,从而对于指定的连接请求也只会有一个进程进行处理。
为此MPM紧接着必须创建接受互斥锁。
if (!is_graceful) {
if (ap_run_pre_mpm(s->process->pool, SB_SHARED) != OK) {
mpm_state = AP_MPMQ_STOPPING;
return 1;
}
ap_scoreboard_image->global->running_generation = ap_my_generation;
}
多数的MPM紧接着会立即创建记分板,并将它设置为共享,以便所有的子进程都可以使用它。记分板在启动的时候被创建一次,直到服务器终止时才会被释放。上面的代码就是用于创建记分板,但是你可能很奇怪,因为你看不到我们描述的记分板创建函数ap_create_scoreboard()。事实上,创建过程由挂钩pre_mpm完成,通过使用pre_mpm,服务器就可以让其他的模块在分配记分板之前访问服务器或者在建立子进程之前访问记分板。
ap_run_pre_mpm()运行挂钩 pre_mpm,该挂钩通常对应类似ap_hook_name之类的函数,对于pre_mpm挂钩,对应的函数则是ap_hook_pre_mpm。在 core.c中的ap_hook_pre_mpm(ap_create_scoreboard, NULL, NULL, APR_HOOK_MIDDLE)设定挂钩pre_mpm的对应处理函数则正是记分板创建函数ap_create_scoreboard。
ap_run_pre_mpm挂钩也只有在进行重新启动的时候才会调用,而在进行平稳启动的时候,并不调用这个挂钩,这样做会丢失所有的仍然正在为长期请求提供服务的子进程的信息。挂钩的引入是 Apache2.0版本的一个新的实现机制,也是理解Apache核心的一个重要机制之一,关于挂钩的具体的实现细节我们在后面的部分会详细分析。
对于每次冷启动,Apache启动之后,内部的记分板的家族号都是从0开始,而每一次平稳启动后家族号则是在原先的家族号加一。
set_signals();
当分配了记分板之后,MPM就应该设置信号处理器,一方面允许外部进程通过信号通知其停止或者重新启动,另一方面服务器应该忽略尽可能多的信号,以确保它不会被偶然的信号所中断。正常情况下,父进程需要处理三种信号:正常的重新启动、非正常的重新启动以及关闭的信号。
SIGTERM:该信号用于关闭主服务进程。信号处理函数sig_term中设置shutdown_pending=true;
SIGHUP:该信号用于重启服务器,信号处理函数中设置restart_pending=true和graceful_mode=false
SIGUSR1:该信号用于平稳启动服务器,信号处理函restart数中设置restart_pending=true和graceful_mode=true
至于信号SIGXCPU、SIGXFSZ则由默认的信号处理函数SIG_DFL处理,SIGPIPE则会被忽略。
在Apache主程序的循环中,程序不断的检测 shutdown_pending,restart_pending和graceful_mode三个变量的值。通常并不是外部程序一发送信号,Apache就立即退出。最差的情况就是信号是在刚检测完就发送了,这样,主程序需要将该次循环执行完毕后才能发现发送的信号。
if (one_process) {
AP_MONCONTROL(1);
make_child(ap_server_conf, 0);
}
至此大部分准备工作已经完成,剩下的任务就是创建进程。进程的创建包括两种模式:单进程模式和多进程模式。
单进程模式通常用于Apache调试,由于不管多进程还是单进程,对HTTP请求处理以及模块等的使用都是完全相同的,区别仅仅在于效率。而多线程的调试要比单进程复杂的多。
如果是单进程调试模式,那么上面的两句程序将被程序。我们首先解释一下AP_MONCONTROL宏的含义。对于调试,一方面可能比较关心执行的正确与否,内存是否溢出等等,另外一方面就是能够找出整个服务器的运行瓶颈,只有找到了运行的瓶颈才能进行改善,从而提高效率。例如,假设应用程序花了 50% 的时间在字符串处理函数上,如果可以对这些函数进行优化,提高 10% 的效率,那么应用程序的总体执行时间就会改进 5%。因此,如果希望能够有效地对程序进行优化,那么精确地了解时间在应用程序中是如何花费的,以及真实的输入数据,这一点非常重要。这种行为就称为代码剖析(code profiling)。
An executable program compiled using the -pg option to cc(1) automatically cally includes calls to collect statistics for the gprof(1) call-graph execution profiler. In typical operation, profiling begins at program startup and ends when the program calls exit. When the program exits, the profiling data are written to the file gmon.out, then gprof(1) can be used to examine the results.
一个可执行的应用程序可以在使用gcc编译的时候利用-pg选项自动的调用相关函数收集一些执行统计信息以便gprof execution profiler使用。
moncontrol() selectively controls profiling within a program. When the program starts, profiling begins. To stop the collection of histogram ticks and call counts use moncontrol(0); to resume the collection of his-histogram togram ticks and call counts use moncontrol(1). This feature allows the cost of particular operations to be measured. Note that an output file will be produced on program exit regardless of the state of moncontrol().
Programs that are not loaded with -pg may selectively collect profiling statistics by calling monstartup() with the range of addresses to be pro-profiled. filed. lowpc and highpc specify the address range that is to be sampled; the lowest address sampled is that of lowpc and the highest is just below highpc. Only functions in that range that have been compiled with the -pg option to cc(1) will appear in the call graph part of the output;
however, all functions in that address range will have their execution time measured. Profiling begins on return from monstartup().
单进程的另外一个任务就是调用make_child。对于单进程,make_child非常的简单:
static int make_child(server_rec *s, int slot)
{
int pid;
……
if (one_process) {
apr_signal(SIGHUP, sig_term);
apr_signal(SIGINT, sig_term);
apr_signal(SIGQUIT, SIG_DFL);
apr_signal(SIGTERM, sig_term);
child_main(slot);
return 0;
}
/*多进程处理代码*/
}
从代码中可以看出,单进程直接调用了child_main,该函数用于直接处理与客户端的请求。在整个系统中只有一个主服务进程存在。
else {
if (ap_daemons_max_free < ap_daemons_min_free + 1) /* Don't thrash... */
ap_daemons_max_free = ap_daemons_min_free + 1;
/* kill off the idle ones */
ap_mpm_pod_killpg(pod, ap_max_daemons_limit);
/* This is mostly for debugging... so that we know what is still
* gracefully dealing with existing request. This will break
* in a very nasty way if we ever have the scoreboard totally
* file-based (no shared memory)
*/
for (index = 0; index < ap_daemons_limit; ++index) {
if (ap_scoreboard_image->servers[index][0].status != SERVER_DEAD) {
ap_scoreboard_image->servers[index][0].status = SERVER_GRACEFUL;
}
}
}
如果Apache被要求的是重新启动,那么对于平稳启动和非平稳启动处理则不太相同。对于平稳启动而言,主进程需要终止的仅仅是那些目前空闲的子进程,而忙碌的进程则不进行任何处理。空闲进程终止通过 ap_mpm_pod_killpg实现;同时由于记分板并不销毁,因此对于那些终止的进程,必须更新其在记分板中的状态信息为SERVER_DEAD;而对于那些仍然活动的子进程,则将其状态更新为SERVER_GRACEFUL。
else {
/* Kill 'em off */
if (unixd_killpg(getpgrp(), SIGHUP) < 0) {
ap_log_error(APLOG_MARK, APLOG_WARNING, errno, ap_server_conf, "killpg SIGHUP");
}
ap_reclaim_child_processes(0); /* Not when just starting up */
ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf,
"SIGHUP received. Attempting to restart");
}
如果服务器被要求强制重启,那么所有的子进程包括那些仍然在处理请求的都将被统统终止。