# Nginx 整体启动流程三

ngx_worker_process_cycle 函数

该函数很简单,就是 master 单进程的事件循环的复刻,只不过这里面涉及到子进程 worker 的属性保存,以及初始化,同时在其中根据 修改 状态来进行流转处理。注意: ngx_process_events_and_timers 为核心函数,该函数我们在之前已经分析过,将会调用 多路复用器完成处理。

ngx_spawn_process(cycle, ngx_worker_process_cycle, (void *) (intptr_t) i, "worker process", type); // 为了方便理解,笔者将创建点放到这里
ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data,
    char *name, ngx_int_t respawn){
    ...
    proc(cycle, data); // 可知传入的数据data 为 当前进程的执行下标
    ...
}static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
    ngx_int_t worker = (intptr_t) data;
    ngx_process = NGX_PROCESS_WORKER; // #define NGX_PROCESS_WORKER 3   进程全局变量 标识当前进程类型
    ngx_worker = worker; // 进程全局变量 保存当前执行下标
    ngx_worker_process_init(cycle, worker); // 初始化工作进程的事件循环,注意:此 cycle 循环为master继承下来的结构
    ngx_setproctitle("worker process"); // 设置子进程的名字
    for ( ;; ) { // 循环处理事件,直到退出
        if (ngx_exiting) { // 检测exiting正在退出标志位,那么检测不存在执行的timer 定时事件,退出worker的事件循环
            if (ngx_event_no_timers_left() == NGX_OK) {
                ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
                ngx_worker_process_exit(cycle);
            }
        }
        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");
        ngx_process_events_and_timers(cycle); // 调用模块的多路复用器执行事件(之前已经讲过)
        if (ngx_terminate) { // 检测到终止标志位,那么退出worker的事件循环
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
            ngx_worker_process_exit(cycle);
        }
        if (ngx_quit) { // 检测到 quit 退出标志位,那么设置进程名为正在关闭,同时将状态转为 ngx_exiting ,在下一次循环处理时,将进入 ngx_exiting 分支
            ngx_quit = 0;
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
                          "gracefully shutting down");
            ngx_setproctitle("worker process is shutting down");
            if (!ngx_exiting) {
                ngx_exiting = 1;
                ngx_set_shutdown_timer(cycle); // 注册关闭定时事件
                ngx_close_listening_sockets(cycle); // 关闭 worker 监听的 fd
                ngx_close_idle_connections(cycle); // 关闭保存的连接
            }
        }
        if (ngx_reopen) { // reopen 事件 ,用于重新打开 fd(后文分析)
            ...
        }
    }
}
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

# ngx_worker_process_init 函数

读者在混沌学堂应该知道:当进程 fork 子进程时,子进程将与父进程共享数据,但当其中一个修改其数据时,将会发生COW机制,所以当 worker 进程 fork 出来的那一刻,事件循环 ngx_cycle_t *cycle 结构的数据将继承父进程的,但我们可以对其中的变量进行重新设置。其他流程自行看注释理解。

