惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Microsoft Azure Blog
Microsoft Azure Blog
大猫的无限游戏
大猫的无限游戏
月光博客
月光博客
V
V2EX
PCI Perspectives
PCI Perspectives
Latest news
Latest news
博客园 - 三生石上(FineUI控件)
C
CERT Recently Published Vulnerability Notes
W
WeLiveSecurity
Last Week in AI
Last Week in AI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
P
Palo Alto Networks Blog
T
The Exploit Database - CXSecurity.com
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
WordPress大学
WordPress大学
V
Vulnerabilities – Threatpost
H
Heimdal Security Blog
Attack and Defense Labs
Attack and Defense Labs
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Hacker News: Ask HN
Hacker News: Ask HN
博客园 - 叶小钗
V
Visual Studio Blog
Jina AI
Jina AI
P
Proofpoint News Feed
罗磊的独立博客
SecWiki News
SecWiki News
J
Java Code Geeks
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
L
LINUX DO - 热门话题
Security Archives - TechRepublic
Security Archives - TechRepublic
The Hacker News
The Hacker News
Hugging Face - Blog
Hugging Face - Blog
N
News and Events Feed by Topic
NISL@THU
NISL@THU
T
Tailwind CSS Blog
T
Tenable Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Recent Announcements
Recent Announcements
H
Hacker News: Front Page
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
T
Tor Project blog
宝玉的分享
宝玉的分享
Help Net Security
Help Net Security
S
Security Affairs
Microsoft Security Blog
Microsoft Security Blog
Google DeepMind News
Google DeepMind News
F
Fortinet All Blogs
G
GRAHAM CLULEY

Jiajun的技术笔记

你好,2026! TiDB 源码阅读(六):TiDB Coprocessor 源码解析 性能优化的核心思想 TiDB 源码阅读(五):索引 TiDB 源码阅读(四):AST、逻辑计划、物理计划 CockroachDB Serverless Architecture podman 无故退出 Cursor Control-L (CTRL-L) Keyboard Shortcuts in Terminal Replace docker with podman Using xmonad with xfce4 A RC script for freebsd frpc 自己动手写一个k8s controller AI 会取代你的(编程)岗位吗? 自建DERP服务器提升Tailscale连接速度(使用Nginx转发) 自动升级Docker容器 再读《程序员修炼之道-从小工到专家》 让浏览器下载文件 再读《软件随想录》/《黑客与画家》/《软技能》 HTTP 压力测试中的 Coordinated Omission 2的补码 编程语言中的 context 是什么? flutter macOS 构建出错 Flatpak 使用小记 Golang CAS 操作是怎么实现的 PostgreSQL 当MQ来使用 Clash 结合 工作VPN 的网络设计 使用 PostgreSQL 搭建 JuiceFS PostgreSQL 配置优化和日志分析 有GitHub Copilot?那就可以搭建你的ChatGPT4服务 窗口函数的使用(以PG为例) 读《为什么学生不喜欢上学》 OpenAI Prompt Engineering 摘录和总结 读《打造真正的新产品》 VueJS 总结 Linux 自动挂载 alist 提供的webdav FreeBSD 使用 vm-bhyve 安装Debian虚拟机 FreeBSD 和 Linux 网卡聚合实现提速 GPT 帮我搞定了时区转换问题 长任务系统如何处理? macOS/Linux 编译 InputLeap 使用开源软KVM - synergy-core 解决 macOS 终端hostname一直变化问题 KVM 共享 Intel 集成显卡 PromQL 备忘 读《格鲁夫给经理人的第一课》 读《打开心智》 为什么要把复杂的联表操作拆成多个单表查询? 红包系统的设计 MySQL Index Condition Pushdown Optimization Go mod 简明教程 OpenWRT 使用 Android/iOS USB 网络 搭建旁路由 Golang gRPC 错误处理 编写可维护的单元测试代码 OAuth 2 详解(六):Authorization Code Flow with PKCE OAuth 2 详解(五):Device Authorization Flow OAuth 2 详解(三):Resource Owner Password Credentials Grant OAuth 2 详解(四):Client Credentials Flow OAuth 2 详解(二):Implict Grant Flow OAuth 2 详解(一):简介及 Authorization Code 模式 ElasticSearch 学习笔记 三种git流程以及发版模型 错误处理实践 权限模型(RBAC/ABAC) OIDC(OpenID Connect) 简介 任务队列简介 PostgreSQL 操作笔记 使用Drone CI构建CI/CD系统 Golang migrate 做数据库变更管理 使用PostgreSQL做搜索引擎 Nginx 源码阅读(三): 连接池、内存池 Nginx 源码阅读(二): 请求处理 Go 泛型简明教程 KVM 显卡穿透给 Windows 使用 HTTP Router 处理 Telegram Bot 按钮回调 使用反射(reflect)对结构体赋值 GIN 是如何绑定参数的 你好 2022(2021 年终总结) 用Go导入大型CSV到PostgreSQL 使用 OpenWRT 搭建软路由 使用软KVM切换器 barrier 共享键鼠 SQL 防注入及原理 使用 gomock 测试 Go 代码 gevent不是黑魔法(二): gevent 实现 gevent不是黑魔法(一): greenlet 实现 用 entgo 替代 gorm 应用内使用crontab不是那么方便 单测时要不要 mock 数据库? Sentry 自建指南 用selenium完成自动化任务 用闲置的安卓手机做垃圾电话短信过滤 推荐三个时间管理工具 一次事故反思 当JS遇到uint64:JS整数溢出问题 SQLite3 存储以及ACID原理 Redis源码阅读:pub/sub实现 Redis源码阅读:zset实现 Redis源码阅读:bitmap 位图的运算 Redis源码阅读:set是怎么做交并集运算的? Redis源码阅读:list实现(ziplist, quicklist)
Nginx 源码阅读(一): 启动流程
Jiajun Huang · 2022-03-21 · via Jiajun的技术笔记

