Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3348844
  • 博文数量: 1450
  • 博客积分: 11163
  • 博客等级: 上将
  • 技术积分: 11101
  • 用 户 组: 普通用户
  • 注册时间: 2005-07-25 14:40
文章分类

全部博文(1450)

文章存档

2017年(5)

2014年(2)

2013年(3)

2012年(35)

2011年(39)

2010年(88)

2009年(395)

2008年(382)

2007年(241)

2006年(246)

2005年(14)

分类: 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 文件的319322行,具体如下:

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_confinit_conf方法。代码参见nginx_cycle.c文件的第210225行 和 274290行。具体代码如下:

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_confcreate_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  3m;

client_body_timeout    3m;

send_timeout          3m;

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    10m;

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文件的第246251行,填充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

阅读(3223) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~