Nginx 源码阅读笔记4 启动流程

总体流程

以下代码删掉了部分内容,也就是启动时指定-v -V -t -T选项时用于显示版本ngx_show_version,测试配置ngx_test_config,显示配置ngx_dump_config的相关代码,因为这里主要分析正常启动的流程,所以不关心这些参数

int ngx_cdecl
main(int argc, char *const *argv)
{
    ngx_buf_t        *b;
    ngx_log_t        *log;
    ngx_uint_t        i;
    ngx_cycle_t      *cycle, init_cycle;
    ngx_core_conf_t  *ccf;

    ngx_debug_init();   // 位于 os/unix/ngx_linux_config.h

    if (ngx_strerror_init() != NGX_OK) {    // 位于 os/unix/ngx_errno.c
        return 1;
    }

    if (ngx_get_options(argc, argv) != NGX_OK) {
        return 1;
    }

    /* TODO */ ngx_max_sockets = -1;

    ngx_time_init();    // 位于 core/ngx_times.c

#if (NGX_PCRE)
    ngx_regex_init();
#endif

    ngx_pid = ngx_getpid();

    log = ngx_log_init(ngx_prefix); // 位于 core/ngx_log.c
    if (log == NULL) {
        return 1;
    }

    /* STUB */
#if (NGX_OPENSSL)
    ngx_ssl_init(log);
#endif

    ngx_memzero(&init_cycle, sizeof(ngx_cycle_t));
    init_cycle.log = log;
    ngx_cycle = &init_cycle;

    init_cycle.pool = ngx_create_pool(1024, log);
    if (init_cycle.pool == NULL) {
        return 1;
    }

    if (ngx_save_argv(&init_cycle, argc, argv) != NGX_OK) {
        return 1;
    }

    if (ngx_process_options(&init_cycle) != NGX_OK) {
        return 1;
    }

    if (ngx_os_init(log) != NGX_OK) {  // 位于 os/unix/ngx_posix_init.c
        return 1;
    }

    if (ngx_crc32_table_init() != NGX_OK) {
        return 1;
    }

    if (ngx_add_inherited_sockets(&init_cycle) != NGX_OK) {
        return 1;
    }

    if (ngx_preinit_modules() != NGX_OK) {
        return 1;
    }

    cycle = ngx_init_cycle(&init_cycle);
    if (cycle == NULL) {
        return 1;
    }

    if (ngx_signal) {
        return ngx_signal_process(cycle, ngx_signal);
    }

    ngx_os_status(cycle->log);

    ngx_cycle = cycle;

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

    if (ccf->master && ngx_process == NGX_PROCESS_SINGLE) {
        ngx_process = NGX_PROCESS_MASTER;
    }

#if !(NGX_WIN32)

    if (ngx_init_signals(cycle->log) != NGX_OK) {   // 位于 os/unix/ngx_process.c 
        return 1;
    }

    if (!ngx_inherited && ccf->daemon) {
        if (ngx_daemon(cycle->log) != NGX_OK) { // 位于 os/unix/ngx_daemon.c
            return 1;
        }

        ngx_daemonized = 1;
    }

    if (ngx_inherited) {
        ngx_daemonized = 1;
    }

#endif

    if (ngx_create_pidfile(&ccf->pid, cycle->log) != NGX_OK) {
        return 1;
    }

    if (ngx_log_redirect_stderr(cycle) != NGX_OK) { // 位于 core/ngx_log.c
        return 1;
    }

    if (log->file->fd != ngx_stderr) {
        if (ngx_close_file(log->file->fd) == NGX_FILE_ERROR) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          ngx_close_file_n " built-in log failed");
        }
    }

    ngx_use_stderr = 0;

    if (ngx_process == NGX_PROCESS_SINGLE) {
        ngx_single_process_cycle(cycle);

    } else {
        ngx_master_process_cycle(cycle);    // 位于 os/unix/ngx_process_cycle.c
    }

    return 0;
}
  • 调用ngx_debug_init,因为这个函数是个宏定义,而且为空,所以不知道什么作用,忽略吧
  • 调用ngx_strerror_init,这个函数比较简单,主要就是将系统中 errno 的描述字符串,存储到一个ngx_str_t数组中,这么做的原因是strerrorstrerror_r不是信号安全的
  • 调用ngx_get_options,根据argv中的参数,设置相关的变量,除了一开始说的测试相关的,还有若含有了p选项则设置ngx_prefix表示路径前缀,含有c选项则设置ngx_conf_file表示配置文件相对路径,含有g选项设置ngx_conf_params表示额外配置项(语法与配置文件相同),这里的重点应该是s选项,用于向master进程发送信号,后面跟着stop quit reopen reload的其中之一,具体后面会讲
  • 调用ngx_time_init,设置了描述时间的字符串长度,并调用ngx_time_update更新初始时间
  • 调用ngx_regex_init,看起来是初始化了正则模块,这个模块我没用过,详情不太了解
  • 调用ngx_getpid,设置表示进程 pid 的变量ngx_pid
  • 调用ngx_log_init,初始化了log对象用于输出错误日志(仅用于初始化),函数大概看了一下,就是根据前缀ngx_prefix打开错误日志文件,默认路径是NGX_ERROR_LOG_PATH,这个宏定义由configure脚本生成,位于objs/ngx_auto_config.h,如果不存在这个宏定义,则输出到stderr
  • 调用ngx_ssl_init,看起来是初始化openssl这个库
  • 设置init_cycle变量,这个变量的类型是ngx_cycle_t,这个类型之后会介绍,现在只知道设置了之前初始化的log用于输出错误,创建了内存池pool用于分配内存
  • 调用ngx_save_argv,主要是将argv拷贝到了ngx_argv,原因应该是之后需要修改argv[0]的值以修改进程名称(可能会覆盖argv[1]的值),所以需要备份参数到ngx_argv
  • 调用ngx_process_options,设置init_cycle中的几个参数,分别为conf_file配置文件绝对路径(ngx_conf_file指定的是相对路径),conf_param额外参数(与ngx_conf_params相同),conf_prefix配置文件前缀,prefix路径前缀,一般情况下,最后两个变量值相同,但是当配置头文件中的NGX_PREFIXNGX_CONF_PREFIX存在且没使用c选项指定ngx_prefix时,两者可能不同
  • 调用ngx_os_init,初始化系统相关信息,例如例如 cpu 数量,缓存行大小,页大小,文件描述符最大数量
  • 调用ngx_crc32_table_init,如果设置了缓存行大小,则将crc32算法需要用到的ngx_crc32_table16数组的首地址对齐到缓存行大小
  • 调用ngx_add_inherited_sockets,继承文件描述符,关于这个函数的细节写在后面
  • 调用ngx_preinit_modules,设置ngx_modules数组中各个模块的indexname
  • 调用ngx_init_cycle,这个函数涉及的东西很多,打算之后再说,目前知道解析了配置文件就行了
  • 根据ngx_signal的值判断是否以选项s启动,是则调用ngx_signal_process发送信号,这个函数从指定文件(默认是nginx.pid,这个文件会在后面创建)中读取master进程的 pid,然后向其发送指定信号,如果以这个选项运行则到这里就结束了
  • 调用ngx_os_status,将系统信息写入日志文件
  • 设置ngx_cycle并根据解析完配置文件得到的信息判断是否以master形式启动
  • 调用ngx_init_signals,初始化所有信号处理函数,具体有哪些信号之后再说
  • 如果不是平滑升级的情况,并且需要以 daemon 的形式启动,则调用ngx_daemon将进程变为守护进程(具体细节参考可 APUE 这类书籍)并将ngx_daemonized设置为 1,如果是平滑升级的情况则直接将ngx_daemonized设置为 1(这里不懂为什么)
  • 调用ngx_create_pidfile,创建记录进程 pid 的文件,用于向本进程发送信号
  • 调用ngx_log_redirect_stderr,如果不使用stderr则将其重定向到cycle->log中指定的日志文件
  • 如果log(由ngx_log_init函数返回的临时log,与cycle->log不同,仅用于初始化)的文件描述符不等于ngx_stderr则关闭它
  • ngx_use_stderr设置为 0,这个变量感觉有点奇怪,初始化时为 1,到这里设置为 0,从代码里可以看出,有一个作用是判断是否需要在ngx_init_cycle里调用ngx_log_redirect_stderr,启动时为 1 所以不调用,后面每次 reload 时都会调用,还有一个作用就是ngx_log_error_core函数中,在初始化时将错误输出到控制台,总之不是很懂
  • 根据ngx_process判断是以master形式还是single形式启动