去年读了一部分Nginx源码,后来耽搁了,最近决定捡起来做完这件事情。说起Nginx源码, 我主要是想了解Nginx的代码组织、架构、master/worker分工、以及一个请求的处理流程。 对其它的我就不是那么感兴趣了。因此这一系列Nginx源码阅读也主要是围绕这些话题来写的。

我读的是 Nginx 0.1.0

源码组织

首先把代码拉下来,然后切到 release-0.1.0 这个tag,这就是我要读的源码。 要了解一份代码,首先我们要了解它的组织结构,然后我们要找到入口,顺着入口一路跟, 通常来说,入口就是 main 函数。

先来看看源码组织:

$ tree -d 
.
├── auto
│   ├── fmt
│   ├── lib
│   │   ├── md5
│   │   ├── openssl
│   │   ├── pcre
│   │   └── zlib
│   ├── os
│   └── types
├── conf
├── docs
│   ├── dtd
│   ├── html
│   ├── text
│   ├── xml
│   │   └── nginx
│   ├── xsls
│   └── xslt
├── objs
│   └── src
│       ├── core
│       ├── event
│       │   └── modules
│       ├── http
│       │   └── modules
│       │       └── proxy
│       ├── imap
│       └── os
│           ├── unix
│           └── win32
└── src
    ├── core
    ├── event
    │   └── modules
    ├── http
    │   └── modules
    │       └── proxy
    ├── imap
    └── os
        ├── unix
        └── win32

41 directories

首先 docs, conf, objs 目录我们不管,分别是文档、配置文件和编译出来的目标 文件存放处。auto 目录下是各种编译脚本,src 下就是源码。

src 下主要是5个子目录:

  • core 一些基本类型和各种数据结构、函数,比如 string, array, pool
  • event 事件库封装,epoll, kqueue, select
  • http HTTP相关的所有代码和模块都在这里
  • imap 邮件相关,我们忽略
  • os 操作系统相关的函数封装

