启动流程

main函数执行过程:

  • ngx_get_options()解析命令参数
  • ngx_time_init()初始化时间,ngx_cached_time也在这里
  • ngx_log_init()初始化日志
  • 清零ngx_cycle,并为ngx_cycle.pool创建1024B的内存池
  • ngx_save_argv()保存命令行参数至全局变量ngx_os_argv、ngx_argc、ngx_argv中
  • ngx_process_options()初始化字段
  • ngx_os_init()初始化系统相关变量
  • ngx_crc32_table_init()初始化CRC表(后续的CRC校验通过查表进行,效率高)
  • ngx_add_inherited_sockets()继承sockets
  • 初始化每个module的index,并计算ngx_max_module
  • ngx_init_cycle()进行初始化
  • 若有信号,进入ngx_signal_process()处理,调用ngx_init_signals()初始化信号;若无信号,继承sockets,设置守护进程标识,ngx_daemon()创建守护进程
  • ngx_create_pidfile()创建进程记录文件
  • 进入程序主循环:若为NGX_PROCESS_SINGLE=1模式,则调用ngx_single_process_cycle()进入进程循环;否则为master-worker模式,调用ngx_master_process_cycle()

ngx_pool_s

Nginx的内存池。每个工作线程都会有一个。内存池的分配原理看ngx_palloc方法。

ngx_cycle_s

每个工作进程都会维护一个。

数据结构

ngx_str_t

加上了长度的字段。

1
2
3
4
typedef struct {
size_t len;
u_char *data;
} ngx_str_t;

定义时,调用一下宏。

1
#define ngx_string(str) {sizeof(str) - 1, (u_char *) str }

ngx_queue_t

双向链表。

1
2
3
4
5
6
typedef struct ngx_queue_s ngx_queue_t;
struct ngx_queue_s {
ngx_queue_t *prev;
ngx_queue_t *next;
};

ngx_queue_t只保存指针,那么数据呢?看一下数据获取的方法:

1
#define ngx_queue_data(q, type, link) (type *) ((u_char *) q - offsetof(type, link))

ngx_array_t

数组定义。

1
2
3
4
5
6
7
typedef struct {
void *elts;
ngx_uint_t nelts;
size_t size;
ngx_uint_t nalloc;
ngx_pool_t *pool;
} ngx_array_t;

可见数组是结合内存池使用的,看下数组如何创建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ngx_array_t *ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size) {
ngx_array_t *a;
a = ngx_palloc(p, sizeof(ngx_array_t));
if (a == NULL) {
return NULL;
}
if (ngx_array_init(a, p, n, size) != NGX_OK) {
return NULL;
}
return a;
}

ngx_list_t

数组加链表的结合体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct ngx_list_part_s ngx_list_part_t;
struct ngx_list_part_s {
void *elts;
ngx_uint_t nelts;
ngx_list_part_t *next;
};
typedef struct {
ngx_list_part_t *last;
ngx_list_part_t part;
size_t size;
ngx_uint_t nalloc;
ngx_pool_t *pool;
} ngx_list_t;

ngx_table_elt_t

hash表的一个元素。

1
2
3
4
5
typedef struct {
void *value;
u_short len;
u_char name[1];
} ngx_hash_elt_t;

顺便提一句,nginx用的是开放地址法,会往右查找空闲的bucket。因为它其实假设了hash表不会占用太多的数据和空间。

ngx_rbtree_t

典型的红黑树。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef struct ngx_rbtree_node_s ngx_rbtree_node_t;
struct ngx_rbtree_node_s {
ngx_rbtree_key_t key;
ngx_rbtree_node_t *left;
ngx_rbtree_node_t *right;
ngx_rbtree_node_t *parent;
u_char color;
u_char data;
};
typedef struct ngx_rbtree_s ngx_rbtree_t;
typedef void (*ngx_rbtree_insert_pt) (ngx_rbtree_node_t *root, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel);
struct ngx_rbtree_s {
ngx_rbtree_node_t *root;
ngx_rbtree_node_t *sentinel;
ngx_rbtree_insert_pt insert;
};

内存管理

内存的申请最终调用的是malloc函数,ngx_calloc在调用ngx_alloc后,用memset来填0。

自己开发模块时,不要直接使用ngx_malloc/ngx_calloc,否则要自己管理内存的释放,可以使用ngx_palloc。

master进程

在ngx_master_process_cycle()函数中。

启动过程:

  • 阻塞信号
  • 设置进程名称
  • 启动工作进程
  • 启动cache管理进程
  • 进入循环处理信号

master工作过程:

  • 设置worker退出等待时间
  • 挂起,等待新的信号
  • 更新时间
  • 如果有worker因为SIGCHLD退出,则重启worker
  • master退出
  • 处理SIGTERM
  • 处理SIGQUIT,关闭socket
  • 处理SIGHUP
  • 处理重启
  • 处理SIGUSR1,重新打开所有文件
  • 处理SIGUSR2,热代码替换,执行新程序
  • 处理SIGWINCH

worker进程

  • ngx_start_worker_processes()
  • 进程相关结构初始化
  • fork子进程
  • 设置ngx_processes[s]相关属性
  • ngx_pass_open_channel(cycle, &ch)通知子进程新进程创建完毕
  • ngx_worker_process_init()初始化
  • ngx_channel_handler()处理管道信号
  • 处理相关信号