nginx的启动流程分析(二)

原创文章,转载请注明: 转载自pagefault

本文链接地址: nginx的启动流程分析(二)

接上篇,这篇主要来看nginx的网络部分的初始化

首先是ngx_http_optimize_servers函数,这个函数是在ngx_http_block中被调用的,它的主要功能就是创建listening结构,然后初始化。这里ngx_listening_t表示一个正在监听的句柄以及它的上下文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static ngx_int_t
ngx_http_optimize_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf,
     ngx_array_t *ports)
{
     ngx_uint_t             p, a;
     ngx_http_conf_port_t  *port;
     ngx_http_conf_addr_t  *addr;
 
     if (ports == NULL) {
         return NGX_OK;
     }
 
     port = ports->elts;
     for (p = 0; p < ports->nelts; p++) {
..................................................
//初始化listen结构
         if (ngx_http_init_listening(cf, &port[p]) != NGX_OK) {
             return NGX_ERROR;
         }
     }
 
     return NGX_OK;
}


通过上面可以看到核心的处理都是在ngx_http_init_listening中的,我们来看它的代码片段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
static ngx_int_t
ngx_http_init_listening(ngx_conf_t *cf, ngx_http_conf_port_t *port)
{
.....................................
     while (i < last) {
 
         if (bind_wildcard && !addr[i].opt.bind) {
             i++;
             continue ;
         }
//这个函数里面将会创建,并且初始化listen结构
         ls = ngx_http_add_listening(cf, &addr[i]);
         if (ls == NULL) {
             return NGX_ERROR;
         }
......................................................
         switch (ls->sockaddr->sa_family) {
 
#if (NGX_HAVE_INET6)
         case AF_INET6:
             if (ngx_http_add_addrs6(cf, hport, addr) != NGX_OK) {
                 return NGX_ERROR;
             }
             break ;
#endif
         default : /* AF_INET */
//初始化虚拟主机相关的地址,设置hash等等.
             if (ngx_http_add_addrs(cf, hport, addr) != NGX_OK) {
                 return NGX_ERROR;
             }
             break ;
         }
 
         addr++;
         last--;
     }
.........................
}

然后就来看真正做事的ngx_http_add_listening函数。这里我们只关注最重要的部分, 就是设置listen 的handler,要注意这个handler并不是accept handler,而是当accpet完之后,处理accept到的句柄的操作.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static ngx_listening_t *
ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr)
{
    ngx_listening_t           *ls;
     ngx_http_core_loc_conf_t  *clcf;
     ngx_http_core_srv_conf_t  *cscf;
//创建listen结构体
     ls = ngx_create_listening(cf, &addr->opt.u.sockaddr, addr->opt.socklen);
     if (ls == NULL) {
         return NULL;
     }
 
     ls->addr_ntop = 1;
//设置listen句柄的回调
     ls->handler = ngx_http_init_connection;
...............................................
//设置对应的属性,backlog,读写buf.
     ls->backlog = addr->opt.backlog;
     ls->rcvbuf = addr->opt.rcvbuf;
     ls->sndbuf = addr->opt.sndbuf;
........................................................
}

可是我们注意到在上面的函数中并没有创建listen socket句柄,而且也没有将句柄挂载到事件驱动中,这些动作都是在后面进行的,我们慢慢来看。

ngx_http_block结束后,也就是继续ngx_init_cycle下面的处理, 下面这部分就是创建socket,然后设置flag,最终绑定到listen结构体中.

1
2
3
4
5
6
7
if (ngx_open_listening_sockets(cycle) != NGX_OK) {
     goto failed;
}
 
if (!ngx_test_config) {
     ngx_configure_listening_sockets(cycle);
}

当这些都结束后,就该挂载listen 句柄到事件处理器里面了。这个动作是在 ngx_worker_process_init中进行的,这个函数是在子进程被fork出来之后马上被调用的。而在这个函数中会执行所有模块的init_process方法.

1
2
3
4
5
6
7
8
9
     for (i = 0; ngx_modules[i]; i++) {
         if (ngx_modules[i]->init_process) {
//进程初始化
             if (ngx_modules[i]->init_process(cycle) == NGX_ERROR) {
                 /* fatal */
                 exit (2);
             }
         }
     }

而nginx的event模块包含一个init_process,也就是ngx_event_process_init.这个函数就是nginx的驱动器,他初始化事件驱动器,连接池,定时器,以及挂在listen 句柄的回调函数。

这个函数比较长,我们来一段段的看。
下面这一段是判断是否使用mutex锁,主要是为了控制负载均衡,nginx多进程的负载均衡我前面的blog有介绍过。这里要注意这个变量,因为下面还会用到。

1
2
3
4
5
6
7
8
9
     if (ccf->master && ccf->worker_processes > 1 && ecf->accept_mutex) {
//使用mutex控制进程的负载均衡.
         ngx_use_accept_mutex = 1;
         ngx_accept_mutex_held = 0;
         ngx_accept_mutex_delay = ecf->accept_mutex_delay;
 
     } else {
         ngx_use_accept_mutex = 0;
     }