由上可知,我们主要的关注点都将落在 core, http 这两个文件夹下,其它的我们 就在要用到的时候,再去考察。

接下来就是找到 main 函数,这个简单,搜索一下便是。

$ ag 'int main'
event/ngx_event_connectex.c
62:int ngx_iocp_new_thread(int main)
120:void ngx_iocp_wait_events(int main)

core/nginx.c
99:int main(int argc, char *const *argv)

启动流程分析

找到了 main 函数,接下来要做的事情就是跟着 main 函数一步一步看启动流程,不过我读源码的时候,还需要用gdb 定位一些函数的位置,所以我费了一些时间在本地把Nginx 0.1.0 编译起来,十几年前的代码,还是需要做一些修改才能 跑起来的。我们来看 main 函数:

int main(int argc, char *const *argv)
{
    // 声明一些变量
    ngx_int_t          i;
    ngx_log_t         *log;
    ngx_cycle_t       *cycle, init_cycle;
    ngx_core_conf_t   *ccf;
    ngx_master_ctx_t   ctx;

    /* TODO */ ngx_max_sockets = -1;

    ngx_time_init(); // 跟踪代码看了一下,是初始化Nginx内部缓存的时间变量

#if (HAVE_PCRE)
    ngx_regex_init(); // 这个很明显,初始化PCRE,也就是正则表达式
#endif

    ngx_pid = ngx_getpid(); // 获取进程ID

    if (!(log = ngx_log_init_stderr())) {
        return 1;
    }

#if (NGX_OPENSSL)
    ngx_ssl_init(log); // 初始化SSL
#endif

    /* init_cycle->log is required for signal handlers and ngx_getopt() */
    // 初始化 cycle。cycle是一个很重要的变量,简单理解,就是Nginx各种乱七八糟的运行时上下文都存在这里。
    // 参考 https://nginx.org/en/docs/dev/development_guide.html#cycle
    ngx_memzero(&init_cycle, sizeof(ngx_cycle_t));
    init_cycle.log = log;
    ngx_cycle = &init_cycle;

    ngx_memzero(&ctx, sizeof(ngx_master_ctx_t));
    ctx.argc = argc;
    ctx.argv = argv;

    // 内存池:https://nginx.org/en/docs/dev/development_guide.html#pool
    if (!(init_cycle.pool = ngx_create_pool(1024, log))) {
        return 1;
    }

    if (ngx_getopt(&ctx, &init_cycle) == NGX_ERROR) {
        return 1;
    }

    if (ngx_test_config) {
        log->log_level = NGX_LOG_INFO;
    }

    // src/os/unix/ngx_linux_init.c 初始化一些系统相关的东西,我的系统是Linux,所以调用 ngx_linux_init.c
    if (ngx_os_init(log) == NGX_ERROR) {
        return 1;
    }

    // 这里是从环境变量里提取要继承的fd
    if (ngx_add_inherited_sockets(&init_cycle) == NGX_ERROR) {
        return 1;
    }

    ngx_max_module = 0;
    // ngx_modules 在 objs/ngx_modules.c 里
    for (i = 0; ngx_modules[i]; i++) {
        ngx_modules[i]->index = ngx_max_module++;
    }

    // 初始化 cycle。里面会初始化各个模块
    cycle = ngx_init_cycle(&init_cycle);
    if (cycle == NULL) {
        if (ngx_test_config) {
            ngx_log_error(NGX_LOG_EMERG, log, 0,
                          "the configuration file %s test failed",
                          init_cycle.conf_file.data);
        }

        return 1;
    }

    if (ngx_test_config) {
        ngx_log_error(NGX_LOG_INFO, log, 0,
                      "the configuration file %s was tested successfully",
                      init_cycle.conf_file.data);
        return 0;
    }

    ngx_os_status(cycle->log);

    ngx_cycle = cycle;

    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);

    ngx_process = ccf->master ? NGX_PROCESS_MASTER : NGX_PROCESS_SINGLE;

    if (ngx_create_pidfile(cycle, NULL) == NGX_ERROR) {
        return 1;
    }

    if (ngx_process == NGX_PROCESS_MASTER) {
        // master/worker 模式下要执行的代码
        ngx_master_process_cycle(cycle, &ctx);
    } else {
        // 单进程模式下要执行的代码
        ngx_single_process_cycle(cycle, &ctx);
    }

    return 0;
}

