Nginx 学习笔记2 启动流程

Nginx 启动流程还是比较清晰的,src/core/nginx.c 直接看 main 函数就可以。不过,模块化初始化的回调还是不直观,需要 gdb 帮助

配置 configure

不同于 Apache 模块可以根据 so 来定态加载,Nginx 必须在编绎时指定,其中 event、email、http、core 是必须加载的核心模块,其它或是自定义模块根据需要加载。学习时为了调试,打开 gdb 即可,使用最简单的单步 next, step 足够,调试 worker 时注意追踪父子进程 set follow-fork-mode parent|child

./configure --with-debug --without-http_rewrite_module --without-http_gzip_module
make && make install

粗略的阅读 configure 脚本,主要功能如下:

  1. uname -s, uname -r, uname -m 获取平台信息,例如我的虚拟机 : Linux 3.10.0-862.el7.x86_64 x86_64
  2. auto/os/conf 根据平台配置使用何种事件模型,比如 linux 使用 epoll,设置 cpu 绑定 sched_setaffinity, 是否是 64 位系统等等
  3. auto/modules 这是该脚本最核心的功能,生成 ngx_modules.c 文件,维护全局模块数组 ngx_modules, Nginx 初始化会依赖这个数组。并且数组必须是有顺序的
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_chunked_filter_module,
    &ngx_http_range_header_filter_module,
    &ngx_http_gzip_filter_module,
    .......
    &ngx_http_not_modified_filter_module,
    NULL
};

可以看到前 6 个是核心模块,中间省略的是 http 相关的。后面会看到,http 初始化是倒序的,也就是说,一个 http request 会先经过 ngx_http_not_modified_filter_module, 再经过 ngx_http_gzip_filter_module 模块。开发时一定要注意顺序。

  1. 检测其它动态、静态库是否存在,生成 summary 汇总,如果失败给出原因。

启动流程

网上看到一哥们的《Nginx代码分析》图不错,借用,侵删。

Nginx 启动注程图

1.解析命令。
ngx_get_options 获取命令行参数,比如大家常用的 -t 检测脚语法文件,-s reload|quit|stop 发送信息等等。如果 -h -v 或未识别的参数则打印 Usage 后退出

2.时间、正则、日志、ssl 初始化

3.初始化os相关
ngx_os_init 设置初始进程名称,pagesize, 获取cpu个数,打开的最大文件个数,生成随机种子,最重要的是执行平台相关的 ngx_os_specific_init 函数,生成全局变量 ngx_os_io

ngx_int_t
ngx_os_specific_init(ngx_log_t *log)
{
    ...........
    ngx_os_io = ngx_linux_io;
    return NGX_OK;
}
static ngx_os_io_t ngx_linux_io = {
    ngx_unix_recv, //ngx_recv
    ngx_readv_chain, //ngx_recv_chain   ->recv_chain(相关指针的地方调用
    ngx_udp_unix_recv, //ngx_udp_recv
    ngx_unix_send, //ngx_send
#if (NGX_HAVE_SENDFILE)
    ngx_linux_sendfile_chain, //ngx_send_chain
    NGX_IO_SENDFILE  //./configure配置了sendfile,编译的时候加上sendfile选项,,就会在ngx_linux_io把flag置为该值
#else
    ngx_writev_chain, //ngx_send_chain
    0
#endif
};

ngx_os_io_t 是一种多态的实现,根据不同平台,生成不同函数指针。后面会看到,从 socket 读数据时使用函数 ngx_unix_recv,发送 http body 时使用 ngx_writev_chain 等等

4.继承 sockets
Nginx 为了支持热升级,老的进程将己监听的端口文件描述符 fd 写到环境变量 “NGINX”,exec 新启动的进程获取。函数 ngx_add_inherited_sockets 将 sockets 信息写到 cycle->listening, 如果不做这一步会出什么问题呢?新启动的进程,读取配置文件,打开端口,bind 一个己经被打开的,肯定会报错。

  1. 生成配置结构体
    Ngx_init_cycle 核心初始化函数,更新时间、时区、生成共享内存、解析配置文件、打开监听句柄等等。最重要的就是模块回调,通过 gdb 来追踪更容易理解

Nginx 启动要解析配置文件,每个模块都要读取解析,但是框架只关心 core 核心的就可以。那么在解析前,create_conf 生成相应的结构体来保存配置。默认为unset 即可。

    for (i = 0; ngx_modules[i]; i++) {
        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;
        }
    }