下面这段是初始化定时器以及event module(epoll etc).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//定时器初始化
     if (ngx_event_timer_init(cycle-> log ) == NGX_ERROR) {
         return NGX_ERROR;
     }
 
//event module的初始化.
     for (m = 0; ngx_modules[m]; m++) {
         if (ngx_modules[m]->type != NGX_EVENT_MODULE) {
             continue ;
         }
 
         if (ngx_modules[m]->ctx_index != ecf->use) {
             continue ;
         }
 
         module = ngx_modules[m]->ctx;
//初始化模块
         if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) {
             /* fatal */
             exit (2);
         }
 
         break ;
     }

下面这段是初始化连接池,以及对应的读写事件的初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
//创建连接池
     cycle->connections =
         ngx_alloc( sizeof (ngx_connection_t) * cycle->connection_n, cycle-> log );
     if (cycle->connections == NULL) {
         return NGX_ERROR;
     }
 
     c = cycle->connections;
//创建所有读事件
     cycle->read_events = ngx_alloc( sizeof (ngx_event_t) * cycle->connection_n,
                                    cycle-> log );
     if (cycle->read_events == NULL) {
         return NGX_ERROR;
     }
 
     rev = cycle->read_events;
//初始化读事件
     for (i = 0; i < cycle->connection_n; i++) {
         rev[i].closed = 1;
//防止stale event
         rev[i].instance = 1;
#if (NGX_THREADS)
         rev[i].lock = &c[i].lock;
         rev[i].own_lock = &c[i].lock;
#endif
     }
//创建写事件
     cycle->write_events = ngx_alloc( sizeof (ngx_event_t) * cycle->connection_n,
                                     cycle-> log );
     if (cycle->write_events == NULL) {
         return NGX_ERROR;
     }
 
     wev = cycle->write_events;
//初始化写事件
     for (i = 0; i < cycle->connection_n; i++) {
         wev[i].closed = 1;
#if (NGX_THREADS)
         wev[i].lock = &c[i].lock;
         wev[i].own_lock = &c[i].lock;
#endif
     }
 
     i = cycle->connection_n;
     next = NULL;
//初始化连接池
     do {
         i--;
//链表
         c[i].data = next;
//每一个连接的读写事件对应cycle的读写事件
         c[i].read = &cycle->read_events[i];
         c[i].write = &cycle->write_events[i];
         c[i].fd = (ngx_socket_t) -1;
 
         next = &c[i];
 
#if (NGX_THREADS)
         c[i].lock = 0;
#endif
     } while (i);
//设置free 连接
     cycle->free_connections = next;
     cycle->free_connection_n = cycle->connection_n;

下面这段初始化listen 事件 ,创建socket句柄,绑定事件回调,然后加入到事件驱动中,这里比较关键的就是如果使用了ngx_use_accept_mutex,则现在不会将事件加入到epoll中,而是等到在ngx_process_events_and_timers中将句柄加入,这是因为nginx为了防止惊群,采取了串行化处理accpet,也就是同时只有一个listen句柄会休眠在epoll_wait上等待连接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
     ls = cycle->listening.elts;
//开始遍历listen
     for (i = 0; i < cycle->listening.nelts; i++) {
//从连接池取得连接
         c = ngx_get_connection(ls[i].fd, cycle-> log );
 
         if (c == NULL) {
             return NGX_ERROR;
         }
 
         c-> log = &ls[i]. log ;
 
         c->listening = &ls[i];
         ls[i].connection = c;
 
         rev = c->read;
 
         rev-> log = c-> log ;
         rev->accept = 1;
.....................................
//设置listen句柄的事件回调,这个回调里面会accept,然后进行后续处理,这个函数是nginx事件驱动的第一个函数
         rev->handler = ngx_event_accept;
//如果默认使用mutex,则会继续下面操作。
         if (ngx_use_accept_mutex) {
             continue ;
         }
 
         if (ngx_event_flags & NGX_USE_RTSIG_EVENT) {
             if (ngx_add_conn(c) == NGX_ERROR) {
                 return NGX_ERROR;
             }
 
         } else {
//加可读事件到事件处理
             if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
                 return NGX_ERROR;
             }
         }
}

然后就是ngx_trylock_accept_mutex代码,这个我以前已经分析过了,这里就不详细分析了,只要知道它会获得一把锁,然后调用ngx_enable_accept_events将listen 事件加入到事件驱动框架中.

在接下来就是进程初始化的操作了,初始化完进程,然后休眠在epoll_wait上等待连接的到来,这部分代码我在前面的nginx进程模型里面有分析.

相关日志:

  1. nginx的启动流程分析(一)
  2. nginx中处理http header详解(1)
  3. nginx中slab分配器的实现
  4. nginx 0.8.x稳定版对linux aio的支持
  5. nginx中处理http header详解(2)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值