看到这里,我们大概知道Nginx是怎么启动的,首先各种初始化,然后判断是否是 master/worker 模式,是的话,就执行 ngx_master_process_cycle 函数去处理,但是我们还不知道具体里面发生了什么,所以继续跟下去。

master 是如何工作的

void ngx_master_process_cycle(ngx_cycle_t *cycle, ngx_master_ctx_t *ctx)
{
    char              *title;
    u_char            *p;
    size_t             size;
    ngx_int_t          i;
    sigset_t           set;
    struct timeval     tv;
    struct itimerval   itv;
    ngx_uint_t         live;
    ngx_msec_t         delay;
    ngx_core_conf_t   *ccf;

    // 设置感兴趣的信号,信号处理函数已经在 ngx_os_init 里设置了
    sigemptyset(&set);
    // ...

    // 设置进程名
    ngx_setproctitle(title);


    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);

    // 此处创建worker进程
    ngx_start_worker_processes(cycle, ccf->worker_processes,
                               NGX_PROCESS_RESPAWN);

    ngx_new_binary = 0;
    delay = 0;
    live = 1;

    for ( ;; ) {
        sigsuspend(&set);

        ngx_gettimeofday(&tv);
        ngx_time_update(tv.tv_sec);

        // 然后根据全局变量来执行对应动作

        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "wake up");

        if (ngx_reap) {
            // ...
        }

        if (!live && (ngx_terminate || ngx_quit)) {
            // ...
        }

        if (ngx_terminate) {
            // ...
        }

        if (ngx_quit) {
            // ...
        }

        if (ngx_timer) {
            // ...
        }

        if (ngx_reconfigure) {
            // ...
        }

        if (ngx_restart) {
            // ...
        }

        if (ngx_reopen) {
            // ...
        }

        if (ngx_change_binary) {
            // ...
        }

        if (ngx_noaccept) {
            // ...
        }
    }
}

现在我们知道了,master其实不会处理任何连接。master首先进行初始化,设置好感兴趣的信号,然后创建worker,之后master本身 就进入一个无限循环,通过 sigsuspend 函数阻塞自身,当收到信号时,通过几个全局变量来决定自己的行为,例如重启,rotate 日志,退出等等。

sig_atomic_t  ngx_reap;
sig_atomic_t  ngx_timer;
sig_atomic_t  ngx_sigio;
sig_atomic_t  ngx_terminate;
sig_atomic_t  ngx_quit;
sig_atomic_t  ngx_reconfigure;
sig_atomic_t  ngx_reopen;
sig_atomic_t  ngx_change_binary;
sig_atomic_t  ngx_noaccept;

Nginx通过使用这几个原子变量来指示 master 收到信号以后,应该如何处理。我们在很多Go的代码里,也看到过类似的逻辑,例如 用一个 atomic 来存储 exit 以指示是否要退出(以前看 Thrift Go 实现就有这样的用法)。

那么,master 是怎么改变这些变量的呢?答案其实就是通过信号。我们上面只看到了 master 设置感兴趣的信号,但是没有看到哪里 设置了信号处理函数,经过搜索,发现其实就在 ngx_os_init 函数里,它调用了 ngx_posix_init

