APR_DECLARE(void *) apr_palloc(apr_pool_t *pool, apr_size_t in_size)
{
将in_size调整为index整数倍的
/*先查看active节点,active是内存池中的激活节点,所有的内存都从这个节点中分配*/
if(active节点就能够满足分配要求)
{
直接从active分配内存
返回了
/*这里不会往下执行,直接返回了,也就不会理睬在active分配内存之后,还是不是链表中的最大
节点,所以tingya的对这个地方的解释不完全正确,应该是active节点是不会理睬有多大的,反正所有的内存都从
active节点分配,当active节点不够分配时,就更换active节点.从active->next开始的链表,是按照可用空间从大
到小排序的*/
}
查看active->next节点/*是链表中最大的节点,如果这个节点都不够分,就只能从分配子中提取节点了*/
if(active->next节点能够满足要求)
{
把这个节点从链表中提取出来,用node表示;
}
else
{
从分配子从获取节点,用node表示;
}
/*此时内存已经分配出来了,并且是从node节点中分配的*/
/*所以接下来我要用node把active替换掉,让active回到链表中,并且是回到排序后的位置上,看代码:*/
把node放到active链表头,即挤掉原active的位置
用active的可用大小去和链表后面的节点依次做比较,插入到合适的位置.
/*因为每次我都做的是相同的操作,所以我不需要再对整个链表进行排序就可以保证链表是从大到小排序
的*/
返回内存起始地址;
}
char* apr_pstrdup | ( | apr_pool_t * | p, | |
const char * | s | |||
) |
duplicate a string into memory allocated out of a pool
-
Parameters:
-
p The pool to allocate out of s The string to duplicate
-
Returns:
- The new string
A "handler" is an internal Apache representation of the action to be performed when a file is called. Generally, files have implicit handlers, based on the file type. Normally, all files are simply served by the server, but certain file types are "handled" separately.
Apache 1.1 adds the ability to use handlers explicitly. Based on either filename extensions or on location, handlers can be specified without relation to file type. This is advantageous both because it is a more elegant solution, and because it also allows for both a type and a handler to be associated with a file.
Handlers can either be built into the server or included in a module, or they can be added with the Action
directive. The built-in handlers in the standard distribution are as follows:
- default-handler: Send the file using the
default_handler()
, which is the handler used by default to handle static content. (core) - send-as-is: Send file with HTTP headers as is. (
mod_asis
) - cgi-script: Treat the file as a CGI script. (
mod_cgi
) - imap-file: Parse as an imagemap rule file. (
mod_imap
) - server-info: Get the server's configuration information. (
mod_info
) - server-status: Get the server's status report. (
mod_status
) - type-map: Parse as a type map file for content negotiation. (
mod_negotiation
)
变量:
process_rec,a structure that represents one process。
struct process_rec {apr_pool_t *pool; //全局池apr_pool_t *pconf; //配置池int argc; //参数个数const char * const *argv; //参数const char *short_name;};//表示进程的数据结构
apr_pool_t:基本池类型
1: AP_MONCONTROL(0); 代码剖析控制
2: process = init_process(&argc, &argc); //进程初始化,process代表主进程信息。
3: apr_pool_create(&pcommands, pglobal);
apr_pool_tag(pcommands, "pcommands");
pcommands --> 命令行参数池
4: error = ap_setup_prelinked_modules(process); //将所有预连接的模块加入到加载模块链表中。
5: ap_run_rewrite_args(process);
rewrite_args是为MPM模块设置的,允许MPM模块对命令行中的传入参数进行重写。
6: 参数处理
apr_getopt_t *opt;
apr_getopt_init(&opt, pcommands, process->argc, process->argv);
处理参数传入opt。
while((rv=apr_getopt(opt, AP_SERVER_BASEARGS, &c, &optarg)) == APR_SUCCESS) {
char **new;
switch(c) {
case 'c':
}
}
7: apr_pool_create(&plog, pglobal);
apr_pool_tag(plog, "plog");
内存池,内存池树
8: ap_replace_stderr_log(process->pool, temp_error_log);
输出日志文件替换。
9: 读取配置
server_conf = ap_read_config(process, ptemp, confname, &ap_conftree);
解析成配置树。
10: ap_run_pre_config(pconf, plog, ptemp);
触发模块的pre_config挂钩,是模块可以修改配置树。
11: ap_process_config_tree
处理配置树。
12: signal_server = APR_RETRIEVE_OPTIONAL_FN(ap_signal_server);
ap_run_post_config(pconf, plog, ptemp, server_conf);
13: for(;;) 主循环
请求处理被分割为多个挂钩阶段,每个阶段实现一个特定的任务,开发人员注册挂钩。Apache会自动执行所有挂钩函数。
APR: Apache Portable Runtime, Apache可移植运行库,屏蔽操作系统底层细节,开发人员关注功能。
MPM: Multi-Processes Modules, 多进程处理模块,充分利用操作系统的特性(不存在适用所有操作系统的并发模 型)。
- #include "apr.h"
- #include "apr_strings.h"
- #include "apr_getopt.h"
- #include "apr_general.h"
- #include "apr_lib.h"
- #include "apr_md5.h"
- #include "apr_time.h"
- #include "apr_version.h"
- #include "apu_version.h"
- #define APR_WANT_STDIO
- #define APR_WANT_STRFUNC
- #include "apr_want.h"
- #define CORE_PRIVATE
- #include "ap_config.h"
- #include "httpd.h"
- #include "http_main.h"
- #include "http_log.h"
- #include "http_config.h"
- #include "http_core.h"
- #include "http_vhost.h"
- #include "apr_uri.h"
- #include "util_ebcdic.h"
- #include "ap_mpm.h"
- #include "mpm_common.h"
- /* WARNING: Win32 binds http_main.c dynamically to the server. Please place
- * extern functions and global data in another appropriate module.
- *
- * Most significant main() global data can be found in http_config.c
- */
- static void show_mpm_settings(void)
- {
- int mpm_query_info;
- apr_status_t retval;
- printf("Server MPM: %s\n", ap_show_mpm());
- retval = ap_mpm_query(AP_MPMQ_IS_THREADED, &mpm_query_info);
- if (retval == APR_SUCCESS) {
- printf(" threaded: ");
- if (mpm_query_info == AP_MPMQ_DYNAMIC) {
- printf("yes (variable thread count)\n");
- }
- else if (mpm_query_info == AP_MPMQ_STATIC) {
- printf("yes (fixed thread count)\n");
- }
- else {
- printf("no\n");
- }
- }
- retval = ap_mpm_query(AP_MPMQ_IS_FORKED, &mpm_query_info);
- if (retval == APR_SUCCESS) {
- printf(" forked: ");
- if (mpm_query_info == AP_MPMQ_DYNAMIC) {
- printf("yes (variable process count)\n");
- }
- else if (mpm_query_info == AP_MPMQ_STATIC) {
- printf("yes (fixed process count)\n");
- }
- else {
- printf("no\n");
- }
- }
- }
- static void show_compile_settings(void)
- {
- printf("Server version: %s\n", ap_get_server_description());
- printf("Server built: %s\n", ap_get_server_built());
- printf("Server's Module Magic Number: %u:%u\n",
- MODULE_MAGIC_NUMBER_MAJOR, MODULE_MAGIC_NUMBER_MINOR);
- printf("Server loaded: APR %s, APR-Util %s\n",
- apr_version_string(), apu_version_string());
- printf("Compiled using: APR %s, APR-Util %s\n",
- APR_VERSION_STRING, APU_VERSION_STRING);
- /* sizeof(foo) is long on some platforms so we might as well
- * make it long everywhere to keep the printf format
- * consistent
- */
- printf("Architecture: %ld-bit\n", 8 * (long)sizeof(void *));
- show_mpm_settings();
- printf("Server compiled with....\n");
- #ifdef BIG_SECURITY_HOLE
- printf(" -D BIG_SECURITY_HOLE\n");
- #endif
- #ifdef SECURITY_HOLE_PASS_AUTHORIZATION
- printf(" -D SECURITY_HOLE_PASS_AUTHORIZATION\n");
- #endif
- #ifdef OS
- printf(" -D OS=\"" OS "\"\n");
- #endif
- #ifdef APACHE_MPM_DIR
- printf(" -D APACHE_MPM_DIR=\"" APACHE_MPM_DIR "\"\n");
- #endif
- #ifdef HAVE_SHMGET
- printf(" -D HAVE_SHMGET\n");
- #endif
- #if APR_FILE_BASED_SHM
- printf(" -D APR_FILE_BASED_SHM\n");
- #endif
- #if APR_HAS_SENDFILE
- printf(" -D APR_HAS_SENDFILE\n");
- #endif
- #if APR_HAS_MMAP
- printf(" -D APR_HAS_MMAP\n");
- #endif
- #ifdef NO_WRITEV
- printf(" -D NO_WRITEV\n");
- #endif
- #ifdef NO_LINGCLOSE
- printf(" -D NO_LINGCLOSE\n");
- #endif
- #if APR_HAVE_IPV6
- printf(" -D APR_HAVE_IPV6 (IPv4-mapped addresses ");
- #ifdef AP_ENABLE_V4_MAPPED
- printf("enabled)\n");
- #else
- printf("disabled)\n");
- #endif
- #endif
- #if APR_USE_FLOCK_SERIALIZE
- printf(" -D APR_USE_FLOCK_SERIALIZE\n");
- #endif
- #if APR_USE_SYSVSEM_SERIALIZE
- printf(" -D APR_USE_SYSVSEM_SERIALIZE\n");
- #endif
- #if APR_USE_POSIXSEM_SERIALIZE
- printf(" -D APR_USE_POSIXSEM_SERIALIZE\n");
- #endif
- #if APR_USE_FCNTL_SERIALIZE
- printf(" -D APR_USE_FCNTL_SERIALIZE\n");
- #endif
- #if APR_USE_PROC_PTHREAD_SERIALIZE
- printf(" -D APR_USE_PROC_PTHREAD_SERIALIZE\n");
- #endif
- #if APR_USE_PTHREAD_SERIALIZE
- printf(" -D APR_USE_PTHREAD_SERIALIZE\n");
- #endif
- #if APR_PROCESS_LOCK_IS_GLOBAL
- printf(" -D APR_PROCESS_LOCK_IS_GLOBAL\n");
- #endif
- #ifdef SINGLE_LISTEN_UNSERIALIZED_ACCEPT
- printf(" -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT\n");
- #endif
- #if APR_HAS_OTHER_CHILD
- printf(" -D APR_HAS_OTHER_CHILD\n");
- #endif
- #ifdef AP_HAVE_RELIABLE_PIPED_LOGS
- printf(" -D AP_HAVE_RELIABLE_PIPED_LOGS\n");
- #endif
- #ifdef BUFFERED_LOGS
- printf(" -D BUFFERED_LOGS\n");
- #ifdef PIPE_BUF
- printf(" -D PIPE_BUF=%ld\n",(long)PIPE_BUF);
- #endif
- #endif
- printf(" -D DYNAMIC_MODULE_LIMIT=%ld\n",(long)DYNAMIC_MODULE_LIMIT);
- #if APR_CHARSET_EBCDIC
- printf(" -D APR_CHARSET_EBCDIC\n");
- #endif
- #ifdef NEED_HASHBANG_EMUL
- printf(" -D NEED_HASHBANG_EMUL\n");
- #endif
- #ifdef SHARED_CORE
- printf(" -D SHARED_CORE\n");
- #endif
- /* This list displays the compiled in default paths: */
- #ifdef HTTPD_ROOT
- printf(" -D HTTPD_ROOT=\"" HTTPD_ROOT "\"\n");
- #endif
- #ifdef SUEXEC_BIN
- printf(" -D SUEXEC_BIN=\"" SUEXEC_BIN "\"\n");
- #endif
- #if defined(SHARED_CORE) && defined(SHARED_CORE_DIR)
- printf(" -D SHARED_CORE_DIR=\"" SHARED_CORE_DIR "\"\n");
- #endif
- #ifdef DEFAULT_PIDLOG
- printf(" -D DEFAULT_PIDLOG=\"" DEFAULT_PIDLOG "\"\n");
- #endif
- #ifdef DEFAULT_SCOREBOARD
- printf(" -D DEFAULT_SCOREBOARD=\"" DEFAULT_SCOREBOARD "\"\n");
- #endif
- #ifdef DEFAULT_LOCKFILE
- printf(" -D DEFAULT_LOCKFILE=\"" DEFAULT_LOCKFILE "\"\n");
- #endif
- #ifdef DEFAULT_ERRORLOG
- printf(" -D DEFAULT_ERRORLOG=\"" DEFAULT_ERRORLOG "\"\n");
- #endif
- #ifdef AP_TYPES_CONFIG_FILE
- printf(" -D AP_TYPES_CONFIG_FILE=\"" AP_TYPES_CONFIG_FILE "\"\n");
- #endif
- #ifdef SERVER_CONFIG_FILE
- printf(" -D SERVER_CONFIG_FILE=\"" SERVER_CONFIG_FILE "\"\n");
- #endif
- }
- #define TASK_SWITCH_SLEEP 10000
- static void destroy_and_exit_process(process_rec *process,
- int process_exit_value)
- {
- /*
- * Sleep for TASK_SWITCH_SLEEP micro seconds to cause a task switch on
- * OS layer and thus give possibly started piped loggers a chance to
- * process their input. Otherwise it is possible that they get killed
- * by us before they can do so. In this case maybe valueable log messages
- * might get lost.
- */
- apr_sleep(TASK_SWITCH_SLEEP);
- apr_pool_destroy(process->pool); /* and destroy all descendent pools */
- apr_terminate();
- exit(process_exit_value);
- }
- static process_rec *init_process(int *argc, const char * const * *argv)
- {
- process_rec *process;
- apr_pool_t *cntx;
- apr_status_t stat;
- const char *failed = "apr_app_initialize()";
- stat = apr_app_initialize(argc, argv, NULL);
- if (stat == APR_SUCCESS) {
- failed = "apr_pool_create()";
- stat = apr_pool_create(&cntx, NULL);
- }
- if (stat != APR_SUCCESS) {
- /* For all intents and purposes, this is impossibly unlikely,
- * but APR doesn't exist yet, we can't use it for reporting
- * these earliest two failures;
- */
- char ctimebuff[APR_CTIME_LEN];
- apr_ctime(ctimebuff, apr_time_now());
- fprintf(stderr, "[%s] [crit] (%d) %s: %s failed "
- "to initial context, exiting\n",
- ctimebuff, stat, (*argv)[0], failed);
- apr_terminate();
- exit(1);
- }
- apr_pool_tag(cntx, "process");
- ap_open_stderr_log(cntx);
- /* Now we have initialized apr and our logger, no more
- * exceptional error reporting required for the lifetime
- * of this server process.
- */
- process = apr_palloc(cntx, sizeof(process_rec)); /* 分配内存,cntx为分配内存的地址 */
- process->pool = cntx;
- apr_pool_create(&process->pconf, process->pool);
- apr_pool_tag(process->pconf, "pconf");
- process->argc = *argc;
- process->argv = *argv;
- process->short_name = apr_filepath_name_get((*argv)[0]);
- return process;
- }
- static void usage(process_rec *process)
- {
- const char *bin = process->argv[0];
- int pad_len = strlen(bin);
- #ifdef SHARED_CORE
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL ,
- "Usage: %s [-R directory] [-D name] [-d directory] [-f file]",
- bin);
- #else
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- "Usage: %s [-D name] [-d directory] [-f file]", bin);
- #endif
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " %*s [-C \"directive\"] [-c \"directive\"]", pad_len, " ");
- #ifdef WIN32
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " %*s [-w] [-k start|restart|stop|shutdown]", pad_len, " ");
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " %*s [-k install|config|uninstall] [-n service_name]",
- pad_len, " ");
- #endif
- #ifdef AP_MPM_WANT_SIGNAL_SERVER
- #ifdef AP_MPM_WANT_SET_GRACEFUL_SHUTDOWN
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " %*s [-k start|restart|graceful|graceful-stop|stop]",
- pad_len, " ");
- #else
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " %*s [-k start|restart|graceful|stop]", pad_len, " ");
- #endif /* AP_MPM_WANT_SET_GRACEFUL_SHUTDOWN */
- #endif
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " %*s [-v] [-V] [-h] [-l] [-L] [-t] [-T] [-S]",
- pad_len, " ");
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- "Options:");
- #ifdef SHARED_CORE
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -R directory : specify an alternate location for "
- "shared object files");
- #endif
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -D name : define a name for use in "
- "<IfDefine name> directives");
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -d directory : specify an alternate initial "
- "ServerRoot");
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -f file : specify an alternate ServerConfigFile");
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -C \"directive\" : process directive before reading "
- "config files");
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -c \"directive\" : process directive after reading "
- "config files");
- #ifdef NETWARE
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -n name : set screen name");
- #endif
- #ifdef WIN32
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -n name : set service name and use its "
- "ServerConfigFile");
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -k start : tell Apache to start");
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -k restart : tell running Apache to do a graceful "
- "restart");
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -k stop|shutdown : tell running Apache to shutdown");
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -k install : install an Apache service");
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -k config : change startup Options of an Apache "
- "service");
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -k uninstall : uninstall an Apache service");
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -w : hold open the console window on error");
- #endif
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -e level : show startup errors of level "
- "(see LogLevel)");
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -E file : log startup errors to file");
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -v : show version number");
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -V : show compile settings");
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -h : list available command line options "
- "(this page)");
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -l : list compiled in modules");
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -L : list available configuration "
- "directives");
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -t -D DUMP_VHOSTS : show parsed settings (currently only "
- "vhost settings)");
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -S : a synonym for -t -D DUMP_VHOSTS");
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -t -D DUMP_MODULES : show all loaded modules ");
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -M : a synonym for -t -D DUMP_MODULES");
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -t : run syntax check for config files");
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL,
- " -T : start without DocumentRoot(s) check");
- destroy_and_exit_process(process, 1);
- }
- int main(int argc, const char * const argv[])
- {
- char c;
- int configtestonly = 0;
- const char *confname = SERVER_CONFIG_FILE;
- const char *def_server_root = HTTPD_ROOT;
- const char *temp_error_log = NULL;
- const char *error;
- process_rec *process;
- server_rec *server_conf;
- apr_pool_t *pglobal; /* 基本池类型 */
- apr_pool_t *pconf;
- apr_pool_t *plog; /* Pool of log streams, reset _after_ each read of conf */
- apr_pool_t *ptemp; /* Pool for temporary config stuff, reset often */
- apr_pool_t *pcommands; /* Pool for -D, -C and -c switches */
- apr_getopt_t *opt;
- apr_status_t rv;
- module **mod;
- const char *optarg;
- APR_OPTIONAL_FN_TYPE(ap_signal_server) *signal_server;
- AP_MONCONTROL(0); /* turn off profiling of startup 代码分析开关 */
- process = init_process(&argc, &argv); /* process_rec表示进程的结构体 */
- pglobal = process->pool;
- pconf = process->pconf;
- ap_server_argv0 = process->short_name;
- #if APR_CHARSET_EBCDIC
- if (ap_init_ebcdic(pglobal) != APR_SUCCESS) {
- destroy_and_exit_process(process, 1);
- }
- #endif
- apr_pool_create(&pcommands, pglobal); /* 创建命令行参数池 */
- apr_pool_tag(pcommands, "pcommands"); /* 给参数池加一个标签 */
- ap_server_pre_read_config = apr_array_make(pcommands, 1, sizeof(char *));
- ap_server_post_read_config = apr_array_make(pcommands, 1, sizeof(char *));
- ap_server_config_defines = apr_array_make(pcommands, 1, sizeof(char *));
- error = ap_setup_prelinked_modules(process);
- if (error) {
- ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_EMERG, 0, NULL, "%s: %s",
- ap_server_argv0, error);
- destroy_and_exit_process(process, 1);
- }
- ap_run_rewrite_args(process);
- /* Maintain AP_SERVER_BASEARGS list in http_main.h to allow the MPM
- * to safely pass on our args from its rewrite_args() handler.
- */
- apr_getopt_init(&opt, pcommands, process->argc, process->argv);
- while ((rv = apr_getopt(opt, AP_SERVER_BASEARGS, &c, &optarg))
- == APR_SUCCESS) {
- char **new;
- switch (c) {
- case 'c':
- new = (char **)apr_array_push(ap_server_post_read_config);
- *new = apr_pstrdup(pcommands, optarg);
- break;
- case 'C':
- new = (char **)apr_array_push(ap_server_pre_read_config);
- *new = apr_pstrdup(pcommands, optarg);
- break;
- case 'd':
- def_server_root = optarg;
- break;
- case 'D':
- new = (char **)apr_array_push(ap_server_config_defines);
- *new = apr_pstrdup(pcommands, optarg);
- /* Setting -D DUMP_VHOSTS is equivalent to setting -S */
- if (strcmp(optarg, "DUMP_VHOSTS") == 0)
- configtestonly = 1;
- /* Setting -D DUMP_MODULES is equivalent to setting -M */
- if (strcmp(optarg, "DUMP_MODULES") == 0)
- configtestonly = 1;
- break;
- case 'e':
- if (strcasecmp(optarg, "emerg") == 0) {
- ap_default_loglevel = APLOG_EMERG;
- }
- else if (strcasecmp(optarg, "alert") == 0) {
- ap_default_loglevel = APLOG_ALERT;
- }
- else if (strcasecmp(optarg, "crit") == 0) {
- ap_default_loglevel = APLOG_CRIT;
- }
- else if (strncasecmp(optarg, "err", 3) == 0) {
- ap_default_loglevel = APLOG_ERR;
- }
- else if (strncasecmp(optarg, "warn", 4) == 0) {
- ap_default_loglevel = APLOG_WARNING;
- }
- else if (strcasecmp(optarg, "notice") == 0) {
- ap_default_loglevel = APLOG_NOTICE;
- }
- else if (strcasecmp(optarg, "info") == 0) {
- ap_default_loglevel = APLOG_INFO;
- }
- else if (strcasecmp(optarg, "debug") == 0) {
- ap_default_loglevel = APLOG_DEBUG;
- }
- else {
- usage(process);
- }
- break;
- case 'E':
- temp_error_log = apr_pstrdup(process->pool, optarg);
- break;
- case 'X':
- new = (char **)apr_array_push(ap_server_config_defines);
- *new = "DEBUG";
- break;
- case 'f':
- confname = optarg;
- break;
- case 'v':
- printf("Server version: %s\n", ap_get_server_description());
- printf("Server built: %s\n", ap_get_server_built());
- destroy_and_exit_process(process, 0);
- case 'V':
- show_compile_settings();
- destroy_and_exit_process(process, 0);
- case 'l':
- ap_show_modules();
- destroy_and_exit_process(process, 0);
- case 'L':
- ap_show_directives();
- destroy_and_exit_process(process, 0);
- case 't':
- configtestonly = 1;
- break;
- case 'T':
- ap_document_root_check = 0;
- break;
- case 'S':
- configtestonly = 1;
- new = (char **)apr_array_push(ap_server_config_defines);
- *new = "DUMP_VHOSTS";
- break;
- case 'M':
- configtestonly = 1;
- new = (char **)apr_array_push(ap_server_config_defines);
- *new = "DUMP_MODULES";
- break;
- case 'h':
- case '?':
- usage(process);
- }
- }
- /* bad cmdline option? then we die */
- if (rv != APR_EOF || opt->ind < opt->argc) {
- usage(process);
- }
- apr_pool_create(&plog, pglobal);
- apr_pool_tag(plog, "plog");
- apr_pool_create(&ptemp, pconf);
- apr_pool_tag(ptemp, "ptemp");
- /* Note that we preflight the config file once
- * before reading it _again_ in the main loop.
- * This allows things, log files configuration
- * for example, to settle down.
- */
- ap_server_root = def_server_root;
- if (temp_error_log) {
- ap_replace_stderr_log(process->pool, temp_error_log);
- }
- server_conf = ap_read_config(process, ptemp, confname, &ap_conftree);
- if (!server_conf) {
- destroy_and_exit_process(process, 1);
- }
- if (ap_run_pre_config(pconf, plog, ptemp) != OK) {
- ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR, 0,
- NULL, "Pre-configuration failed");
- destroy_and_exit_process(process, 1);
- }
- rv = ap_process_config_tree(server_conf, ap_conftree,
- process->pconf, ptemp);
- if (rv == OK) {
- ap_fixup_virtual_hosts(pconf, server_conf);
- ap_fini_vhost_config(pconf, server_conf);
- apr_hook_sort_all();
- if (configtestonly) {
- ap_run_test_config(pconf, server_conf);
- ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, "Syntax OK");
- destroy_and_exit_process(process, 0);
- }
- }
- signal_server = APR_RETRIEVE_OPTIONAL_FN(ap_signal_server);
- if (signal_server) {
- int exit_status;
- if (signal_server(&exit_status, pconf) != 0) {
- destroy_and_exit_process(process, exit_status);
- }
- }
- /* If our config failed, deal with that here. */
- if (rv != OK) {
- destroy_and_exit_process(process, 1);
- }
- apr_pool_clear(plog);
- if ( ap_run_open_logs(pconf, plog, ptemp, server_conf) != OK) {
- ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR,
- 0, NULL, "Unable to open logs");
- destroy_and_exit_process(process, 1);
- }
- if ( ap_run_post_config(pconf, plog, ptemp, server_conf) != OK) {
- ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR, 0,
- NULL, "Configuration Failed");
- destroy_and_exit_process(process, 1);
- }
- apr_pool_destroy(ptemp);
- for (;;) {
- apr_hook_deregister_all();
- apr_pool_clear(pconf);
- for (mod = ap_prelinked_modules; *mod != NULL; mod++) {
- ap_register_hooks(*mod, pconf);
- }
- /* This is a hack until we finish the code so that it only reads
- * the config file once and just operates on the tree already in
- * memory. rbb
- */
- ap_conftree = NULL;
- apr_pool_create(&ptemp, pconf);
- apr_pool_tag(ptemp, "ptemp");
- ap_server_root = def_server_root;
- server_conf = ap_read_config(process, ptemp, confname, &ap_conftree);
- if (!server_conf) {
- destroy_and_exit_process(process, 1);
- }
- if (ap_run_pre_config(pconf, plog, ptemp) != OK) {
- ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR,
- 0, NULL, "Pre-configuration failed");
- destroy_and_exit_process(process, 1);
- }
- if (ap_process_config_tree(server_conf, ap_conftree, process->pconf,
- ptemp) != OK) {
- destroy_and_exit_process(process, 1);
- }
- ap_fixup_virtual_hosts(pconf, server_conf);
- ap_fini_vhost_config(pconf, server_conf);
- apr_hook_sort_all();
- apr_pool_clear(plog);
- if (ap_run_open_logs(pconf, plog, ptemp, server_conf) != OK) {
- ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR,
- 0, NULL, "Unable to open logs");
- destroy_and_exit_process(process, 1);
- }
- if (ap_run_post_config(pconf, plog, ptemp, server_conf) != OK) {
- ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR,
- 0, NULL, "Configuration Failed");
- destroy_and_exit_process(process, 1);
- }
- apr_pool_destroy(ptemp);
- apr_pool_lock(pconf, 1);
- ap_run_optional_fn_retrieve();
- if (ap_mpm_run(pconf, plog, server_conf))
- break;
- apr_pool_lock(pconf, 0);
- }
- apr_pool_lock(pconf, 0);
- destroy_and_exit_process(process, 0);
- return 0; /* Termination 'ok' */
- }
- #ifdef AP_USING_AUTOCONF
- /* This ugly little hack pulls any function referenced in exports.c into
- * the web server. exports.c is generated during the build, and it
- * has all of the APR functions specified by the apr/apr.exports and
- * apr-util/aprutil.exports files.
- */
- const void *suck_in_APR(void);
- const void *suck_in_APR(void)
- {
- extern const void *ap_ugly_hack;
- return ap_ugly_hack;
- }
- #endif
Apache HTTP连接处理流程
总结帖:
首先更新虚拟主机信息,利用process_connection挂钩函数处理。
1. 请求读取 ap_read_request
报文解析,HTTP_PROTOCOL
HTTP请求头:"GET index.html HTTP/1.1"
HTTP请求域:"Accept:*/*"
HTTP请求体:数据
请求信息保存在request_rec中,在请求处理过程中一直存在。
读取报文的处理也在这个地方做,输入处理过滤器。比如说文件分隔符(Windows "/", Unix "\")。
2. 请求处理 ap_process_request
(1):请求解析
①URL字符转义
浏览器会对地址栏输入的特殊字符进行转化,例如空格转换成%20,因此服务器需要将字符转换回去。处理函数是:ap_unescape_url()。
②从URL中剔除/../和/./字符
确保路径成为绝对路径。处理函数是ap_getparents()。
③首次读取URL相关配置信息
从配置系统中查找与该URL相关联的配置信息,为后续的用户权限验证,权限控制做准备。处理函数是ap_location_walk()。
④URL名称转换 translate_name
指令Alias:将URL映射到另外一个特定的URL。mod_writer -- URL重写。
⑤map to storage
确定指定的资源在磁盘上是否存在。
⑥二次读取URL相关配置信息
URL名称转换和映射后,读取改新URL的相关联的配置信息。
⑦head parser
检查HTTP请求头。
(2):安全处理
①access_checker
对客户的访问做基础性的检查限制工作。比如根据IP地址,访问时间。模块是mod_access。
②check_user_id
检查用户的身份权限(用户名和密码)。
③auth_checker
检查用户是否具有访问该资源的权限。
(3):请求准备
①type_checker
确认客户请求的资源类型。mod_mime。
②fixups
补丁修复。可选处理过程。在内容生成以前的最后一步处理操作。
Apache Architecture:
How do we measure performance? 如何测量web服务器的性能?
–Requests per Second 每秒请求次数
–Bandwidth 带宽
–Latency 等待时间
–Concurrency (Scalability) 并发(可扩展)
Building a scalable web server:设计可扩展的web server
handling an HTTP request 处理http请求
–map the URL to a resource 将url映射成资源
–check whether client has permission to access the resource 检查客户端是否有访问资源的权限 –choose a handler and generate a response 选择处理器和生成响应
–transmit the response to the client 发送响应到客户端
–log the request 记录请求日志
must handle many clients simultaneously 必须同时处理多个请求
must do this as fast as possible 必须尽可能快的处理
Resource Pools:资源池
one bottleneck to server performance is the operating system 服务器性能瓶颈之一是操作系统
–system calls to allocate memory, access a file, or create a child process take significant amounts of time
分配内存,访问文件和创建子进程的系统调用会消耗大量时间
–as with many scaling problems in computer systems, caching is one solution
计算机系统中的许多扩展性的问题,缓存是一种解决方法
resource pool: application-level data structure to allocate and cache resources 应用层数据存储的分配和缓存资源
–allocate and free memory in the application instead of using a system call
在应用中分配和释放内存,用来代替使用系统调用
–cache files, URL mappings, recent responses
缓存文件,url映射,最近产生的回复(响应)
–limits critical functions to a small, well-tested part of code
把至关重要的函数限制到小的,充分测试的代码
Multi-Processor Architectures:多处理器体系结构
a critical factor in web server performance is how each new connection is handled
web服务器性能至关重要的因素之一就是每一个连接如何被处理
–common optimization strategy: identify the most commonly-executed code and make this run as fast as possible
通常的优化策略:识别最频繁执行的代码并且让它执行的尽可能快
–common case: accept a client and return several static objects
通常的情况:接受客户端请求,返回几个静态对象
–make this run fast: pre-allocate a process or thread, cache commonly-used files and the HTTP message for the response
让这运行的更快:预分配线程或进程,缓存经常使用的文件和http消息
Connections:连接
must multiplex handling many connections simultaneously 必须同时处理多个连接
–select(), poll(): event-driven, singly-threaded 事件驱动,单线程的
–fork(): create a new process for a connection 为每个连接创建一个进程
–pthread create(): create a new thread for a connection 为每个连接创建一个线程
synchronization among processes/threads 进程/线程间同步
–shared memory: semaphores message passing 共享内存:信号消息传递
Select:
select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
Allows a process to block until data is available on any one of a set of file descriptors.
允许进程阻断,直到一系列文件描述符中的一个准备好
One web server process can service hundards of socket connections
一个web服务器进程可以服务于几百个连接
Event Driven Architecture:事件驱动体系结构
one process handles all events 一个进程处理多个事件
must multiplex handling of many clients and their messages
必须多路处理多个客户端和它们的消息
use select() or poll() to multiplex socket I/O events 使用select或poll来多路复用io事件
provide a list of sockets waiting for I/O events 提供一个socket列表来等待io事件
sleeps until an event occurs on one or more sockets 休眠直到多个socket的事件发生
can provide a timeout to limit waiting time 可以提供一个超时时间来限制等待时间
must use non-blocking system calls 必须使用非阻塞系统调用
some evidence that it can be more efficient than process or thread architectures 一些证据表明比进线程更有效率
Process Driven Architecture:进程驱动体系结构
devote a separate process/thread to each event 每个线程或进程专注于一个事件
master process listens for connections 主进程监听连接
master creates a separate process/thread for each new connection 主进程为每一个连接创建独立的进程/线程
performance considerations 性能考虑
creating a new process involves significant overhead 创建一个进程需要大量的系统开销
threads are less expensive, but still involve overhead 线程相比少了很多,但仍然需要系统开销
may create too many processes/threads on a busy server 可能在一个繁忙的服务器上创建太多的进程/线程
Process/Thread Pool Architecture:进程/线程池体系结构:
master thread 主线程
creates a pool of threads 创建线程池
listens for incoming connections 监听到来的连接
places connections on a shared queue 把连接放到共享队列
processes/threads 进程/线程
take connections from shared queue 从共享队列里获取线程
handle one I/O event for the connection 为每个连接处理io事件
return connection to the queue 返回连接给队列
live for a certain number of events (prevents long-lived memory leaks) 有一定数量的事件(阻止长寿命的内存泄露)
need memory synchronization 需要内存同步
Hybrid Architectures:复杂体系结构:
each process can handle multiple requests 每一个进程处理多个请求
each process is an event-driven server 每一个进程是一个事件驱动服务器
must coordinate switching among events/requests 必须在事件和请求间切换
each process controls several threads 每一个进程控制几个线程
threads can share resources easily 线程间可以容易的共享资源
requires some synchronization primitives 需要一些同步原语
event driven server that handles fast tasks but spawns helper processes for time-consuming requests
事件驱动服务器处理快速任务,而生成助手进程来处理耗时的请求
What makes a good Web Server?:什么东西可以设计一个优秀的web服务器
Correctness 准确性
Reliability 可靠性
Scalability 可扩展性
Stability 稳定性
Speed 速度
Correctness 准确性
Does it conform to the HTTP specification? 符合http协议标准吗?
Does it work with every browser? 对每一个浏览器起作用吗?
Does it handle erroneous input gracefully? 能优雅的处理错误的输入吗?
Reliability 可靠性
Can you sleep at night? 你晚上可以休息吗?
Are you being paged during dinner? 吃晚饭的时候被打断了吗?
It is an appliance? 这是一个设备,工具?
Scalability 可扩展性
Does it handle nominal load? 它可以处理标称的负载吗?
Have you been Slashdotted?
And did you survive?
What is your peak load? 峰值负载时多少?
Speed 速度
Does it feel fast? 速度快吗?
Do pages snap in quickly?
Do users often reload pages?
接触openssl已经有一段时间了,我读过很多源码,感觉不错的也就那么几个,linux内核是其中之 一,openssl也是其中之一。openssl说白了不是什么功能性的东西,而是提供了一个支撑性的底层框架,本质上和linux内核一样,但是和 apache有区别,apache明确的实现了一个功能,就是web服务器,而openssl中我认为最重要的就是它提供的BIO框架和EVP框架,与其 说openssl是一个ssl的实现不如说ssl只不过是openssl框架的一个demo,本质上openssl提供了一套抽象的IO接口,这就是 BIO,还有一套很容易使用的加密解密接口,这就是EVP,最后证实ssl这个协议使用了上述两类接口,如果从这个意义上讲的话,apache其实也是这 样的,在构建顶层的web服务器之前首先实现了一套apr通用框架,甚至memcahce使用的libevent也是这样,这就是说很多的成功的开源的代 码与其说是一种专用的程序倒不如说包含有一套通用的框架,而这个带代码的原始意义最后退化成了使用这个通用框架的一个实例,最起码我看好的几个开源项目都 是这样。策略和机制分离,可扩展性十分棒,用起来十分灵活。
openssl总的来说由三部分组成,一个是BIO,一个是EVP,还有一个就是构建于二者之上的ssl实现。研究这套代码实现给我们可以带来很多的启 示,其中最重要的我想就是IO的意义,还是先拿linux内核来说事吧,其实多任务操作系统提供给用户n台冯诺依曼机器,不但将逻辑抽象给了用户,而且将 大部分cpu指令也抽象给了用户,唯独不包括IO指令,因为外设不是多任务的,外设无法自己处理好多个任务的互斥关系,所有的多个任务共享一个外设,因此 操作系统内核必须进行管理,这就是操作系统内核提供的IO系统调用的作用,也就是说IO指令必须通过系统调用进入操作系统内核之后让内核帮忙处理。以上就 是io的意义,对于openssl提供BIO接口其实意义和上面的差不多,不同的是在原始的io指令发送给操作系统之前提供了用户处理的机会,也就是说用 户可以过滤发送给内核的io系统调用的消息,比如加密,分段等等。如果按照网络的观点,协议栈提供一幅和io截然不同的图景,虽然协议栈最终也要将数据给 了网卡和电缆,但是网卡作为一个外设的意义被更抽象的物理层和链路层取代了,人们不再认为发送网络数据就是共享网卡外设或者电缆,而更多讨论以太网 csma/cd模型,或者ppp链路规程之类的抽象的概念,因此对于网络数据的io,协议栈将一切都抽象了,一切都抽象成了分层的模型,这就是分层模型的 重大意义,人们可以在共享的网卡和电缆上发送不共享的数据,一切井井有条。不像普通的打印机数据发送,网络数据的发送方式截然不同,如果你想发送数据给打 印机,那么首先你必须首先得到打印机的文件描述符,如果另一个用户也要发送数据给打印机,那么操作系统内核的文件系统模块此时将协调它们的顺序,如果网络 数据的话,协议栈就搞定了一切,在用户层不需要类似的文件描述符的概念,而相同意义的概念就是套接字,需要一个ip地址和一个端口号,这就是协议栈的功 能,然后协议栈会将数据一直传输到最底层的物理层,这种方式看起来要比文件的方式更加直观,因此unix/linux中没有将网卡抽象成设备文件,毕竟协 议栈完成了相同的功能。当然套接字接口还是和文件接口相统一的,但是这仅仅是为了操作的统一和方便,普通的设备将通过打开一个设备文件的方式得到文件描述 符,而套接字的方式完全按照协议栈的要求,需要提供一个ip地址和一个端口号以及协议族。在openssl中,你既可以通过普通io的方式也就是说设备文 件的方式扩展传统io模型,也可以使用网络协议栈的方式扩展io模型,实际上openssl框架将二者统一成为一体。
首先,如果使用普通的设备文件方式的io,那么我们只需要BIO就可以了,因为BIO是链式的,所以你可以随意进行io过滤,如果想扩展网络协议栈,鲁迅 式的废话就是同样只需要BIO就可以了,但是条件是最下面的BIO必须是一个套接字,因为我们要在应用层扩展并且只能在应用层扩展,那么就必然要在套接字 之上扩展,ssl就是在普通套接字之上加入了安全保护功能。BIO统一了两种方式,其实目前为止也就这两种io的方式,一种是以可以抽象为设备文件的设备 为载体的IO,这种IO又可细分为块设备和字符设备的IO,对于块设备往往通过文件系统来访问;另一种以网络协议栈方式进行的网络IO,协议栈实现几乎一 切流程,整个协议栈的分层模型和文件系统/设备驱动的分层模型类似,openssl的BIO可以包容这二者的任何一种,并且还可以混合它们,本质上BIO 可以组合成通向世界任何一个地方的管道。
对于EVP,它提供了很多易用的加密解密接口函数,openssl本身已经自带了很多加密解密算法,利用这套EVP接口,你甚至可以轻松实现自己的加密解 密算法。EVP接口中封装了算法的流程,具体的算法由不同的策略来填充,这一点上所有优秀的东西几乎是一致的。
openssl导出的接口是十分容易使用的,基本都是一看声明就知道其用处。以下这个例子读取并且解析一个证书:
X509 * cert = NULL;
BIO *pbio = BIO_new_file((const char*)strPath, "r");
cert = PEM_read_bio_X509(pbio,NULL,NULL,NULL);
tomcat是一个很优秀的轻量级的web引擎和java容器,它本身用java开发,而java是一个面向对象的语言,因此面向对象的语言开发出来的tomcat当然也就继承了面向对象的特征,tomcat还说明,OO真的使得开发变得简单,而且开发出来的产品天生就带着OO的特点,不光开发和维护本身,就连产品也OO了。反过来看apache,标准c语言开发,纯粹过程式的处理,十分符合http的处理流程,单个http请求本身的处理就是串行的过程化的,其实那个年代的东西都是这样,比如c语言,比如unix,当然还包括Tcp/ip协议族,虽然c开发的apache很稳定,性能也不错,但是它的最大的用武之地在于它对静态页面的推送,这也是http协议最基本的初衷,随着web应用日趋复杂,静态页面已经不能解决所有的问题,于是大量的动态页面出来了,像asp,php,jsp等,此时,apache本身解析并推送处理这些动态页面就有些力不从心了,毕竟这不是它的强项,即使设计了相应的模块,那么由于apache设计上的因素,对于动态页面它还是不适合的,不过由于apache性能以及稳定性的优势,它很适合做web服务器的前端,将请求定向给后端复杂的应用服务器或者动态页面容器,然后从后端接收处理后的静态元素,最终推送给客户端web浏览器。从过滤器这个web服务器的一个模块最能看出该web服务器的架构。
apache以往的过滤器都是连成一条链,分为输入过滤链和输出过滤链,前者可以将请求过滤而后者可以将推送给客户的内容过滤。所有的请求无一遗漏的要通过事先安好的所有的过滤钩子,这种方式虽然工作得很不错,但是由于页面都是静态的,所以缺点当然无法暴露,试想动态页面的处理结果,有些钩子只在特定的动态解析结果下起作用,对于大多数不起作用,如果将这个过滤钩子安装的话,很多处理都要在它里面白跑一趟,这很损失性能,或者说这很丑陋。apache新版本的做法是,实现了一个过滤器的动态安装,对于输出过滤器,在内容处理器处理完以后,也就是动态元素已经解析完毕,这个时候就可以根据解析的结果裁定要不要安装输出过滤器了,如果要的话,那么此时才安装,如果不要,就不安装了,而且,新的apache还可以为一个过滤器注册多个策略,在内容处理器的任务完毕之后根据结果裁决最终哪个策略作为过滤器被使用,如此一来就可以在某种意义上实现“每请求”的钩子过滤了,而不是以前的对所有的请求安装相同的过滤钩子,然后在钩子函数内部裁定对这个请求是否起作用。这可以说是apache从静态向动态发展的一项创举,但是apache天生的设计问题决定了它也只能走到这里,下面的主角该是tomcat了,它就是由于要解析动态jsp要求而生的。
既然如此,tomcat的优势就不言自明了,它具有OO的特性,里面的模块设计得特别人性化,特别的OO,它虽然比不上apache的强大与灵活,但是它的设计我感觉比apache的要好得多,毕竟儿子终归要超越父亲的。在tomcat中实现了“每网站”配置,就是每个网站在遵循tomcat总体的大的配置之后还可以拥有自己个性的配置,总体的配置在tomcat安装目录\conf目录下,而每网站的配置在网站\WEB_INF目录下的web.xml文件,之所以如此就是因为tomcat是以动态jsp页面解析为主的,既然是动态的,那么当然需要很多的配置策略才可以进行,最起码你要告诉解析引擎在哪里可以找到一些servlet类,而每个网站使用的类很大程度上是不同的,因此每网站配置就成了必须,如此的解决方案就是,将所有网站的共性配置放到tomcat安装目录\conf\web.xml中,将个性配置放到每网站的配置中。对于过滤,tomcat也使用了不同于apache的方式,在tomcat中,解析引擎根据每网站的配置文件为每一个网站的每一个过滤器生成了一个唯一的实例,之后针对这个网站的所有的请求都使用这一个实例,如果你想设计一个过滤器针对于所有的网站,那么很好办,只需要将过滤器的代码放到所有网站共享的shared\classes目录下即可,然后再在公共的配置文件里配置过滤器,这样的好处在于可以方便的进行不同请求间的通信和同一请求的不同过滤器之间的通信,执行引擎只需要简单得将这些通信转化为类的对象之间的通信,而类的对象之间的通信在java的OO规范中有明确的定义。tomcat之所以可以如此灵活,全在于它的OO特性,在传统的apache中,一切都是以函数调用为基础的,因此很难在执行过程的层面上形象地表达真实世界,而tomcat的执行是以对象为基础的,对象的设计如果设计的好的话必然是现实世界的写照,因此执行过程才显得如此简单而易懂。
因此可以说,apache并没有过时,它是针对http协议的,而tomcat是针对应用的,它和http协议没有冲突,如果你用tomcat作web服务器亲自处理http,那么我感觉不如用apache,如果你将tomcat作为一个serlet容器,这才是它的职责,apache和tomcat的分工是不同,apache使http协议实现的一个例子,而tomcat某种意义上是http上面的东西,为了使http更灵活,使http承载的内容更丰富,http为繁多的应用铺就了一条道路而已。简单的说,apache就是接受http请求,热后解析,最后将结果推送给客户端浏览器,这里的解析就是一个机制,具体怎么解析,http不管,而apache很多时候也是交给了可加载模块,让模块找到合适的解析者,比如tomcat,比如websphere,比如weblogic等等,这些已经不是apache本身的事情了,如果它们能如此分工的话,运行于它们之上的web网站一定很强大。
linux内核差一点就走进了windows的为需求而升级的发展道路从而偏离了它原来 的“只提供机制不提供策略”的道路,那就是在2.4内核时期,内核提供了一个叫做khttpd的内核级别的web服务器,当时linux的性能还不是很 好,比如进程调度算法还是O(n)的方式,而且不能有效利用多处理器的优势,线程的实现还是很拙劣,很多方面没有达到posix的高性能指标,因此当时的 开发者可能就钻进了解决性能瓶颈的牛角尖上,鉴于apache服务器很多都运行在linux上,而且web服务是如此的普遍和重要,因此性能瓶颈也就主要 是apache的性能瓶颈,因此面对这个如此“特化”而又如此不能不管的需求,开发者们只好就事论事地将web服务器单独加速,也就是在内核中实现一个 web加速服务,如果按照这条路走下去,linux最终还会在内核实现ftp,甚至任何用户的需求性的策略,还会把图形处理搬到内核,像windows NT做的那样,不过linux到了2.6内核以后重新找到了自己的坐标,因为没有必要再用那种特化需求的方式解决局部的性能瓶颈了,2.6内核的很多机制 都将性能提高到一个新的水平,以web服务器为例,经测试证明2.6下的web服务器完全比内核实现的khttpd加速服务拥有更好的性能,这证明 linux只提供机制是很好的,用户在这种机制上实现的策略性能并不差,这样责任便分来了,如果说用户的程序在用户使了大劲后性能还是不够好,那么就要看 看内核是否可以提供更好的机制。2.6内核的所作所为完全阻止了开发者们再在某一个方面进行优化,2.6内核的进步是整体的,比如新的调度算法,比如 2.6.17中的splice和tee系统调用,这些都使khttpd没有了存在下去的必要。策略怎么会污染机制呢?想象一下建筑,早先的是混砖式的结构,现在的大厦都成了全框架结构,看看有何不同,住混砖结构房屋的人可能都为装修犯过愁,因为为 了使客厅显得更大,那么就必须打掉一堵墙,可是那堵墙偏偏是承重墙,于是...现在的框架结构就没有这个问题,屋子里除了几个大柱子就没有别的什么了,你 想垒墙就垒墙,想打掉就打掉,这就是机制和策略,框架就是机制,而内部的墙就是策略,如果你把墙垒到了框架里面,势必会影响美观,也会带来不利影响,你将 再次承受承重墙的压力。在操作系统中,尽量不要去通过内核完成一些需求,内核的作用就是为全体进程服务而不是满足用户的个别需求,尽量在用户空间实现需 求,因为这才是需求的实现地,你在内核实现一个你自己进程的需求,如果别的进程不需要这个需求,那人家还是要承受你这个承重墙的压力,只有一个影响全系统 的需求才可以在内核实现,比如杀毒程序,比如防火墙。而khttpd仅仅是一个web加速服务,根本没有资格进入内核独占一地。 在一篇叫做《内核比较:2.4和2.6上的Web服务》的文章中列举了2.6内核的几个新特性:1.超线程支持,多处理支持无论何时都是不错 的;2.NUMA支持;3.线程改进,在内核中增强了posix的线程支持,比如使用NPTL,撤销了管理线程;4.O(1)调度程序,这个就不多说 了;5.I/O的改进,主要是“2.4 中的全局 I/O 请求锁不再使用。在 2.6 中块 I/O 缓冲区(kiobuf)允许 I/O 请求可以比 PAGE_SIZE 大。”;6.异步I/O,这个也不说了;7.这个是比上述6点更高一层次的,就是splice和tee的加入,这俩系统调用可以在用户空间操作内核,其实 就是一个一个内核缓冲的handle。以上7点使khttpd黯然退却,没有必要继续生存。 无论如何,我还是比较欣赏khttpd本身的设计的,很简单,它的代码框架就是:1.启动一些子内核线程在监听80端口(要看内核的khttpd是主 web服务器还是辅web服务器)的套接字上进行accept,并且实时跟踪子内核线程的数量,根据策略进行数量保持,也就是少了创建新的,多了就干 掉;2.子内核线程accept客户浏览器的连接,对于每一个连接进行处理;3.如果内核可以处理,那么直接将结果回写给客户端套接字;4.如果内核处理 不了,那么就呼叫用户端的套接字进行处理,其实就是交给了用户的web服务器。这些流程间涉及到一些技巧,还是看看代码吧:
- void NeedSpawn(struct socket *sock)
- { //InitCount表示已经创建的但是还没有接受连接的子线程;AcceptCount表示正在accept过程中的子线程
- while (atomic_read(&InitCount)+atomic_read(&AcceptCount) {
- kernel_thread(khttpd_child,sock,0);
- atomic_inc(&InitCount);
- }
- }
- int khttpd_child(void *TMP)
- {
- struct socket *sock,*newsock;
- struct sock *sk;
- int error;
- struct proto_ops *ops;
- char *Buffer;
- size_t BufLen;
- struct http_time *httptime;
- current->session = 1;
- current->pgrp = 1;
- current->state |= TASK_WAKE_ONE;
- sprintf(current->comm,"khttpd - request");
- sock = (struct socket*)TMP;
- sk = sock->sk;
- sk->nonagle = 1;
- sk->linger = 1;
- ...
- error=0;
- Buffer = (char*) get_free_page(GFP_KERNEL);
- httptime = kmalloc(sizeof(struct http_time),GFP_KERNEL);
- atomic_dec(&InitCount);
- ...
- memset(httptime,0,sizeof(struct http_time));
- while (1==1)
- {
- if (atomic_read(&AcceptCount)>KHTTPD_MAXACCEPT) //判断是否拥有太多的子内核线程,其实就是http处理线程
- break; //如果太多就不再启动本次的了
- newsock = sock_alloc();
- newsock->type = sock->type;
- sock->ops->dup(newsock,sock);
- ops = newsock->ops;
- atomic_inc(&AcceptCount);
- error = ops->accept(sock,newsock,0);
- atomic_dec(&AcceptCount);
- ...
- error=HandleIncommingConnection(newsock,Buffer,&BufLen,httptime); //实际处理客户端请求
- if (error<0)
- {
- if (HandleUserSpace(newsock,Buffer,BufLen)<0) //如果内核处理出错,那么返回给用户空间去处理,也就是交给用户空间的web服务器
- ErrorXXX(newsock,-error);
- }
- ...
- }
- free_page((unsigned long)Buffer);
- kfree(httptime);
- return 0;
- }
- int HandleIncommingConnection(struct socket *sock, char *Buffer, size_t *BufLen,struct http_time *httptime)
- {
- struct msghdr msg;
- struct iovec iov;
- int len;
- mm_segment_t oldfs;
- struct http_header Head;
- ...//数据初始化
- oldfs = get_fs(); set_fs(KERNEL_DS);
- len = sock_recvmsg(sock,&msg,1024,0);
- set_fs(oldfs);
- ...//出错处理,出错返回500
- /* Check if the request is finished */
- if ((len<4))
- {
- int len2;
- ...//数据初始化
- len2 = sock_recvmsg(sock,&msg,1024,MSG_DONTWAIT);
- set_fs(oldfs);
- interruptible_sleep_on_timeout(&sock->wait,HZ*5);
- if (len2>0)
- len+=len2;
- }
- Buffer[len+1]=0;
- *BufLen = len;
- ParseHeader(Buffer,len,&Head); //解析头部
- if (Head.FileName[0]!=0)
- {
- struct file * filp;
- ...//出错处理,出错返回403
- filp = filp_open(Head.FileName,00,O_RDONLY); //打开客户端需要的文件
- ...//出错处理,出错返回404
- if ((filp!=NULL)&&(filp->f_dentry!=NULL))
- {
- int FileLength,Permission;
- char *Header;
- ...//权限判定,访问控制功能
- FileLength = (int)(filp->f_dentry->d_inode->i_size);
- if (Head.IMS[0]>0)
- {
- time_t TIME;
- TIME=mimeTime_to_UnixTime(Head.IMS);
- ...//出错处理,出错返回304
- }
- Header = HTTPHeader(Head.FileName,FileLength,filp->f_dentry->d_inode->i_mtime,httptime);
- ...//出错处理,出错返回500
- khttp_copy_filp_to_sock(filp,sock,FileLength,Header);
- if (Header)
- kfree(Header);
- fput(filp);
- }
- return 200;
- }
- return -404;
- }
- 注 意以上除了成功返回200之外,别的出错码都不是返回给客户端的,而是在khttpd_child经过判断出错后,交给用户空间再处理一次,因为内核只负 责静态页面的推送而不管别的,因此内核处理不了而出错不代表用户空间的web服务器就处理不了,于是交给khttpd_child中调用的 HandleUserSpace:
- int HandleUserSpace(struct socket *sock, char *ReceivedSoFar, size_t length)
- {
- struct msghdr msg;
- struct iovec iov;
- int len;
- mm_segment_t oldfs;
- char *Buffer;
- struct socket *clientsock;
- /* struct sockaddr_un name;*/
- struct sockaddr_in sin;
- int error,count=0;
- Buffer = (char*) get_free_page(GFP_KERNEL);
- ...//出错处理,真的返回500
- error = sock_create(PF_INET,SOCK_STREAM,IPPROTO_TCP,&clientsock);
- if (error<0)
- printk("Error during creation of socket; terminating\n");
- sin.sin_family = AF_INET;
- sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); //注意,向回环接口也就是127.0.0.1接口发送客户端请求,这样写入的请求就会被用户空间的web服务器受到,某种意义上,这个khttpd更像 是一个web代理,在客户端和用户空间真正的web服务器之间的代理。
- sin.sin_port = htons(KHTTPD_CLIENTPORT); //注意,这个端口一定要在用户空间的web服务器上配置
- error = clientsock->ops->connect(clientsock,(struct sockaddr*)&sin,sizeof(sin),0); //连接真正的用户空间的web服务器,比如apache
- ...
- SendBuffer(clientsock,ReceivedSoFar,length); //把客户端的请求发送真正的用户空间web服务器
- len=1;
- while ((len>0)&&(count<10000)) //这个循环中可以看出内核的khttpd就是一个代理
- {
- ...//数据缓冲区初始化
- oldfs = get_fs(); set_fs(KERNEL_DS);
- len = sock_recvmsg(sock,&msg,4096,MSG_DONTWAIT); //继续从客户端浏览器接受请求
- set_fs(oldfs);
- ...
- if (len>0)
- SendBuffer(clientsock,Buffer,len); //如果有请求则继续向用户空间的web服务器转发
- if (len<-100)
- break;
- ...数据缓冲区初始化
- oldfs = get_fs(); set_fs(KERNEL_DS);
- len = sock_recvmsg(clientsock,&msg,4096,MSG_DONTWAIT); //接受用户空间web服务器的回应
- set_fs(oldfs);
- if (len>0)
- SendBuffer(sock,Buffer,len); //将回应转发给客户端
- ...
- count++;
- }
- sock_release(clientsock);
- free_page((unsigned long)Buffer);
- return 0;
- }
- 注 意,这个函数返回了,如果返回值是负数,那么就说明用户空间的web服务器也处理不了,那就是真的发生了错误,于是就要真的进行错误返回了,其实就是调用 ErrorXXX(newsock,-error)函数,此时从HandleIncommingConnection返回的错误码还保留着:
- void ErrorXXX(struct socket *sock, int Error)
- {
- if (Error == 404)
- {
- Error404(sock);
- return;
- }
- ...
- }
- void Error404(struct socket *sock)
- {
- char Message[]=
- "HTTP/1.0 404 File Not Found\r\nServer: KHTTPD/0.0\r\nContent-type: text/html\r\n\r\nFILE NOT FOUND!!";
- SendBuffer(sock,Message,sizeof(Message)); //真正向客户端返回错误码。
- }
本文转自: http://blog.csdn.net/dog250 天地有如此静穆,我不能大笑而且歌唱
曾经读过n多个开源的代码,包括linux内核,apache,openssl,memcache,libevent,vsftpd,xinetd等等其 中我最喜欢的就是linux内核了,除了linux内核排第二的就是apache,本文我就把欣赏的心得记录下来。
apache中我最喜欢的有两个模块,一个就是它的MPM,一个就是内存管理,mem使得它可以最高效得挖掘操作系统固有的优势,而不用为了接口兼容或者 接口一致而必须提供低效的模拟层,比如windows的进程创建过于低效,但是线程却是操作系统内置的,那么在windows平台上,apache就使用 线程作为mpm的机制,在linux2.6内核上,可以使用线程+进程的方式,而在linux2.4以前可以使用纯进程的经典unix方式来进行多处理。 mpm独立作为一个模块支撑着apache的运行,它是支撑模块,因此它是必须的,典型的,apache要么使用进程,要么使用线程来处理请求,但是绝对 不是进程就是为了稳定,而线程就是高效,而是不同的操作系统拥有不同的选择策略,它很灵活,管理员可以灵活地进行配置,当然线程+进程的方式拥有高效+稳 定的双重优势,最常用的mpm模块就是prefork和worker,具体的内容我就不讨论了,读源代码是最好的方式,另外还可以看一个博 客:http://blog.csdn.net/tingya。在mpm中还有一个很有趣的就是记分板,通过它负责管理的父进程就可以更快捷的管理实际进 行web服务的子进程,这实在是必用管道高效,因为记分板用的是共享内存机制,实际绕开了文件系统,具体工作的时候就是父进程查询记分板得到子进程的状 态,之所以可以如此轻松的完成这样的工作就是因为记分板的构造,每个子进程在记分板上都有一个插槽,父进程可以通过子进程的pid来检索到子进程的插槽从 而得到子进程的信息,子进程同样也需要记分板,它可以将需要通报给父亲的信息写到记分板然后由父进程读取,也可以读取父进程设置进去的信息,比如父进程需 要杀死自己的孩子,它不需要给孩子发送信号,而是通过写自杀信息到该孩子的记分板,等待该孩子看到这个信息以后采取自杀策略,这样的话不需要任何内核机 制,直接靠所有操作系统都提供的东西就可以完成父子通信,另外还可以进行兄弟通信,共享内存机制比管道或者信号来的更好,因为它减少了对操作系统的依赖, 提高了apache的跨平台特性,而且共享内存更加高效,可以做到父子进程内存的零拷贝。
在通信方面,apache可以创建很多套接字排入一个队列或者放入一个池,每次只有一个监听,当监听到用户连接请求的时候,马上就可以将自己转换到服务套 接字,然后从队列或者池中选出下一个套接字进行监听,如此反复,这里讨论的仅仅是通信模型,当然,每个套接字可以是一个进程也可以是一个线程,归根结底要 想高效,还要用到上面提到的mpm机制,这里进程或者线程可以和套接字相对应,但是这种对应不是确定以及一定的,传统的通信模型在apache里面都可以 看到,它非常灵活,曾几何时,只有一个套接字进程监听,只要来一个马上fork一个进程,在子进程里面处理新的连接,但是那个日子一去不复返了,看看 apache吧,看完了你就知道如何进程多处理了。
apache的内存池模型令我陶醉,这是真的,我读过很多源码,可是读每个源码的时候,我都感觉大同小异,只要是网络方面的,必然是select,只要是 进程方面的,必然是...,可是当我开读apache的时候,我改变了这种想法,apache的内存管理就是与众不同,如果你仅仅把目光停留在它的内存管 理算法上,你会发现它和boost没有什么太大的区别,甚至有时都不如stl的,但是它却实现一种思想,一种事务的思想,你再也不用关注于编程细节,只要 知道你的程序在干什么事情就可以了,apache内存管理采用了内存池的思想,在进行内存分配之前,你必须首先创建一个内存池,然后所有的事情就不用你操 心了,一切都在池中进行,内存池的管理是apache里面进行的,用户不必涉及,在分配策略了,内部用到了很多链表等数据结构,还用到了很多巧妙的算法, 比如用内存的大小索引化,在查找的时候从最大的开始,减少了查找的脱靶次数从而提高了效率,但是这些都不是我这里要强调的。传统的看来,谁分配内存谁释放 这已经成了一个成文的规则,但是虽然规则如此严明的写了出来,代码的作者却还是造成了内存泄露,因为这种说法是以内存为中心的,而不是以人的思维为中心 的,因此程序员很容易犯一些错误,哪怕是最低级的错误,想想我们为何写程序,程序肯定是为了帮助人来完成一些事情,而这件事情肯定是很“人性化”的,比如 查询,比如删除,比如恐怖袭击,既然程序执行的都是人的事儿,那么为何不用点人的方式呢?我们人做事从来没有那么小的粒度,我们总是把一个事务作为一个整 体来看待,这不,apache里面的内存池就非常好,如果谁要用内存,那就只管用吧,用内存才是你的事,怎么管理内存不是你的事,而是你上级的事,这个现 象很好理解,举个例子,我在一家电脑公司任职,我平时要用电脑,打印机,打印纸等等,这些资源并不需要我自己管理,而是我的上级帮我管理,比如我的上级给 了我一台台式机和一台笔记本的份额,那么我根本不用管别的,份额内直接用就是了。在apache中,内存分配都在内存池中,一段代码代表一个业务逻辑,它 需要内存的话并不需要负责释放,而是这段代码的上级来负责释放,实际上apache中根本没有显式的释放给操作系统的内存释放,所有内存释放都是释放给内 存池的,如果要开始一个业务逻辑,那么必然需要先申请有一个内存池,在这个业务逻辑中所有的内存需求都从这个池中分配,业务逻辑本身并不需要和操作系统交 互,而由池的管理代码负责交互,内存释放时,释放到池中,这些内存还可以被该业务逻辑重新利用,等到这个业务逻辑结束的时候,内存池被销毁,池销毁的时 候,池中的内存将全部还给操作系统,这丝毫不用业务逻辑本身操任何心。内存池就好像一个封装,你只需要大胆的用内存就可以了,不够了池可以帮你从操作系统 要,多余了可以先存着,等到业务逻辑结束的时候,池销毁的时候,所有内存一并销毁,而销毁操作对用户即业务逻辑来说是不可见的。
apache的内存池管理方式使我想到了操作系统的内存管理方式,apache的libapr本身就好像一个操作系统,它分配资源不会被业务逻辑看到,业 务逻辑只需要等待结果就可以了,可是现在的任何操作系统却美誉这么“人性化”,可以这么说,apache是以业务逻辑即事务为中心的,其实它就是以人为本 的,而传统的操作系统由于收到强大的unix的影响,还是以任务为中心的,unix创立的时代是个必须以任务为中心的时代,但是现在还是那样的吗?虽然我 不很喜欢windows,但是我发现windows已经迈出了超越unix的第一步,当然linux早就超越了unix。
apache在我心中就是老二,linux内核永远是老大,不过如果单挑某个方面的话,不能否认apache远远超越了linux内核,在你想远离人群的时候,读读linux内核,当你想融入喜欢你的人的时候,读读apache,试试看。
关于作者
张中庆,目前主要的研究方向是嵌入式浏览器,移动中间件以及大规模服务器设计。目前正在进行Apache的源代码分析,计划出版《Apache源代码全景分析》上下册。Apache系列文章为本书的草案部分,对Apache感兴趣的朋友可以通过flydish1234 at sina.com.cn与之联系!
如果你觉得本文不错,请点击文后的“推荐本文”链接!!