分类: C/C++
2010-07-22 23:59:43
最近一段时间在研究 nginx ,对它做了一些分析和总结,这篇文章是从nginx 加载模块的角度来分析它。闲话少叙,让我们直入正题吧。
一、模块的分类
Nginx中有很多模块,每个模块都有其各自的功能,不过大体上可以将所有模块分为四大类。它们分别是,core模块,event模块,http模块和mail模块。
Core模块主要作用是加载全局信息,建立socket监听,启动进程,加载其它子模块等等。
core类型的模块有以下几个:
ngx_core_module,该模块是nginx的主模块,系统的启动、停止都是从该模块开始的。
ngx_errlog_module,该模块用于处理错误日志。
ngx_conf_module,该模块虽然是配置模块,但我们也把它当做core模块。该模块用于加载配置信息。
ngx_events_module,该模块用于加载event模块。
ngx_http_module,该模块用于加载
http 模块。
ngx_mail_module,该模块用于加载
mail 模块。
Event类模块用于事件处理。
http类模块用于http相关的处理。
Mail类模块用于 mail 相关的处理。
二、模块的加载过程
第一步,Nginx在启动时会将所有用到的模块保存在一个静态数组中。这个数组是在nginx编译的时候根据操作系统、配置文件、模块文件等信息自动生成。它定义在 nginx目录/objs/ngx_modules.c文件中。让我们来看一下它的大体样子如下:
ngx_module_t *ngx_modules[] = {
&ngx_core_module,
&ngx_errlog_module,
&ngx_conf_module,
&ngx_events_module,
&ngx_event_core_module,
&ngx_epoll_module,
&ngx_http_module,
&ngx_http_core_module,
……
NULL
};
第二步,Nginx会对这些模块进行点数,可以参见nginx.c 文件的319~322行,具体如下:
ngx_max_module = 0; //初始化最大模块数
for (i = 0; ngx_modules[i];
i++) { // 遍历模块数组
ngx_modules[i]->index = ngx_max_module++; //对模块进行点数
}
这里需要说明模块结构体中的几个主要字段的含义:
struct ngx_module_s {
// nginx模块进行两种编码,一种是它在全部模块的编号,
// 另一种是它在同一类型模块中的编号
ngx_uint_t ctx_index; //同类型模块的编号
ngx_uint_t index; //所有模块的编号
……
ngx_uint_t version; //使用的版本
/////////////////////////////////////////////////////////////////////////////////////////////////
void *ctx; //上下文
ngx_command_t *commands; //指令
ngx_uint_t type; //模块类型
/////////////////////////////////////////////////////////////////////////////////////////////////
ngx_int_t (*init_master)(ngx_log_t *log);
ngx_int_t (*init_module)(ngx_cycle_t *cycle);
ngx_int_t (*init_process)(ngx_cycle_t *cycle);
ngx_int_t (*init_thread)(ngx_cycle_t *cycle);
void (*exit_thread)(ngx_cycle_t
*cycle);
void (*exit_process)(ngx_cycle_t
*cycle);
void (*exit_master)(ngx_cycle_t *cycle);
///////////////////////////////////////////////////////////////////////////////////////////////////
……
};
第三步,在初始化全局变量时,调用CORE模块的上下文中的create_conf和init_conf方法。代码参见nginx_cycle.c文件的第210~225行 和 274~290行。具体代码如下:
for (i = 0; ngx_modules[i];
i++) { //遍历所有模块
//如果模块类型不是CORE则换下一个模块
if (ngx_modules[i]->type !=
NGX_CORE_MODULE) {
continue;
}
//得到模块的上下文
module = ngx_modules[i]->ctx;
//如果已经设置了create_conf回调函数,则执行该函数
if (module->create_conf) {
rv = module->create_conf(cycle);
if (rv == NULL) {
ngx_destroy_pool(pool);
return NULL;
}
//将结果放到全局变量中保存
cycle->conf_ctx[ngx_modules[i]->index] = rv;
}
}
Init_conf与create_conf似类,如下:
for (i = 0; ngx_modules[i];
i++) {
if (ngx_modules[i]->type !=
NGX_CORE_MODULE) {
continue;
}
module = ngx_modules[i]->ctx;
if (module->init_conf) {
if (module->init_conf(cycle,
cycle->conf_ctx[ngx_modules[i]->index])
== NGX_CONF_ERROR)
{
environ = senv;
ngx_destroy_cycle_pools(&conf);
return NULL;
}
}
}
那么这些CORE模块的
create_conf 方法是什么呢?还是让我们看代码吧。先让我们看看CORE上下文结构体的具体内容,如下:
typedef struct {
ngx_str_t name; //上下文名子
void *(*create_conf)(ngx_cycle_t
*cycle); //创建函数
char *(*init_conf)(ngx_cycle_t
*cycle, void *conf);//初始化函数
} ngx_core_module_t;
ngx_core_module模块的上下文件如下:
static ngx_core_module_t ngx_core_module_ctx = {
ngx_string("core"),
ngx_core_module_create_conf,
ngx_core_module_init_conf
};
ngx_errlog_module模块的上下文件如下:
static ngx_core_module_t ngx_errlog_module_ctx = {
ngx_string("errlog"),
NULL,
NULL
};
ngx_events_module模块的上下文件如下:
static ngx_core_module_t ngx_events_module_ctx = {
ngx_string("events"),
NULL,
NULL
};
ngx_http_module模块的上下文件如下:
static ngx_core_module_t ngx_http_module_ctx = {
ngx_string("http"),
NULL,
NULL
};
ngx_mail_module模块的上下文件如下:
static ngx_core_module_t ngx_mail_module_ctx = {
ngx_string("mail"),
NULL,
NULL
};
第四步,
从上面的代码可以看出,除了 ngx_core_module 模块外,其它的CORE模块没有实现create_conf 与 init_conf函数。这是因为它们在配置文件中表式的是配置块不是指令,所以不需要实现该函数。为了下面讲解的更清楚,下面我对nginx的配置文件分析一下。这里我们附一段nginx最简单的配置文件,如下:
#运行用户
user nobody nobody;
#启动进程
worker_processes 2;
#全局错误日志及PID文件
error_log logs/error.log notice;
pid logs/nginx.pid;
#工作模式及连接数上限
events {
use epoll;
worker_connections 1024;
}
#设定http服务器,利用它的反向代理功能提供负载均衡支持
http {
#设定mime类型
include conf/mime.types;
default_type application/octet-stream;
#设定日志格式
log_format main
'$remote_addr - $remote_user [$time_local] '
'"$request" $status $bytes_sent '
'"$http_referer" "$http_user_agent" '
'"$gzip_ratio"';
log_format download '$remote_addr - $remote_user [$time_local]
'
'"$request" $status $bytes_sent '
'"$http_referer" "$http_user_agent" '
'"$http_range" "$sent_http_content_range"';
#设定请求缓冲
client_header_buffer_size 1k;
large_client_header_buffers 4 4k;
#开启gzip模块
gzip on;
gzip_min_length 1100;
gzip_buffers 4 8k;
gzip_types text/plain;
output_buffers 1 32k;
ostpone_output 1460;
#设定access log
access_log logs/access.log main;
client_header_timeout
client_body_timeout
send_timeout
sendfile
on;
tcp_nopush
on;
tcp_nodelay
on;
keepalive_timeout 65;
#设定负载均衡的服务器列表
upstream mysvr {
#weigth 参数表示权值,权值越高被分配到的几率越大
#本机上的Squid开启3128端口
server 192.168.8.1:3128 weight=5;
server 192.168.8.2:80 weight=1;
server 192.168.8.3:80 weight=6;
}
#设定虚拟主机
server {
listen
80;
server_name 192.168.8.1
charset gb2312;
#设定本虚拟主机的访问日志
access_log logs/ main;
#如果访问 /img/*, /js/*, /css/* 资源,则直接取本地文件,不通过squid
#如果这些文件较多,不推荐这种方式,因为通过squid的缓存效果更好
location ~ ^/(img|js|css)/ {
root /data3/Html;
expires 24h;
}
#对 "/" 启用负载均衡
location / {
proxy_pass
proxy_redirect
off;
proxy_set_header
Host $host;
proxy_set_header
X-Real-IP $remote_addr;
proxy_set_header
X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size
client_body_buffer_size 128k;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffer_size 4k;
proxy_buffers
4 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
}
#设定查看Nginx状态的地址
location /NginxStatus {
stub_status
on;
access_log
on;
auth_basic
"NginxStatus";
auth_basic_user_file conf/htpasswd;
}
}
}
查看 Nginx 运行状态 输入地址 ,输入验证帐号密码,即可看到类似如下内容:
Active connections: 328 //目前活跃的连接数
server accepts handled requests
9309
8982 28890 //第三个数字表示Nginx运行到当前时间接受到的总请求数,如果快达到了上限,就需要加大上限值了。
Reading: 1 Writing: 3 Waiting: 324 //Nginx的队列状态
第五步,下面我们来看ngx_core_module_t模块的create_conf函数(即ngx_core_module_create_conf)都做些什么呢?很简单,就是初始化ngx_core_conf_t结构体。该结构体存放的字段就是配置文件中的核心指令,如 deamon,master等等。
static void *
ngx_core_module_create_conf(ngx_cycle_t *cycle)
{
ngx_core_conf_t *ccf;
ccf =
ngx_pcalloc(cycle->pool, sizeof(ngx_core_conf_t));
……
ccf->daemon =
NGX_CONF_UNSET;
ccf->master =
NGX_CONF_UNSET;
ccf->timer_resolution = NGX_CONF_UNSET_MSEC;
ccf->worker_processes = NGX_CONF_UNSET;
ccf->debug_points
= NGX_CONF_UNSET;
ccf->rlimit_nofile = NGX_CONF_UNSET;
ccf->rlimit_core
= NGX_CONF_UNSET_SIZE;
ccf->rlimit_sigpending = NGX_CONF_UNSET;
ccf->user =
(ngx_uid_t) NGX_CONF_UNSET_UINT;
ccf->group =
(ngx_gid_t) NGX_CONF_UNSET_UINT;
#if (NGX_THREADS)
ccf->worker_threads = NGX_CONF_UNSET;
ccf->thread_stack_size = NGX_CONF_UNSET_SIZE;
#endif
if
(ngx_array_init(&ccf->env, cycle->pool, 1, sizeof(ngx_str_t))
!= NGX_OK)
{
return NULL;
}
return ccf;
}
执行完该函数后,就会调用cycle->conf_ctx[ngx_modules[i]->index]
= rv;将结果存放到conf_ctx数组中的相应元素中。
第六步,做完上面的工作后,nginx会调用nginx.c文件的第246~251行,填充ngx_conf_t结构体。代码如下:
conf.ctx =
cycle->conf_ctx;
conf.cycle = cycle;
conf.pool = pool;
conf.log = log;
conf.module_type =
NGX_CORE_MODULE;
conf.cmd_type =
NGX_MAIN_CONF;
之后以 ngx_conf_t结构为参数,调用 ngx_conf_parse函数。那么这个函数又做什么事呢?通过名子我们就可以猜个八九不离十,就是解析配置文件。我们来看看它的代码,如下:
char *
ngx_conf_parse(ngx_conf_t
*cf, ngx_str_t *filename)
{
……
for ( ;; ) {
rc = ngx_conf_read_token(cf);
……
rc = ngx_conf_handler(cf, rc);
……
}
……
return NGX_CONF_OK;
}
这个函数很大,我将其中各种逻辑判断都去掉了,只保留它的主干,这样便于我们分析问题。至于其它的代码就请大家自行分析吧。
现在看这个代码就只剩下了两个函数了,一个是ngx_conf_read_token。它起什么作用呢?就是读配置文件,找到一个指令就返回。别一个是ngx_conf_handler。它的作用是处理由ngx_conf_read_token读出的指令。而ngx_conf_parse函数的作用就是循环不停的从配置文件中读取指令,然后进行指令处理,直到结束。
ngx_conf_read_token这个函数我就不分析了,下面主要看一下ngx_conf_handler函数。代码如下:
static ngx_int_t
ngx_conf_handler(ngx_conf_t
*cf, ngx_int_t last)
{
……
name = cf->args->elts; //得到指令,该值是从ngx_conf_read_token中得到的
for (i = 0; ngx_modules[i]; i++) {//遍历所有模块
……
/* look up the directive in the appropriate
modules */
if (ngx_modules[i]->type !=
NGX_CONF_MODULE
&& ngx_modules[i]->type
!= cf->module_type)//过滤掉不必要的模块
{
continue;
}
……
cmd = ngx_modules[i]->commands;//得到模块的命令集
for ( /* void */ ; cmd->name.len;
cmd++) {//遍历命令集
……
if (ngx_strcmp(name->data, cmd->name.data) != 0) {
continue;
}
/* set up the directive's
configuration context */
conf = NULL;
if (cmd->type &
NGX_DIRECT_CONF) {
conf = ((void **)
cf->ctx)[ngx_modules[i]->index];
} else if (cmd->type &
NGX_MAIN_CONF) {
conf = &(((void **)
cf->ctx)[ngx_modules[i]->index]);
} else if (cf->ctx) {
confp = *(void **) ((char *)
cf->ctx + cmd->conf);
if (confp) {
conf =
confp[ngx_modules[i]->ctx_index];
}
}
rv = cmd->set(cf, cmd, conf);//执行指令的具体处理函数
……
}
……
}
从代码中我们可以看出它的意思是遍历模块所有命令,直到找个对应的处理命令,之后执行该命令的具体处理函数。
第七步,下面我们再看一下command结构体的是如何定义的,可能就更清楚上面的代码的意思了,结构定义如下:
struct ngx_command_s {
ngx_str_t name; //命令名
ngx_uint_t type; //可以在那些模块中执行
char *(*set)(ngx_conf_t *cf,
ngx_command_t *cmd, void *conf); //命令的处理函数
ngx_uint_t conf; //
ngx_uint_t offset; //偏移位置
void *post;
};
看看CORE模块们是如何定义他们的命令的吧。ngx_core_module模块的命令如下:
static ngx_command_t ngx_core_commands[] = {
{ ngx_string("daemon"),
NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
0,
offsetof(ngx_core_conf_t, daemon),
NULL },
……
ngx_null_command
};
ngx_errlog_module模块的命令如下:
static ngx_command_t ngx_errlog_commands[] = {
{ngx_string("error_log"),
NGX_MAIN_CONF|NGX_CONF_1MORE,
ngx_error_log,
0,
0,
NULL},
ngx_null_command
};
ngx_events_module模块的命令如下:
static ngx_command_t ngx_events_commands[] = {
{ ngx_string("events"),
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
ngx_events_block,
0,
0,
NULL },
ngx_null_command
};
ngx_http_module模块的命令如下:
static ngx_command_t ngx_http_commands[] = {
{ ngx_string("http"),
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
ngx_http_block,
0,
0,
NULL },
ngx_null_command
};
ngx_mail_module模块的命令如下:
static ngx_command_t ngx_mail_commands[] = {
{ ngx_string("mail"),
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
ngx_mail_block,
0,
0,
NULL },
{ ngx_string("imap"),
NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
ngx_mail_block,
0,
0,
NULL },
ngx_null_command
};
我们以ngx_events_module模块为例子,来看看它的命令处理函数做些什么事吧。代码如下:
static char *
ngx_events_block(ngx_conf_t *cf, ngx_command_t *cmd, void
*conf)
{
……
//对NGX_EVENT_MODULE模块进行点数
ngx_event_max_module
= 0;
for (i = 0;
ngx_modules[i]; i++) {
if (ngx_modules[i]->type !=
NGX_EVENT_MODULE) {
continue;
}
ngx_modules[i]->ctx_index = ngx_event_max_module++;
}
ctx =
ngx_pcalloc(cf->pool, sizeof(void *));
//分配数组
*ctx =
ngx_pcalloc(cf->pool, ngx_event_max_module * sizeof(void *));
*(void **) conf =
ctx;//将ctx值存放到conf指向的地址中
for (i = 0;
ngx_modules[i]; i++) {
if
(ngx_modules[i]->type != NGX_EVENT_MODULE) {
continue;
}
m =
ngx_modules[i]->ctx;
//将模块的create_conf赋值给NGX_EVENT_MODULE数组上下文的相应元素中
if
(m->create_conf) {
(*ctx)[ngx_modules[i]->ctx_index] = m->create_conf(cf->cycle);
if
((*ctx)[ngx_modules[i]->ctx_index] == NULL) {
return
NGX_CONF_ERROR;
}
}
}
pcf = *cf;
cf->ctx = ctx;
cf->module_type =
NGX_EVENT_MODULE;
cf->cmd_type =
NGX_EVENT_CONF;
rv =
ngx_conf_parse(cf, NULL); //开始分析 event 配置信息,是不是与ngx_core_module
模块很相似?
*cf = pcf;
if (rv !=
NGX_CONF_OK)
return rv;
//调用初始化函数
for (i = 0;
ngx_modules[i]; i++) {
if
(ngx_modules[i]->type != NGX_EVENT_MODULE) {
continue;
}
m =
ngx_modules[i]->ctx;
if
(m->init_conf) {
rv =
m->init_conf(cf->cycle, (*ctx)[ngx_modules[i]->ctx_index]);
if (rv !=
NGX_CONF_OK) {
return
rv;
}
}
}
return NGX_CONF_OK;
}
然后就是对NGX_EVENT_MODULE 类型模块的分析。如ngx_event_core_module模块的加载过程。不过这些过程与上面的分析过程是一样的, 这里就不再赘述了。
至此,我对nginx模块的加载过程已经讲完了。这篇文件写的时间很仓促,希望大家多多指教。J