ngx_int_t ngx_posix_init(ngx_log_t *log)
{
    ngx_signal_t      *sig;
    struct sigaction   sa;

    ngx_pagesize = getpagesize();

    if (ngx_ncpu == 0) {
        ngx_ncpu = 1;
    }

    // 在这里初始化信号处理函数,看代码,统一都是 ngx_signal_handler
    for (sig = signals; sig->signo != 0; sig++) {
        ngx_memzero(&sa, sizeof(struct sigaction));
        sa.sa_handler = sig->handler;
        sigemptyset(&sa.sa_mask);
        if (sigaction(sig->signo, &sa, NULL) == -1) {
            ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
                          "sigaction(%s) failed", sig->signame);
            return NGX_ERROR;
        }
    }

    // ...
}

秘密就藏在 sigaction(sig->signo, &sa, NULL) 函数调用里,sa.sa_handler 赋值于 sig->handlersig 来自于迭代 signals

ngx_signal_t  signals[] = {
    { ngx_signal_value(NGX_RECONFIGURE_SIGNAL),
      "SIG" ngx_value(NGX_RECONFIGURE_SIGNAL),
      ngx_signal_handler },

    { ngx_signal_value(NGX_REOPEN_SIGNAL),
      "SIG" ngx_value(NGX_REOPEN_SIGNAL),
      ngx_signal_handler },

    { ngx_signal_value(NGX_NOACCEPT_SIGNAL),
      "SIG" ngx_value(NGX_NOACCEPT_SIGNAL),
      ngx_signal_handler },

    { ngx_signal_value(NGX_TERMINATE_SIGNAL),
      "SIG" ngx_value(NGX_TERMINATE_SIGNAL),
      ngx_signal_handler },

    { ngx_signal_value(NGX_SHUTDOWN_SIGNAL),
      "SIG" ngx_value(NGX_SHUTDOWN_SIGNAL),
      ngx_signal_handler },

    { ngx_signal_value(NGX_CHANGEBIN_SIGNAL),
      "SIG" ngx_value(NGX_CHANGEBIN_SIGNAL),
      ngx_signal_handler },

    { SIGALRM, "SIGALRM", ngx_signal_handler },

    { SIGINT, "SIGINT", ngx_signal_handler },

    { SIGIO, "SIGIO", ngx_signal_handler },

    { SIGCHLD, "SIGCHLD", ngx_signal_handler },

    { SIGPIPE, "SIGPIPE, SIG_IGN", SIG_IGN },

    { 0, NULL, NULL }
};

可以看到,基本所有的信号处理函数,都是 ngx_signal_handler

void ngx_signal_handler(int signo)
{
    /*
     * 收到对应信号,就设置对应的变量,比如 ngx_reconfigure。它是一个atomic值,
     * 然后 master 进程就会从 sigsuspend 苏醒,然后检测变量进行处理
     */
    char            *action;
    struct timeval   tv;
    ngx_int_t        ignore;
    ngx_err_t        err;
    ngx_signal_t    *sig;

    ignore = 0;

    err = ngx_errno;

    for (sig = signals; sig->signo != 0; sig++) {
        if (sig->signo == signo) {
            break;
        }
    }

    ngx_gettimeofday(&tv);
    ngx_time_update(tv.tv_sec);

    action = "";

    switch (ngx_process) {

    case NGX_PROCESS_MASTER:
    case NGX_PROCESS_SINGLE:
        switch (signo) {

        case ngx_signal_value(NGX_SHUTDOWN_SIGNAL):
            ngx_quit = 1;
            action = ", shutting down";
            break;

        case ngx_signal_value(NGX_TERMINATE_SIGNAL):
        case SIGINT:
            ngx_terminate = 1;
            action = ", exiting";
            break;

        // ...
        }
    }
}

至此,我们大概就知道了 master 是如何工作的,它本身接收一些信号,然后通过比较信号值,改变一些内部的全局 atomic 变量, 之后在循环中,通过判断这些 atomic 变量,来做出对应的行为。

worker 是如何工作的

要看 worker,那我们就得回到 ngx_start_worker_processes