static void *
ngx_core_module_create_conf(ngx_cycle_t *cycle)
{
    ........
    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;

    ccf->user = (ngx_uid_t) NGX_CONF_UNSET_UINT;
    ccf->group = (ngx_gid_t) NGX_CONF_UNSET_UINT;
    ............
    return ccf;
}
  1. 读取配置文件
    ngx_conf_param, ngx_conf_parse 读取并生成配置文件块,Nginx 配置分为三类,parse_file、parse_block、parse_param,即文件,语句块,语句参数。ngx_conf_parse 会根据不同模块的 ngx_command_t set 方法来解析设置配置,举个例子,如果遇到 auth_basic 命令,那么就调用 ngx_http_set_complex_value_slot 来设置参数
static ngx_command_t  ngx_http_auth_basic_commands[] = {

    { ngx_string("auth_basic"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF
                        |NGX_CONF_TAKE1,
      ngx_http_set_complex_value_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_auth_basic_loc_conf_t, realm),
      NULL },
      ngx_null_command
};

然后,调用 init_conf 初始化 core 参数配置

    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_module 的 ngx_core_module_init_conf 函数。

  1. 初始化共享内存
    共享内存很重要,Nginx 用来做进程交互,数据的共享,比如 limit req, limit zone 模块

  2. 打开监听的 socket 句柄
    ngx_open_listening_sockets、ngx_configure_listening_sockets 用来 bind socket, 并设置额外的参数,比如 SO_REUSEPORT、SO_REUSEADDR、SO_RCVBUF、SO_SNDBUF、SO_KEEPALIVE、TCP_KEEPIDLE、TCP_FASTOPEN、TCP_DEFER_ACCEPT

  3. 初始化模块 init_module
    ngx_event_core_module 模块在此调用 init_module 回调 ngx_event_module_init 函数,开避共享内存用来防止惊群的 accept_mutex, 再初始化一些 counter 用来统计。

    for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->init_module) {
            if (ngx_modules[i]->init_module(cycle) != NGX_OK) {
                /* fatal */
                exit(1);
            }
        }
    }
  1. 周边设置
    此时退出 ngx_init_cycle 函数,daemon 变成后台执行,并生成 pidfile, 最后根据角色进入不同逻辑,单机模式进入 ngx_single_process_cycle,否则走入正常的 ngx_master_process_cycle 初始化逻辑。

11.启动 worker
ngx_master_process_cycle 调用 ngx_start_worker_processes 启动子进程,启动 ngx_start_cache_manager_processes cache_loader、cache_manager 进程,然后进入 master 工作模式:热加载配置、拉取挂掉的子进程、binary热更新 等工作。

12.子进程登场
ngx_start_worker_processes 根据 worker 个数,fork 相应的进程,并设置 channel 用于父子进程通信。

  1. 初始化 worker
    ngx_worker_process_cycle 调用 ngx_worker_process_init 进行初始化:环境变量、打开最大的文件个数、set sid gid、ngx_get_cpu_affinity绑定 cpu, 最重要的是调用模块的 init_process 回调
    for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->init_process) {
            if (ngx_modules[i]->init_process(cycle) == NGX_ERROR) { //ngx_event_process_init等
                /* fatal */
                exit(2);
            }
        }
    }

调用 ngx_events_module 事件模块的 ngx_event_process_init 方法,功能如下:

  • ngx_event_timer_init 初始化红黑树定时器
  • 执行 epoll module 的 ngx_epoll_init 方法
  • 为 ngx_cycle 的 connections、read_events、write_events 提前分配空间
  • 生成 ngx_cycle.free_connections 连接池
  • 遍历 ngx_cycle.listenings 监听句柄,设置回调函数为 ngx_event_accept,最后将这个句柄 ngx_add_event 添加到 epoll 中。如果监听到 socket 有新连接,就会调用 ngx_event_accept 创建连接
  1. worker开始工作
    此时 worker 进入死循环,正式进入工作模式,除了获得到 quit 等信号,阻塞在 函数 ngx_process_events_and_timers, 等待用户请求或是超时事件。

小结

这就是 Nginx 的完整启动流程,还有很多细节需要看代码才能清楚。梳理流程,可以深入理解 Nginx 是如何利用模块来组织功能,下一节争取走一遍 Nginx http 流程。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,165评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,720评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,849评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,245评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,596评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,747评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,977评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,708评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,448评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,657评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,141评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,493评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,153评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,890评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,799评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,685评论 2 272

推荐阅读更多精彩内容