static void ngx_worker_process_init(ngx_cycle_t *cycle, ngx_int_t worker)
{
    sigset_t          set;
    ngx_int_t         n;
    ngx_time_t       *tp;
    ngx_uint_t        i;
    ngx_cpuset_t     *cpu_affinity;
    struct rlimit     rlmt;
    ngx_core_conf_t  *ccf;
    ngx_listening_t  *ls;
    if (ngx_set_environment(cycle, NULL) == NULL) { // 重新设置 worker 的环境变量
        exit(2);
    }
    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); // 获取核心模块的配置信息
    if (worker >= 0 && ccf->priority != 0) { // 若指定worker优先级,那么设置优先级 
        if (setpriority(PRIO_PROCESS, 0, ccf->priority) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "setpriority(%d) failed", ccf->priority);
        }
    }
    if (ccf->rlimit_nofile != NGX_CONF_UNSET) { // 设置 worker 进程 打开 fd 限制
      ...
    }if (ccf->rlimit_core != NGX_CONF_UNSET) { // 设置 worker 进程 core 内核转存文件大小限制
      ...
    }if (geteuid() == 0) { // 当前以root用户执行,那么设置用户组信息
        ...
    }if (worker >= 0) { // 若指定了将 worker 进程绑定到指定CPU上,避免进程在不同 CPU中迁移导致性能损耗,那么进行CPU绑定操作
        cpu_affinity = ngx_get_cpu_affinity(worker);if (cpu_affinity) {
            ngx_setaffinity(cpu_affinity, cycle->log);
        }
    }
    if (ccf->working_directory.len) { // 改变worker进程工作目录
        if (chdir((char *) ccf->working_directory.data) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "chdir(\"%s\") failed", ccf->working_directory.data);
            /* fatal */
            exit(2);
        }
    }
    sigemptyset(&set); // 清空信号集
    if (sigprocmask(SIG_SETMASK, &set, NULL) == -1) { // 开始响应信号
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "sigprocmask() failed");
    }
    // 更新时间并生成随机数
    tp = ngx_timeofday();
    srandom(((unsigned) ngx_pid << 16) ^ tp->sec ^ tp->msec);
    // 删除 监听套接字 以前的事件,因为此时 worker 进程中根本没有任何事件
    ls = cycle->listening.elts;
    for (i = 0; i < cycle->listening.nelts; i++) {
        ls[i].previous = NULL;
    }
    for (i = 0; cycle->modules[i]; i++) { // worker 进程 回调模块 init_process 回调函数
        if (cycle->modules[i]->init_process) {
            if (cycle->modules[i]->init_process(cycle) == NGX_ERROR) {
                exit(2);
            }
        }
    }for (n = 0; n < ngx_last_process; n++) { // 遍历所有进程,关闭他们的 unix socket 通道(为何关闭?因为worker 进程继承了父进程的打开文件,而我们并不需要关心其他进程自己读取数据的fd :ngx_processes[n].channel[1] ,所以直接关闭)
        if (ngx_processes[n].pid == -1) { // 进程不存在
            continue;
        }
        if (n == ngx_process_slot) { // 当前进程
            continue;
        }
        if (ngx_processes[n].channel[1] == -1) { // 进程 unix socket 读通道 fd 已经关闭
            continue;
        }
        if (close(ngx_processes[n].channel[1]) == -1) { // 关闭读通道 fd
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "close() channel failed");
        }
    }if (close(ngx_processes[ngx_process_slot].channel[0]) == -1) { // 关闭当前进程的写通道fd(pass:我自己写我自己?)
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "close() channel failed");
    }if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT, ngx_channel_handler) // 向事件循环组注册通道读取事件以及事件处理函数 ngx_channel_handler,也即将自己的读通道注册到多路复用器中
        == NGX_ERROR)
    {
        /* fatal */
        exit(2);
    }
}
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96

# ngx_add_channel_event 函数

该函数用于将事件和通道注册到多路复用器中,我们看到这里首先从连接处获取一个 ngx_connection_t 结构,然后从该结构的属性中获取 ngx_event_t 结构 并 初始化,然后 根据 多路复用器 的类型 选择调用 多路复用器 的 ngx_add_conn 或者 ngx_add_event 方法来注册事件或者连接,我们这里 只研究 epoll,所以后面在讲 EPOLL 时 会直接分析 ngx_add_event 函数指针的回调。pass:连接池的设计部分,我们后文再说。

// 根据宏定义选择 读写事件 对应的多路选择器类型,这里我们关注EPOLL 的宏即可
#elif (NGX_HAVE_EPOLL) && !(NGX_TEST_BUILD_EPOLL)
#define NGX_READ_EVENT     (EPOLLIN|EPOLLRDHUP)
#define NGX_WRITE_EVENT    EPOLLOUTngx_int_t ngx_add_channel_event(ngx_cycle_t *cycle, ngx_fd_t fd, ngx_int_t event,ngx_event_handler_pt handler)
{
    ngx_event_t       *ev, *rev, *wev;
    ngx_connection_t  *c;
​
    c = ngx_get_connection(fd, cycle->log); // 从连接池中获取连接结构 ngx_connection_t
    if (c == NULL) {
        return NGX_ERROR;
    }
    c->pool = cycle->pool;
    rev = c->read; // 连接 读事件
    wev = c->write; // 连接 写事件
​
    rev->log = cycle->log;
    wev->log = cycle->log;
​
    rev->channel = 1;
    wev->channel = 1;
​
    ev = (event == NGX_READ_EVENT) ? rev : wev; // 根据读写类型选择 读事件结构 或者 写事件结构
    ev->handler = handler; // 绑定回调函数(当对应事件产生后回调)// 根据多路复用器类型 调用不同函数
    if (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0) { 
        if (ngx_add_conn(c) == NGX_ERROR) {
            ngx_free_connection(c);
            return NGX_ERROR;
        }} else {
        if (ngx_add_event(ev, event, 0) == NGX_ERROR) {
            ngx_free_connection(c);
            return NGX_ERROR;
        }
    }return NGX_OK;
}
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