static void ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n,
                                       ngx_int_t type)
{
    ngx_int_t         i;
    ngx_channel_t     ch;
    struct itimerval  itv;

    ngx_log_error(NGX_LOG_INFO, cycle->log, 0, "start worker processes");

    ch.command = NGX_CMD_OPEN_CHANNEL;

    while (n--) {
        // 起n个worker进程,执行 ngx_worker_process_cycle
        ngx_spawn_process(cycle, ngx_worker_process_cycle, NULL,
                          "worker process", type);

        ch.pid = ngx_processes[ngx_process_slot].pid;
        ch.slot = ngx_process_slot;
        ch.fd = ngx_processes[ngx_process_slot].channel[0];

        for (i = 0; i < ngx_last_process; i++) {

            if (i == ngx_process_slot
                || ngx_processes[i].pid == -1
                || ngx_processes[i].channel[0] == -1)
            {
                continue;
            }

            ngx_log_debug6(NGX_LOG_DEBUG_CORE, cycle->log, 0,
                           "pass channel s:%d pid:" PID_T_FMT
                           " fd:%d to s:%d pid:" PID_T_FMT " fd:%d",
                           ch.slot, ch.pid, ch.fd,
                           i, ngx_processes[i].pid,
                           ngx_processes[i].channel[0]);

            /* TODO: NGX_AGAIN */

            ngx_write_channel(ngx_processes[i].channel[0],
                              &ch, sizeof(ngx_channel_t), cycle->log);
        }
    }

    /*
     * we have to limit the maximum life time of the worker processes
     * by 10 days because our millisecond event timer is limited
     * by 24 days on 32-bit platforms
     */

    itv.it_interval.tv_sec = 0;
    itv.it_interval.tv_usec = 0;
    itv.it_value.tv_sec = 10 * 24 * 60 * 60;
    itv.it_value.tv_usec = 0;

    if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "setitimer() failed");
    }
}

这个函数里,有两个很重要的东西,第一个是 ngx_spawn_process(cycle, ngx_worker_process_cycle, NULL, "worker process", type), 第二个是 ngx_write_channel(ngx_processes[i].channel[0], &ch, sizeof(ngx_channel_t), cycle->log);。我们先来跟踪第一个:

ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle,
                            ngx_spawn_proc_pt proc, void *data,
                            char *name, ngx_int_t respawn)
{
    // ...
    pid = fork();

    switch (pid) {

    case -1:
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "fork() failed while spawning \"%s\"", name);
        ngx_close_channel(ngx_processes[s].channel, cycle->log);
        return NGX_ERROR;

    case 0:
        ngx_pid = ngx_getpid();
        proc(cycle, data); // 看起来这里最终也是要返回然后设置下面的这些东西。此处的 proc 是 ngx_worker_process_cycle
        break;

    default:
        break;
    }
}

这里的 proc 就是调用 ngx_spawn_process 时传入的 ngx_worker_process_cycle

static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
    // ...

    for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->init_process) {
            // 这里调用 init_process,此处就会初始化事件模块等
            if (ngx_modules[i]->init_process(cycle) == NGX_ERROR) {
                /* fatal */
                exit(2);
            }
        }
    }

    // ...

    for ( ;; ) {
        // worker 真正执行处理事件的地方
        ngx_process_events(cycle);

        // ...

        if (ngx_terminate) {
            // ...
        }

        if (ngx_quit) {
            // ...
        }

        if (ngx_reopen) {
            // ...
        }

        // ...
    }
}

这就是 worker 大概的启动流程,由 master fork之后,初始化各个模块,然后进入自身的工作循环,执行 ngx_process_events 处理各种事件,ngx_process_events 的定义为:

// src/event/ngx_event.h 里
#define ngx_process_events   ngx_event_actions.process_events

// src/event/ngx_event.c 里
ngx_event_actions_t               ngx_event_actions;