继承文件描述符

static ngx_int_t
ngx_add_inherited_sockets(ngx_cycle_t *cycle)
{
    u_char           *p, *v, *inherited;
    ngx_int_t         s;
    ngx_listening_t  *ls;

    inherited = (u_char *) getenv(NGINX_VAR);

    if (inherited == NULL) {
        return NGX_OK;
    }

    if (ngx_array_init(&cycle->listening, cycle->pool, 10,
                       sizeof(ngx_listening_t))
        != NGX_OK)
    {
        return NGX_ERROR;
    }

    for (p = inherited, v = p; *p; p++) {
        if (*p == ':' || *p == ';') {
            s = ngx_atoi(v, p - v);
            if (s == NGX_ERROR) {
                break;
            }

            v = p + 1;

            ls = ngx_array_push(&cycle->listening);
            if (ls == NULL) {
                return NGX_ERROR;
            }

            ngx_memzero(ls, sizeof(ngx_listening_t));

            ls->fd = (ngx_socket_t) s;
        }
    }

    if (v != p) {
        ngx_log_error(NGX_LOG_EMERG, cycle->log, 0,
                      "invalid socket number \"%s\" in " NGINX_VAR
                      " environment variable, ignoring", v);
    }

    ngx_inherited = 1;

    return ngx_set_inherited_sockets(cycle);
}

这个函数整体比较好看,一开始从环境变量NGINX_VAR中获取旧进程打开的监听文件描述符,这个环境变量的值是以冒号隔开的数字,这些数字代表旧进程的文件描述符,在旧进程调用 exec 系函数时传入,问题是这些数字与当前进程有什么关系呢?由于这些文件描述符没有设置FD_CLOEXEC,所以在 fork 并调用 exec 执行新程序时不会被关闭,也就是和旧进程的文件描述符一致,所以只要知道值就可以拿来用
然后初始化了cycle->listening数组,数量与旧进程传入的文件描述符相同,这是一个监听对象ngx_listening_t数组,可以理解为每个监听对象对应一个监听端口,这里将旧进程的fd直接赋值给监听对象的fd
最后,将ngx_inherited设置为 1,并调用ngx_set_inherited_sockets,这个函数太长了,就不贴上来了,大概就是设置了各个监听对象的 socket 地址,socket 类型,接收缓冲区大小,发送缓冲区大小还有一些端口重用等选项,需要注意的是,如果出错会将ignore的值设置为 1,ngx_cycle_init中需要使用这个值

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值