// src/event/ngx_event.h 里
typedef struct {
    // 添加/删除事件
    ngx_int_t  (*add)(ngx_event_t *ev, int event, u_int flags);
    ngx_int_t  (*del)(ngx_event_t *ev, int event, u_int flags);

    ngx_int_t  (*enable)(ngx_event_t *ev, int event, u_int flags);
    ngx_int_t  (*disable)(ngx_event_t *ev, int event, u_int flags);

    // 添加和删除连接,也就是对读和写都进行处理
    ngx_int_t  (*add_conn)(ngx_connection_t *c);
    ngx_int_t  (*del_conn)(ngx_connection_t *c, u_int flags);

    ngx_int_t  (*process_changes)(ngx_cycle_t *cycle, ngx_uint_t try);
    // 工作循环中处理事件
    ngx_int_t  (*process_events)(ngx_cycle_t *cycle);

    ngx_int_t  (*init)(ngx_cycle_t *cycle);
    void       (*done)(ngx_cycle_t *cycle);
} ngx_event_actions_t;


extern ngx_event_actions_t   ngx_event_actions;

可以看到,ngx_event_actions 就是事件模块,而其中的 process_events 就是处理事件的函数,而且 ngx_event_actions 是 一个全局变量。试着想想,Nginx要支持跨平台,而且多个 I/O 多路复用库,这里又是一个全局变量,我怀疑是编译的时候生成,然后 具体初始化某个 I/O 多路复用 的时候赋值的,搜索一下代码:

$ ag ngx_event_actions
src/event/ngx_event.c
59:ngx_event_actions_t               ngx_event_actions;

src/event/ngx_event.h
219:} ngx_event_actions_t;
222:extern ngx_event_actions_t   ngx_event_actions;
386:#define ngx_process_changes  ngx_event_actions.process_changes
387:#define ngx_process_events   ngx_event_actions.process_events
388:#define ngx_done_events      ngx_event_actions.done
390:#define ngx_add_event        ngx_event_actions.add
391:#define ngx_del_event        ngx_event_actions.del
392:#define ngx_add_conn         ngx_event_actions.add_conn
393:#define ngx_del_conn         ngx_event_actions.del_conn
435:    ngx_event_actions_t     actions;

src/event/modules/ngx_aio_module.c
78:    ngx_event_actions = ngx_aio_module_ctx.actions;

src/event/modules/ngx_epoll_module.c
169:    ngx_event_actions = ngx_epoll_module_ctx.actions;

src/event/modules/ngx_kqueue_module.c
189:    ngx_event_actions = ngx_kqueue_module_ctx.actions;

src/event/modules/ngx_iocp_module.c
112:    ngx_event_actions = ngx_iocp_module_ctx.actions;

src/event/modules/ngx_poll_module.c
101:    ngx_event_actions = ngx_poll_module_ctx.actions;

src/event/modules/ngx_select_module.c
113:    ngx_event_actions = ngx_select_module_ctx.actions;

src/event/modules/ngx_rtsig_module.c
163:    ngx_event_actions = ngx_rtsig_module_ctx.actions;
529:        ngx_event_actions.process_events = ngx_rtsig_process_overflow;
765:    ngx_event_actions.process_events = ngx_rtsig_process_events;

src/event/modules/ngx_devpoll_module.c
168:    ngx_event_actions = ngx_devpoll_module_ctx.actions;

可以看到,代码上确实是在多个 I/O 多路复用的模块里分别赋值的,我的系统是Linux,所以肯定是用 epoll,跳到 ngx_epoll_module.c 看一下:

static int ngx_epoll_init(ngx_cycle_t *cycle)
{
    // ...
    ngx_event_actions = ngx_epoll_module_ctx.actions;
    // ...
}

好,就此打住。我们大概知道了worker的流程,首先由 master fork,然后初始化各个模块,这里就包括了 I/O 多路复用模块, 然后进入自身的工作循环,执行 ngx_process_events 处理各种事件,ngx_process_events 就是由 I/O 多路复用模块提供的。

每次执行完之后,也是通过判断几个全局变量来决定worker的行为。

master/worker 通信

刚才我们说到 ngx_write_channel(ngx_processes[i].channel[0], &ch, sizeof(ngx_channel_t), cycle->log),我们现在来看看它是 干啥的:

// master/worker 之间进程间通信
ngx_int_t ngx_write_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size,
                            ngx_log_t *log) 
{
    ssize_t             n;
    ngx_err_t           err;
    struct iovec        iov[1];
    struct msghdr       msg;

#if (HAVE_MSGHDR_MSG_CONTROL)

    union {
        struct cmsghdr  cm;
        char            space[CMSG_SPACE(sizeof(int))];
    } cmsg;

    if (ch->fd == -1) {
        msg.msg_control = NULL;
        msg.msg_controllen = 0;

    } else {
        msg.msg_control = (caddr_t) &cmsg;
        msg.msg_controllen = sizeof(cmsg);

        cmsg.cm.cmsg_len = sizeof(cmsg);
        cmsg.cm.cmsg_level = SOL_SOCKET; 
        cmsg.cm.cmsg_type = SCM_RIGHTS;
        *(int *) CMSG_DATA(&cmsg.cm) = ch->fd;
    }

#else

    if (ch->fd == -1) {
        msg.msg_accrights = NULL;
        msg.msg_accrightslen = 0;

    } else {
        msg.msg_accrights = (caddr_t) &ch->fd;
        msg.msg_accrightslen = sizeof(int);
    }

#endif

    iov[0].iov_base = (char *) ch;
    iov[0].iov_len = size;

    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;

    n = sendmsg(s, &msg, 0);

    if (n == -1) {
        err = ngx_errno;
        if (err == NGX_EAGAIN) {
            return NGX_AGAIN;
        }

        ngx_log_error(NGX_LOG_ALERT, log, err, "sendmsg() failed");
        return NGX_ERROR;
    }

    return NGX_OK;
}

这里其实是 master 和 worker 之间通信的代码,也是通过网络,传输的结构体定义为:

typedef struct {
     ngx_uint_t  command;
     ngx_pid_t   pid;
     ngx_int_t   slot;
     ngx_fd_t    fd;
} ngx_channel_t;

可以看到,ch 其实是一个写往通道里的命令。而 channel 的定义则是 ngx_processes[i].channel[0], 其实它是一个 socket 的 fd。那么这个 ngx_processes 是什么时候初始化的呢? 答案就在 ngx_spawn_process 中:

ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle,
                            ngx_spawn_proc_pt proc, void *data,
                            char *name, ngx_int_t respawn)
{
    u_long     on;
    ngx_pid_t  pid;
    ngx_int_t  s;

    if (respawn >= 0) {
        s = respawn;

    } else {
        for (s = 0; s < ngx_last_process; s++) {
            if (ngx_processes[s].pid == -1) {
                break;
            }
        }

        if (s == NGX_MAX_PROCESSES) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
                          "no more than %d processes can be spawned",
                          NGX_MAX_PROCESSES);
            return NGX_ERROR;
        }
    }
    // ...
        if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1)
    // ...
    pid = fork();
    // ...
}

在 fork 之前,Nginx会在 ngx_processes 里找到一个最小的位置,然后用 socketpair 创建一个 socket pair 用于 master 和 worker 通信。

总结

看到这里,这第一篇终于可以收尾了,在这一篇源码阅读中,我们从 main 函数入手,依次跟踪了 main 函数的启动过程, 然后我们抵达了 ngx_master_process_cycle,从这里开始,Nginx便会进入 master/worker 模型的代码。接着我们了解了 master 大概的启动流程,worker的启动流程,以及他们之间是如何通信的,以及master和worker大致的工作模型。

接下来的文章,我们将聚焦到 worker ,看 worker 中是如何初始化模块的,以及worker是如何处理请求的。


参考资料: