<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title><![CDATA[Category: nginx | 后端技术杂谈]]></title>
  <link href="https://www.rowkey.cn/blog/categories/nginx/atom.xml" rel="self"/>
  <link href="https://www.rowkey.cn/"/>
  <updated>2026-03-06T09:14:43+00:00</updated>
  <id>https://www.rowkey.cn/</id>
  <author>
    <name><![CDATA[HJ]]></name>
    <email><![CDATA[superhj1987@126.com]]></email>
  </author>
  <generator uri="http://octopress.org/">Octopress</generator>

  
  <entry>
    <title type="html"><![CDATA[Nginx源码分析之启动过程]]></title>
    <link href="https://www.rowkey.cn/blog/2014/09/24/nginx-bootstrap/"/>
    <updated>2014-09-24T17:38:57+08:00</updated>
    <id>https://www.rowkey.cn/blog/2014/09/24/nginx-bootstrap</id>
    <content type="html"><![CDATA[<p>nginx的启动过程代码主要分布在src/core以及src/os/unix目录下。启动流程的函数调用序列：main(src/core/nginx.c)→ngx_init_cycle(src/core/ngx_cycle.c)→ngx_master_process_cycle(src/os/)。nginx的启动过程就是围绕着这三个函数进行的。</p>

<p>main函数的处理过程总体上可以概括如下：</p>

<!--more-->


<ol>
<li>简单初始化一些数据接结构和模块：ngx_debug_init、ngx_strerror_init、ngx_time_init、ngx_regex_init、ngx_log_init、ngx_ssl_init等</li>
<li>获取并处理命令参数：ngx_get_options。</li>
<li><p>初始化ngx_cycle_t结构体变量init_cycle，设置其log、pool字段等。
 <pre>
 ngx_memzero(&amp;init_cycle, sizeof(ngx_cycle_t));
 init_cycle.log = log;
 ngx_cycle = &amp;init_cycle;</p>

<p> init_cycle.pool = ngx_create_pool(1024, log);
 if (init_cycle.pool == NULL) {
     return 1;
 }
</pre></p></li>
<li>保存参数，设置全局变量：ngx_argc ngx_os_argv ngx_argv ngx_environ</li>
<li>处理控制台命令行参数，设置init_cycle的字段。这些字段包括：conf_prefix、prefix（-p prefix）、conf_file(-c filename)、conf_param(-g directives)。此外，还设置init_cyle.log.log_level=NGX_LOG_INFO。</li>
<li>调用ngx_os_init来设置一些和操作系统相关的全局变量：ngx_page_size、ngx_cacheline_size、ngx_ncpu、ngx_max_sockets、ngx_inherited_nonblocking。</li>
<li>调用ngx_crc32_table_init初始化ngx_crc32_table_short。用于后续做crc校验。</li>
<li>调用ngx_add_inherited_sockets(&amp;init_cycle)→ngx_set_inherited_sockets，初始化init_cycle的listening字段（一个ngx_listening_t的数组）。</li>
<li>对所有模块进行计数
 <pre>
 ngx_max_module = 0;
 for (i = 0; ngx_modules[i]; i++) {
     ngx_modules[i]->index = ngx_max_module++;
 }
</pre></li>
<li>调用ngx_init_cycle进行模块的初始化，当解析配置文件错误时，退出程序。这个函数传入init_cycle然后返回一个新的ngx_cycle_t。</li>
<li>调用ngx_signal_process、ngx_init_signals处理信号。</li>
<li>在daemon模式下，调用ngx_daemon以守护进程的方式运行。这里可以在./configure的时候加入参数&ndash;with-debug，并在nginx.conf中配置:
<pre>
master_process  off; # 简化调试 此指令不得用于生产环境
daemon          off; # 简化调试 此指令可以用到生产环境
</pre>
可以取消守护进程模式以及master线程模型。</li>
<li>调用ngx_create_pidfile创建pid文件，把master进程的pid保存在里面。</li>
<li><p>根据进程模式来分别调用相应的函数
<pre>
if (ngx_process == NGX_PROCESS_SINGLE) {
    ngx_single_process_cycle(cycle);</p>

<p>} else {
    ngx_master_process_cycle(cycle);
}
</pre>
多进程的情况下，调用ngx_master_process_cycle。单进程的情况下调用ngx_single_process_cycle完成最后的启动工作。</p></li>
</ol>


<p>整个启动过程中一个关键的变量init_cycle，其数据结构ngx_cycle_t如下所示：</p>

<pre>
struct ngx_cycle_s {
    void                  ****conf_ctx;
    ngx_pool_t               *pool;

    ngx_log_t                *log;
    ngx_log_t                 new_log;

    ngx_connection_t        **files;
    ngx_connection_t         *free_connections;
    ngx_uint_t                free_connection_n;

    ngx_queue_t               reusable_connections_queue;

    ngx_array_t               listening;
    ngx_array_t               paths;
    ngx_list_t                open_files;
    ngx_list_t                shared_memory;

    ngx_uint_t                connection_n;
    ngx_uint_t                files_n;

    ngx_connection_t         *connections;
    ngx_event_t              *read_events;
    ngx_event_t              *write_events;

    ngx_cycle_t              *old_cycle;

    ngx_str_t                 conf_file;
    ngx_str_t                 conf_param;
    ngx_str_t                 conf_prefix;
    ngx_str_t                 prefix;
    ngx_str_t                 lock_file;
    ngx_str_t                 hostname;
};
</pre>


<p>它保存了一次启动过程需要的一些资源。</p>

<p>ngx_init_cycle函数的处理过程如下：</p>

<ol>
<li>调用ngx_timezone_update()、ngx_timeofday、ngx_time_update()来更新时区、时间等，做时间校准，用来创建定时器等。</li>
<li><p>创建pool,赋给一个新的cycle（ngx_cycle_t）。这个新的cycle的一些字段从旧的cycle传递过来，比如：log,conf_prefix,prefix,conf_file,conf_param。
 <pre>
 cycle->conf_prefix.len = old_cycle->conf_prefix.len;
 cycle->conf_prefix.data = ngx_pstrdup(pool, &amp;old_cycle->conf_prefix);
 if (cycle->conf_prefix.data == NULL) {
     ngx_destroy_pool(pool);
     return NULL;
 }</p>

<p> cycle->prefix.len = old_cycle->prefix.len;
 cycle->prefix.data = ngx_pstrdup(pool, &amp;old_cycle->prefix);
 if (cycle->prefix.data == NULL) {
     ngx_destroy_pool(pool);
     return NULL;
 }</p>

<p> cycle->conf_file.len = old_cycle->conf_file.len;
 cycle->conf_file.data = ngx_pnalloc(pool, old_cycle->conf_file.len + 1);
 if (cycle->conf_file.data == NULL) {
     ngx_destroy_pool(pool);
     return NULL;
 }
 ngx_cpystrn(cycle->conf_file.data, old_cycle->conf_file.data,
             old_cycle->conf_file.len + 1);</p>

<p> cycle->conf_param.len = old_cycle->conf_param.len;
 cycle->conf_param.data = ngx_pstrdup(pool, &amp;old_cycle->conf_param);
 if (cycle->conf_param.data == NULL) {
     ngx_destroy_pool(pool);
     return NULL;
 }
 </pre>
还有一些字段会首先判断old_cycle中是否存在，如果存在，则申请同样大小的空间，并初始化。这些字段如下：
 <pre>
 n = old_cycle->paths.nelts ? old_cycle->paths.nelts : 10;</p>

<p> //paths
 cycle->paths.elts = ngx_pcalloc(pool, n * sizeof(ngx_path_t *));
 if (cycle->paths.elts == NULL) {
     ngx_destroy_pool(pool);
     return NULL;
 }</p>

<p> cycle->paths.nelts = 0;
 cycle->paths.size = sizeof(ngx_path_t *);
 cycle->paths.nalloc = n;
 cycle->paths.pool = pool;</p>

<p> //open_files
 if (old_cycle->open_files.part.nelts) {
     n = old_cycle->open_files.part.nelts;
     for (part = old_cycle->open_files.part.next; part; part = part->next) {
         n += part->nelts;
     }</p>

<p> } else {
     n = 20;
 }</p>

<p> //shared_memory
 if (old_cycle->shared_memory.part.nelts) {
     n = old_cycle->shared_memory.part.nelts;
     for (part = old_cycle->shared_memory.part.next; part; part = part->next)
     {
         n += part->nelts;
     }</p>

<p> } else {
     n = 1;
 }</p>

<p> //listening
 n = old_cycle->listening.nelts ? old_cycle->listening.nelts : 10;</p>

<p> cycle->listening.elts = ngx_pcalloc(pool, n * sizeof(ngx_listening_t));
 if (cycle->listening.elts == NULL) {
     ngx_destroy_pool(pool);
     return NULL;
 }</p>

<p> cycle->listening.nelts = 0;
 cycle->listening.size = sizeof(ngx_listening_t);
 cycle->listening.nalloc = n;
 cycle->listening.pool = pool;
</pre></p>

<p>  此外，new_log.log_level重新赋值的为NGX_LOG_ERR；old_cycle为传递进来的cycle；hostname为gethostname;初始化resuable_connection_queue。</p>

<p>  这里有一个关键变量的初始化：conf_ctx。初始化为ngx_max_module个void *指针。说明其实所有模块的配置结构的指针。</p></li>
<li><p>调用所有模块的create_conf，返回的配置结构指针放到conf_ctx数组中，索引为ngx_modules[i]->index。</p></li>
<li>从命令行和配置文件读取配置更新到conf_ctx中。ngx_conf_param是读取命令行中的指令，ngx_conf_parse是把读取配置文件。ngx_conf_param最后也是通过调用ngx_cong_parse来读取配置的。ngx_conf_parse函数中有一个for循环，每次都调用ngx_conf_read_token取得一个配置指令，然后调用ngx_conf_handler来处理这条指令。ngx_conf_handler每次会遍历所有模块的指令集，查找这条配置指令并分析其合法性，如果正确则创建配置结构并把指针加入到cycle.conf_ctx中。
 遍历指令集的过程首先是遍历所有的核心类模块，若是 event类的指令，则会遍历到ngx_events_module，这个模块是属于核心类的，其钩子set又会嵌套调用ngx_conf_parse去遍历所有的event类模块，同样的，若是http类指令，则会遍历到ngx_http_module，该模块的钩子set进一步遍历所有的http类模块，mail类指令会遍历到ngx_mail_module，该模块的钩子进一步遍历到所有的mail类模块。要特别注意的是：这三个遍历过程中会在适当的时机调用event类模块、http类模块和mail类模块的创建配置和初始化配置的钩子。从这里可以看出，event、http、mail三类模块的钩子是配置中的指令驱动的。</li>
<li>调用core module的init_conf。</li>
<li><p>读取核心模块ngx_core_module的配置结构，调用ngx_create_pidfile创建pid文件。
 <pre>
 ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
</pre></p>

<p>  这里代码中有一句注释：
  <pre>
   we do not create the pid file in the first ngx_init_cycle() call
   because we need to write the demonized process pid
</pre>
  当不是第一次初始化cycles时才会调用ngx_create_pidfile写入pid。</p></li>
<li>调用ngx_test_lockfile,ngx_create_paths并打开error_log文件复制给cycle->new_log.file。</li>
<li>遍历cycle的open_files.part.elts，打开每一个文件。open_files填充的文件数据是读取配置文件时写入的。</li>
<li>创建共享内存。这里和对open_files类似。先预分配空间，再填充数据。</li>
<li>处理listening sockets，遍历cycle->listening数组与old_cycle->listenning进行比较，设置cycle->listening的一些状态信息，调用ngx_open_listening_sockets启动所有监听socket，循环调用socket、bind、listen完成服务端监听监听socket的启动。并调用ngx_configure_listening_sockets配置监听socket,根据ngx_listening_t中的状态信息设置socket的读写缓存和TCP_DEFER_ACCEPT。</li>
<li>调用每个module的init_module。</li>
<li>关闭或者删除一些残留在old_cycle中的资源，首先释放不用的共性内存，关闭不使用的监听socket，再关闭不适用的打开文件。最后把old_cycle放入ngx_old_cycles。最后再设定一个定时器，定期回调ngx_cleaner_event清理ngx_old_cycles。周期设置为30000ms。</li>
</ol>


<p>接下来是进程的启动，包括master和worker进程。main函数最后调用ngx_master_process_cycle来启动master进程模式(这里对单进程模式不做讲述)。</p>

<ol>
<li>设置一些信号，如下：
 <pre>
 sigaddset(&amp;set, SIGCHLD);
 sigaddset(&amp;set, SIGALRM);
 sigaddset(&amp;set, SIGIO);
 sigaddset(&amp;set, SIGINT);
 sigaddset(&amp;set, ngx_signal_value(NGX_RECONFIGURE_SIGNAL));
 sigaddset(&amp;set, ngx_signal_value(NGX_REOPEN_SIGNAL));
 sigaddset(&amp;set, ngx_signal_value(NGX_NOACCEPT_SIGNAL));
 sigaddset(&amp;set, ngx_signal_value(NGX_TERMINATE_SIGNAL));
 sigaddset(&amp;set, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
 sigaddset(&amp;set, ngx_signal_value(NGX_CHANGEBIN_SIGNAL));
</pre></li>
<li>调用ngx_setproctitle设置进程标题："master process" + ngx_argv[0&hellip;]</li>
<li>启动worker进程,数量为ccf->worker_processes。
 <pre>
 ngx_start_worker_processes(cycle, ccf->worker_processes,
                            NGX_PROCESS_RESPAWN);
</pre></li>
<li>启动文件cache管理进程。
 <pre>
 ngx_start_cache_manager_processes(cycle, 0);
</pre>
 这里的cahche在一些模块中是需要的，如fastcgi模块等,这些模块会把文件cache路径添加到cycle->paths中，文件cache管理进程会定期调用这些模块的文件cache处理钩子处理一下文件cache。</li>
<li>master主循环，主要是循环处理信号量。在循环过程中，判断相应的条件然后进入相应的处理。这里的相关标志位基本都是在信号处理函数中赋值的。
 <pre>
 for ( ;; ) {
  // delay用来设置等待worker退出的时间，master接收了退出信号后首先发送退出信号给worker，
  // 而worker退出需要一些时间
  if (delay) {
      delay = 2;
          &hellip;
      itv.it_interval.tv_sec = 0;
      itv.it_interval.tv_usec = 0;
      itv.it_value.tv_sec = delay / 1000;
      itv.it_value.tv_usec = (delay % 1000 ) * 1000;
      // 设置定时器
      if (setitimer(ITIMER_REAL, &amp;itv, NULL) == -1) {
          ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
          “setitimer() failed”);
      }
  }
  ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, “sigsuspend”);
  // 挂起信号量，等待定时器
  sigsuspend(&amp;set);
  ngx_time_update(0, 0);
  ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, “wake up”);
  // 收到了SIGCHLD信号，有worker退出（ngx_reap==1）
  if (ngx_reap) {
      ngx_reap = 0;
      ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, “reap children”);
      // 处理所有worker，如果有worker异常退出则重启这个worker，如果所有worker都退出
      // 返回0赋值给live
      live = ngx_reap_children(cycle);
  }
  // 如果worker都已经退出，
  // 并且收到了NGX_CMD_TERMINATE命令或者SIGTERM信号或者SIGINT信号(ngx_terminate=1)
  // 或者NGX_CMD_QUIT命令或者SIGQUIT信号(ngx_quit=1)，则master退出
  if (!live &amp;&amp; (ngx_terminate || ngx_quit)) {
      ngx_master_process_exit(cycle);
  }
  // 收到了NGX_CMD_TERMINATE命令或者SIGTERM信号或者SIGINT信号，
  // 通知所有worker退出，并且等待worker退出
  if (ngx_terminate) {
      // 设置延时
      if (delay == 0) {
          delay = 50;
      }
      if (delay > 1000) {
          // 延时已到，给所有worker发送SIGKILL信号，强制杀死worker
          ngx_signal_worker_processes(cycle, SIGKILL);
      } else {
          // 给所有worker发送SIGTERM信号，通知worker退出
          ngx_signal_worker_processes(cycle,
          ngx_signal_value(NGX_TERMINATE_SIGNAL));
      }
      continue;
  }
  // 收到了NGX_CMD_QUIT命令或者SIGQUIT信号
  if (ngx_quit) {
      // 给所有worker发送SIGQUIT信号
      ngx_signal_worker_processes(cycle,
      ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
      // 关闭所有监听的socket
      ls = cycle->listening.elts;
      for (n = 0; n &lt; cycle->listening.nelts; n++) {
          if (ngx_close_socket(ls[n].fd) == -1) {
              ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
                  ngx_close_socket_n ” %V failed”,&amp;ls[n].addr_text);
          }
      }
      cycle->listening.nelts = 0;
  continue;
  }
  // 收到了SIGHUP信号
  if (ngx_reconfigure) {
      ngx_reconfigure = 0;
      // 代码已经被替换，重启worker，不需要重新初始化配置
      if (ngx_new_binary) {
          ngx_start_worker_processes(cycle, ccf->worker_processes,
              NGX_PROCESS_RESPAWN);
          ngx_start_cache_manager_processes(cycle, 0);
          ngx_noaccepting = 0;
          continue;
      }
      ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, “reconfiguring”);
      // 重新初始化配置
      cycle = ngx_init_cycle(cycle);
      if (cycle == NULL) {
          cycle = (ngx_cycle_t ) ngx_cycle;
          continue;
      }
      // 重启worker
      ngx_cycle = cycle;
      ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,
      ngx_core_module);
      ngx_start_worker_processes(cycle, ccf->worker_processes,
          NGX_PROCESS_JUST_RESPAWN);
      ngx_start_cache_manager_processes(cycle, 1);
      live = 1;
      ngx_signal_worker_processes(cycle,
          ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
  }
  // 当ngx_noaccepting=1的时候会把ngx_restart设为1，重启worker
  if (ngx_restart) {
      ngx_restart = 0;
      ngx_start_worker_processes(cycle, ccf->worker_processes,
          NGX_PROCESS_RESPAWN);
      ngx_start_cache_manager_processes(cycle, 0);
      live = 1;
  }
  // 收到SIGUSR1信号，重新打开log文件
  if (ngx_reopen) {
      ngx_reopen = 0;
      ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, “reopening logs”);
      ngx_reopen_files(cycle, ccf->user);
      ngx_signal_worker_processes(cycle,
      ngx_signal_value(NGX_REOPEN_SIGNAL));
  }
  // 收到SIGUSR2信号，热代码替换
  if (ngx_change_binary) {
      ngx_change_binary = 0;
      ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, “changing binary”);
      // 调用execve执行新的代码
      ngx_new_binary = ngx_exec_new_binary(cycle, ngx_argv);
  }
  // 收到SIGWINCH信号，不再接收请求，worker退出，master不退出
  if (ngx_noaccept) {
      ngx_noaccept = 0;
      ngx_noaccepting = 1;
      ngx_signal_worker_processes(cycle,
      ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
  }
}
</pre>
信号处理函数是在main函数中进行的初始化：ngx_init_signals(cycle->log)。其中的signal handler代码如下所示：</li>
</ol>


<pre>
void
ngx_signal_handler(int signo)
{
    char            *action;
    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_time_sigsafe_update();

    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;

        case ngx_signal_value(NGX_NOACCEPT_SIGNAL):
            if (ngx_daemonized) {
                ngx_noaccept = 1;
                action = ", stop accepting connections";
            }
            break;

        case ngx_signal_value(NGX_RECONFIGURE_SIGNAL):
            ngx_reconfigure = 1;
            action = ", reconfiguring";
            break;

        case ngx_signal_value(NGX_REOPEN_SIGNAL):
            ngx_reopen = 1;
            action = ", reopening logs";
            break;

        case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):
            if (getppid() > 1 || ngx_new_binary > 0) {

                /*
                 * Ignore the signal in the new binary if its parent is
                 * not the init process, i.e. the old binary's process
                 * is still running.  Or ignore the signal in the old binary's
                 * process if the new binary's process is already running.
                 */

                action = ", ignoring";
                ignore = 1;
                break;
            }

            ngx_change_binary = 1;
            action = ", changing binary";
            break;

        case SIGALRM:
            ngx_sigalrm = 1;
            break;

        case SIGIO:
            ngx_sigio = 1;
            break;

        case SIGCHLD:
            ngx_reap = 1;
            break;
        }

        break;

    case NGX_PROCESS_WORKER:
    case NGX_PROCESS_HELPER:
        switch (signo) {

        case ngx_signal_value(NGX_NOACCEPT_SIGNAL):
            if (!ngx_daemonized) {
                break;
            }
            ngx_debug_quit = 1;
        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;

        case ngx_signal_value(NGX_REOPEN_SIGNAL):
            ngx_reopen = 1;
            action = ", reopening logs";
            break;

        case ngx_signal_value(NGX_RECONFIGURE_SIGNAL):
        case ngx_signal_value(NGX_CHANGEBIN_SIGNAL):
        case SIGIO:
            action = ", ignoring";
            break;
        }

        break;
    }

    ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0,
                  "signal %d (%s) received%s", signo, sig->signame, action);

    if (ignore) {
        ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, 0,
                      "the changing binary signal is ignored: "
                      "you should shutdown or terminate "
                      "before either old or new binary's process");
    }

    if (signo == SIGCHLD) {
        ngx_process_get_status();
    }

    ngx_set_errno(err);
}
</pre>


<p>创建worker子进程的函数是ngx_start_worker_processes。</p>

<pre>
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;

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

    ch.command = NGX_CMD_OPEN_CHANNEL; //传递给其他worker子进程的命令：打开通信管道

    //创建n个worker进程
    for (i = 0; i < n; i++) {

        //创建worker子进程并初始化相关资源和属性
        ngx_spawn_process(cycle, ngx_worker_process_cycle,
                          (void *) (intptr_t) i, "worker process", type);

        /*master父进程向所有已经创建的worker子进程（不包括本子进程）广播消息
        包括当前worker子进程的进程id、在进程表中的位置和管道句柄，这些worker子进程收到消息后，会更新这些消息到自己进程空间的进程表，以此实现两个worke子进程之间的通信。
        */
        ch.pid = ngx_processes[ngx_process_slot].pid;
        ch.slot = ngx_process_slot;
        ch.fd = ngx_processes[ngx_process_slot].channel[0];

        ngx_pass_open_channel(cycle, &ch);
    }
}
</pre>


<p>这里ngx_pass_open_channel，即遍历所有worker进程，跳过自己和异常的worker，把消息发送给各个worker进程。worker进程的管道可读事件捕捉函数是ngx_channel_handler(ngx_event_t *ev)，在这个函数中，会读取message，然后解析，并根据不同给的命令做不同的处理。</p>

<p>这里有一个关键的函数是ngx_pid_t ngx_spawn_process(ngx_cycle_t <em>cycle, ngx_spawn_proc_pt proc, void </em>data,char *name, ngx_int_t respawn)。proc是子进程的执行函数，data是其参数，name是进程名。</p>

<p>这个函数的任务：</p>

<ol>
<li>有一个ngx_processes全局数组，包含了所有的子进程，这里会fork出子进程并放入相应的位置，并设置这个进程的相关属性。</li>
<li>创建socketpair，并设置相关属性</li>
<li>子啊子进程中执行传递进来的函数。</li>
</ol>


<pre>
u_long     on;
ngx_pid_t  pid;
ngx_int_t  s; //fork的子进程在ngx_processes中的位置

//如果传递进来的respawn>0，说明是要替换进程ngx_processes[respawn]，可安全重用该进程表项。
if (respawn >= 0) {
   s = respawn;
} else {
   遍历进程表，找到空闲的slot
   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_INVALID_PID;
        }
    }
</pre>


<p></p>

<p>接下来创建一对socketpair句柄，然后初始化相关属性。</p>

<pre>
    if (respawn != NGX_PROCESS_DETACHED) { //NGX_PROCESS_DETACHED是热代码替换

        /* Solaris 9 still has no AF_LOCAL */

        //建立socketpair
        if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1)
        {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "socketpair() failed while spawning \"%s\"", name);
            return NGX_INVALID_PID;
        }

        。。。

        //设置非阻塞模式
        if (ngx_nonblocking(ngx_processes[s].channel[0]) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          ngx_nonblocking_n " failed while spawning \"%s\"",
                          name);
            ngx_close_channel(ngx_processes[s].channel, cycle->log);
            return NGX_INVALID_PID;
        }

        if (ngx_nonblocking(ngx_processes[s].channel[1]) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          ngx_nonblocking_n " failed while spawning \"%s\"",
                          name);
            ngx_close_channel(ngx_processes[s].channel, cycle->log);
            return NGX_INVALID_PID;
        }

        //打开异步模式
        on = 1;
        if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "ioctl(FIOASYNC) failed while spawning \"%s\"", name);
            ngx_close_channel(ngx_processes[s].channel, cycle->log);
            return NGX_INVALID_PID;
        }

        //设置异步io所有者
        if (fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "fcntl(F_SETOWN) failed while spawning \"%s\"", name);
            ngx_close_channel(ngx_processes[s].channel, cycle->log);
            return NGX_INVALID_PID;
        }

        //当exec后关闭句柄
        if (fcntl(ngx_processes[s].channel[0], F_SETFD, FD_CLOEXEC) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
                           name);
            ngx_close_channel(ngx_processes[s].channel, cycle->log);
            return NGX_INVALID_PID;
        }

        if (fcntl(ngx_processes[s].channel[1], F_SETFD, FD_CLOEXEC) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
                           name);
            ngx_close_channel(ngx_processes[s].channel, cycle->log);
            return NGX_INVALID_PID;
        }

        //设置当前子进程的句柄
        ngx_channel = ngx_processes[s].channel[1];

    } else {
        ngx_processes[s].channel[0] = -1;
        ngx_processes[s].channel[1] = -1;
    }
</pre>


<p>接下来就是fork子进程，并设置进程相关参数。</p>

<pre>
    //设置进程在进程表中的slot
    ngx_process_slot = s;

    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_INVALID_PID;

    case 0:
        //子进程，执行传递进来的子进程函数
        ngx_pid = ngx_getpid();
        proc(cycle, data);
        break;

    default:
        break;
    }

    ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start %s %P", name, pid);

    ngx_processes[s].pid = pid;
    ngx_processes[s].exited = 0;

    if (respawn >= 0) { //使用原来的子进程即可
        return pid;
    }

    //初始化进程结构
    ngx_processes[s].proc = proc;
    ngx_processes[s].data = data;
    ngx_processes[s].name = name;
    ngx_processes[s].exiting = 0;

    switch (respawn) {

    case NGX_PROCESS_NORESPAWN:
        ngx_processes[s].respawn = 0;
        ngx_processes[s].just_spawn = 0;
        ngx_processes[s].detached = 0;
        break;

    case NGX_PROCESS_JUST_SPAWN:
        ngx_processes[s].respawn = 0;
        ngx_processes[s].just_spawn = 1;
        ngx_processes[s].detached = 0;
        break;

    case NGX_PROCESS_RESPAWN:
        ngx_processes[s].respawn = 1;
        ngx_processes[s].just_spawn = 0;
        ngx_processes[s].detached = 0;
        break;

    case NGX_PROCESS_JUST_RESPAWN:
        ngx_processes[s].respawn = 1;
        ngx_processes[s].just_spawn = 1;
        ngx_processes[s].detached = 0;
        break;

    case NGX_PROCESS_DETACHED:
        ngx_processes[s].respawn = 0;
        ngx_processes[s].just_spawn = 0;
        ngx_processes[s].detached = 1;
        break;
    }

    if (s == ngx_last_process) {
        ngx_last_process++;
    }   
</pre>


<p>最后看一下，worker进程执行的函数static void
ngx_worker_process_cycle(ngx_cycle_t <em>cycle, void </em>data)。</p>

<ol>
<li><p>调用ngx_worker_process_init初始化；</p>

<ul>
<li>设置ngx_process=NGX_PROCESS_WORKER</li>
<li>全局性的设置，包括执行环境、优先级、限制、setgid、setuid、信号初始化</li>
<li>调用所有模块的init_process钩子</li>
<li>关闭不使用的管道句柄，关闭当前的worker子进程的channel[0]句柄和继承来的其他进程的channel[1]句柄。使用其他进程的channel[0]句柄发送消息，使用本进程的channel[1]句柄监听事件。</li>
</ul>
</li>
<li><p>进行线程相关的操作。（如果有线程模式）</p></li>
<li>主循环处理各种状态，类似master进程的主循环。</li>
</ol>


<pre>   
for ( ;; ) {

        if (ngx_exiting) { //退出状态，关闭所有连接

            c = cycle->connections;

            for (i = 0; i < cycle->connection_n; i++) {

                /* THREAD: lock */

                if (c[i].fd != -1 && c[i].idle) {
                    c[i].close = 1;
                    c[i].read->handler(c[i].read);
                }
            }

            if (ngx_event_timer_rbtree.root == ngx_event_timer_rbtree.sentinel)
            {
                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) { //收到NGX_CMD_TERMINATE命令，清理进城后退出，并调用所有模块的exit_process钩子。
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");

            ngx_worker_process_exit(cycle);
        }

        if (ngx_quit) {
            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) {
                //关闭监听socket，并设置退出状态
                ngx_close_listening_sockets(cycle);
                ngx_exiting = 1;
            }
        }

        if (ngx_reopen) { //重新打开log
            ngx_reopen = 0;
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
            ngx_reopen_files(cycle, -1);
        }
    }
</pre>


<p>总结一下，nginx的启动过程可以划分为两个部分，</p>

<ol>
<li>读取配置文件并设置全局的配置结构信息以及每个模块的配置结构信息，调用模块的 create_conf钩子和init_conf钩子。</li>
<li>创建进程和进程间通信机制，master进程负责管理各个worker子进程，通过 socketpair向子进程发送消息，各个worker子进程服务利用事件机制处理请求，通过socketpair与其他子进程通信（发送消息或者接收消息），进程启动的各个适当时机会调用模块的init_module钩子、init_process钩子、exit_process钩子和 exit_master钩子，init_master钩子没有被调用过。nginx的worker子进程继承了父进程的全局变量之后，子进程和父进程就会独立处理这些全局变量，有些全局量需要在父子进程之间同步就要通过通信方式了，比如 ngx_processes（进程表）的同步。</li>
</ol>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Nginx负载均衡]]></title>
    <link href="https://www.rowkey.cn/blog/2014/08/27/nginx-loadbalance/"/>
    <updated>2014-08-27T14:32:50+08:00</updated>
    <id>https://www.rowkey.cn/blog/2014/08/27/nginx-loadbalance</id>
    <content type="html"><![CDATA[<h2>目录</h2>

<ul>
<li><a href="#%E4%B8%80%20%E7%89%B9%E7%82%B9">一 特点</a></li>
<li><a href="#%E4%BA%8C%20%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1">二 负载均衡</a></li>
<li><a href="#%E4%B8%89%20%E5%8A%A8%E6%80%81%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1">三 动态负载均衡</a></li>
<li><a href="#%E5%9B%9B%20%E4%BC%98%E5%8A%BF%E5%92%8C%E5%8A%A3%E5%8A%BF">四 优势和劣势</a></li>
<li><a href="#%E4%BA%94%20Tengine">五 Tengine</a></li>
</ul>


<p><strong><em>本文最新更新于2016.11.01</em></strong></p>

<h2><a name='一 特点'></a>一 特点</h2>

<h3>1.1 应用情况</h3>

<p>Nginx做为一个强大的Web服务器软件，具有高性能、高并发性和低内存占用的特点。此外，其也能够提供强大的反向代理功能。俄罗斯大约有超过20%的虚拟主机采用Nginx作为反向代理服务器,在国内也有腾讯、新浪、网易等多家网站在使用Nginx作为反向代理服务器。据Netcraft统计，世界上最繁忙的网站中有11.48%使用Nginx作为其服务器或者代理服务器。基于反向代理的功能，Nginx作为负载均衡主要有以下几点理由：</p>

<ol>
<li>高并发连接</li>
<li>内存消耗少</li>
<li>配置文件非常简单</li>
<li>成本低廉</li>
<li>支持Rewrite重写规则</li>
<li>内置的健康检查功能</li>
<li>节省带宽</li>
<li>稳定性高</li>
</ol>


<!--more-->


<h3>1.2 架构</h3>

<p><img src="//images/blog_images/nginx/ngx_arch.jpg" alt="" /></p>

<p>上图为来自官方的一张总体架构图。</p>

<p>nginx在启动后，会以daemon的方式在后台运行，后台进程包含一个master进程和多个worker进程。工作进程以非特权用户运行。</p>

<p>master进程主要用来管理worker进程，包含：接收来自外界的信号，向各worker进程发送信号，监控worker进程的运行状态，当worker进程退出后(异常情况下)，会自动重新启动新的worker进程。</p>

<p>worker进程则是处理基本的网络事件。多个worker进程之间是对等的，他们同等竞争来自客户端的请求，各进程互相之间是独立的。一个请求，只可能在一个worker进程中处理，一个worker进程，不可能处理其它进程的请求。</p>

<p>开发模型：epoll和kqueue。</p>

<p>支持的事件机制：kqueue、epoll、rt signals、/dev/poll 、event ports、select以及poll。</p>

<p>支持的kqueue特性包括EV_CLEAR、EV_DISABLE、NOTE_LOWAT、EV_EOF，可用数据的数量，错误代码.</p>

<p>支持sendfile、sendfile64和sendfilev;文件AIO；DIRECTIO;支持Accept-filters和TCP_DEFER_ACCEP.</p>

<h3>1.3 性能</h3>

<p>Nginx的高并发，官方测试支持5万并发连接。实际生产环境能到2-3万并发连接数。10000个非活跃的HTTP keep-alive 连接仅占用约2.5MB内存。三万并发连接下，10个Nginx进程，消耗内存150M。淘宝tengine团队说测试结果是“24G内存机器上，处理并发请求可达200万”。</p>

<h2><a name='二 负载均衡'></a>二 负载均衡</h2>

<h3>2.1 协议支持</h3>

<p>Nginx工作在网络的7层，可以针对http应用本身来做分流策略。支持七层HTTP、HTTPS协议的负载均衡。对四层协议的支持需要第三方插件-yaoweibin的ngx_tcp_proxy_module实现了tcp upstream。</p>

<p><a href="https://github.com/yaoweibin/nginx_tcp_proxy_module">https://github.com/yaoweibin/nginx_tcp_proxy_module</a></p>

<p>此外，nginx本身也逐渐在完善对其他协议的支持：</p>

<ul>
<li>nginx 1.4.0对Websocket和SPDY都做了正式的支持。</li>
<li>nginx 1.6.0对SPDY 3.1的正式支持</li>
<li>nginx 1.10.0正式支持HTTP/2</li>
</ul>


<p>目前，nginx最新稳定版为1.10.2，主线开发版本已经到了1.11.5。Tengine最新版本则继承到了nginx的1.6.2版本。</p>

<h3>2.2 均衡策略</h3>

<p>nginx的负载均衡策略可以划分为两大类：内置策略和扩展策略。内置策略包含加权轮询和ip hash，在默认情况下这两种策略会编译进nginx内核，只需在nginx配置中指明参数即可。扩展策略有很多，如fair、通用hash、consistent hash等，默认不编译进nginx内核。</p>

<ol>
<li><p>加权轮询（weighted round robin）</p>

<p> 轮询的原理很简单，首先我们介绍一下轮询的基本流程。如下是处理一次请求的流程图：</p>

<p> <img src="//images/blog_images/nginx/ngx_wr.png" alt="" /></p>

<p> 图中有两点需要注意，第一，如果可以把加权轮询算法分为先深搜索和先广搜索，那么nginx采用的是先深搜索算法，即将首先将请求都分给高权重的机器，直到该机器的权值降到了比其他机器低，才开始将请求分给下一个高权重的机器；第二，当所有后端机器都down掉时，nginx会立即将所有机器的标志位清成初始状态，以避免造成所有的机器都处在timeout的状态，从而导致整个前端被夯住。</p></li>
<li><p>ip hash</p>

<p> ip hash是nginx内置的另一个负载均衡的策略，流程和轮询很类似，只是其中的算法和具体的策略有些变化，如下图所示：</p>

<p> <img src="//images/blog_images/nginx/ngx_iphash.png" alt="" /></p>

<p> ip hash算法的核心实现如下：</p>

<pre><code> for(i = 0;i &lt; 3;i++){
     hash = (hash * 113 + iphp-&gt;addr[i]) % 6271; 
 }

 p = hash % iphp-&gt;rrp.peers-&gt;number;     
</code></pre>

<p> 从代码中可以看出，hash值既与ip有关又与后端机器的数量有关。经过测试，上述算法可以连续产生1045个互异的value，这是该算法的硬限制。对此nginx使用了保护机制，当经过20次hash仍然找不到可用的机器时，算法退化成轮询。因此，从本质上说，ip hash算法是一种变相的轮询算法，如果两个ip的初始hash值恰好相同，那么来自这两个ip的请求将永远落在同一台服务器上，这为均衡性埋下了很深的隐患。</p></li>
<li><p>fair</p>

<p> fair策略是扩展策略，默认不被编译进nginx内核。其原理是根据后端服务器的响应时间判断负载情况，从中选出负载最轻的机器进行分流。这种策略具有很强的自适应性，但是实际的网络环境往往不是那么简单，因此要慎用。</p></li>
<li><p>通用hash、一致性hash</p>

<p> 这两种也是扩展策略，在具体的实现上有些差别，通用hash比较简单，可以以nginx内置的变量为key进行hash，一致性hash采用了nginx内置的一致性hash环，可以支持memcache。</p></li>
<li><p>session_sticky</p>

<p> 此种策略就是一次会话内的请求都会落到同一个结点上。在做分布式架构时可以使用，但是当一个结点挂掉时，会话信息同时也会丢失,如果使用session同步方案同步session信息到所有结点的话代价又会很高，慎重使用此方案。nginx默认不支持此种策略，tengine提供了支持: <a href="http://tengine.taobao.org/document_cn/http_upstream_session_sticky_cn.html">http://tengine.taobao.org/document_cn/http_upstream_session_sticky_cn.html</a>.</p></li>
</ol>


<h3>2.2 配置示例</h3>

<ol>
<li><p>HTTP</p>

<pre><code> upstream upstream_test{  
     server 192.168.0.1:8080;
     server 192.168.0.2:8080;

     #ip_hash;
     keepalive 30;

     ## tengine config
     #check interval=300 rise=10 fall=10 timeout=100 type=http port=80;
     #check_http_send "GET / HTTP/1.0\r\n\r\n";
     #check_http_expect_alive http_2xx http_3xx;

     ## tengine config
     #session_sticky cookie=cookieTest mode=insert;
 }  

 location / {

     proxy_pass         http://upstream_test;
     proxy_set_header   Host             $host;
     proxy_set_header   X-Real-IP        $remote_addr;
     proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;

 }
</code></pre>

<p> 对于nginx更为具体的配置可参考：<a href="https://github.com/superhj1987/awesome-config/blob/master/nginx/nginx.conf">https://github.com/superhj1987/awesome-config/blob/master/nginx/nginx.conf</a></p></li>
<li><p>TCP - ngx_tcp_proxy_module</p>

<pre><code> tcp {

     upstream cluster {

         # simple round-robin

         server 192.168.0.1:8080;

         server 192.168.0.2:8080;



         check interval=3000 rise=2 fall=5 timeout=1000;



         #check interval=3000 rise=2 fall=5 timeout=1000 type=ssl_hello;



         #check interval=3000 rise=2 fall=5 timeout=1000 type=http;

         #check_http_send "GET / HTTP/1.0\r\n\r\n";

         #check_http_expect_alive http_2xx http_3xx;

     }



     server {

         listen 8888;

         proxy_pass cluster;

     }

 }
</code></pre></li>
</ol>


<h2><a name='三 动态负载均衡'></a>三 动态负载均衡</h2>

<h3>3.1 自身监控</h3>

<p>内置了对后端服务器的健康检查功能。如果Nginx proxy后端的某台服务器宕机了，会把返回错误的请求重新提交到另一个节点，不会影响前端访问。它没有独立的健康检查模块，而是使用业务请求作为健康检查，这省去了独立健康检查线程，这是好处。坏处是，当业务复杂时，可能出现误判，例如后端响应超时，这可能是后端宕机，也可能是某个业务请求自身出现问题，跟后端无关。</p>

<h3>3.2 可扩展性</h3>

<p>Nginx属于典型的微内核设计，其内核非常简洁和优雅，同时具有非常高的可扩展性。如下图所示：</p>

<p><img src="//images/blog_images/nginx/ngx_micro.png" alt="" /></p>

<p>Nginx是纯C语言的实现，其可扩展性在于其模块化的设计。目前，Nginx已经有很多的第三方模块，大大扩展了自身的功能。nginx_lua_module可以将Lua语言嵌入到Nginx配置中，从而利用Lua极大增强了Nginx本身的编程能力，甚至可以不用配合其它脚本语言（如PHP或Python等），只靠Nginx本身就可以实现复杂业务的处理。</p>

<h3>3.3 配置修改</h3>

<p>nginx的配置架构如下图所示：</p>

<p><img src="//images/blog_images/nginx/ngx_config.png" alt="" /></p>

<p>Nginx支持热部署，几乎可以做到7*24不间断运行，即使运行数个月也不需要重新启动。能够在不间断服务的情况下，对软件版本进行进行升级。Nginx的配置文件非常简单，风格跟程序一样通俗易懂，能够支持perl语法。使用nginx –s reload可以在运行时加载配置文件，便于运行时扩容/减容。重新加载配置时，master进程发送命令给当前正在运行的worker进程worker进程接到命令后会在处理完当前任务后退出。同时，master进程会启动新的worker进程来接管工作。</p>

<h2><a name='四 优势和劣势'></a>四 优势和劣势</h2>

<h3>4.1 优势</h3>

<ol>
<li>可以很好地进行http 的头处理</li>
<li>对http协议以及https的良好支持</li>
<li>有足够的第三方插件供使用</li>
<li>支持热部署，更改后端是平滑的</li>
</ol>


<h3>4.2 劣势</h3>

<ol>
<li>缺少对session的支持</li>
<li>对四层tcp的支持不够好</li>
<li>post请求写文件系统，造成500 error</li>
<li>缺乏主动的后端服务器健康监测</li>
<li>默认的监控界面统计信息不全</li>
</ol>


<h2><a name='五 Tengine'></a>五 Tengine</h2>

<p>Tengine是淘宝基于nginx开源代码二次开发一款服务器软件，在继承了nginx的特性以外，提供了一些nginx商业版才有的功能。基本上同步于nginx的更新，目前最新的版本已经继承了nginx 1.6.2稳定版。</p>

<h3>5.1 特性</h3>

<p>tengine的特性包括但不限于：</p>

<ol>
<li>更友好的运维信息显示</li>
<li>动态模块加载机制</li>
<li>自动根据CPU数目设置进程个数和绑定CPU亲缘性</li>
<li>更方便的命令行参数，如列出编译的模块列表、支持的指令等</li>
<li>更加强大的负载均衡能力，包括一致性hash模块、会话保持模块，还可以对后端的服务器进行主动健康检查，根据服务器状态自动上线下线</li>
<li>动态脚本语言Lua支持。扩展功能非常高效简单</li>
<li>输入过滤器机制支持。通过使用这种机制Web应用防火墙的编写更为方便</li>
</ol>


<h3>5.2 负载均衡</h3>

<p>负载均衡方面，Tengine主要有以下几个特点，基本上弥补了nginx在负载均衡方面的欠缺：</p>

<ol>
<li>支持一致性Hash模块</li>
<li>会话保持模块</li>
<li>对后端服务器的主动健康检查。</li>
<li>增加了请求体不缓存到磁盘的机制</li>
</ol>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Nginx源码分析之基本数据结构]]></title>
    <link href="https://www.rowkey.cn/blog/2014/07/25/nginx-data-structure/"/>
    <updated>2014-07-25T17:08:02+08:00</updated>
    <id>https://www.rowkey.cn/blog/2014/07/25/nginx-data-structure</id>
    <content type="html"><![CDATA[<h3>引言</h3>

<p>nginx实现中有很多结构体，一般命名为ngx_xxx_t。这些结构体分散在许多头文件中。src/core/ngx_core.h中把几乎所有的头文件都集合起来。也因此造成了nginx各部分源代码的耦合。但实际上nginx各个部分逻辑划分还是很明确的，整体上是一种松散的结构。</p>

<p>作者之所以重复造了这些轮子，无非是为了追求高效。查看这些数据结构的源码，的确是设计的比较精巧，也保证了对内存足够小的占用以及各种操作的高效。</p>

<!--more-->


<h3>数据结构</h3>

<p>nginx实现中有很多结构体，一般命名为ngx_XXX_t。这些结构体分散在许多头文件中。src/core/ngx_core.h中把几乎所有的头文件都集合起来。也因此造成了nginx各部分源代码的耦合。但实际上nginx各个部分逻辑划分还是很明确的，整体上是一种松散的结构。</p>

<ul>
<li><p>ngx_str_t</p>

<pre><code>  typedef struct{
      size_t len;
      u_char *data;
  }ngx_str_t;
</code></pre></li>
</ul>


<p>这是nginx对字符串的实现，源码在ngx_string.h中。len指的是字符串的长度（不包括\0），data指向字符串。这种设计一方面，在计算字符创长度时只需要读取len字段即可，另一方面可以重复引用一段字符串内存。</p>

<p>常用api:</p>

<pre><code>    #define ngx_string(str) { sizof(str) - 1},(u_char *) str } //从一个普通字符串构造出一个nginx字符串，用sizeof计算长度，故参数必须是一个常量字符串。

    #define ngx_null_string {0,NULL}

    ngx_strncmp(s1,s2,n)

    ngx_strcm(s1,s2)
</code></pre>

<ul>
<li><p>ngx_pool_t</p>

<pre><code>  struct ngx_pool_s {
      ngx_pool_data_t       d;
      size_t                max;
      ngx_pool_t           *current;
      ngx_chain_t          *chain;
      ngx_pool_large_t     *large;
      ngx_pool_cleanup_t   *cleanup;
      ngx_log_t            *log;
  };
</code></pre></li>
</ul>


<p>这个数据结构在nginx中是一个非常重要的数据结构。用来管理一系列的资源（如内存、文件等
，使得对这些资源的使用和释放统一进行。这个是在c语言编程中值得借鉴的一个东西，代码中如果到处都是malloc和free的话，不仅会导致内存泄露，也会使代码难以阅读和维护。</p>

<ul>
<li><p>ngx_array_t</p>

<pre><code>  struct ngx_array_s {
      void        *elts; //指向实际的存储区域
      ngx_uint_t   nelts; //数组实际元素个数
      size_t       size; //数组单个元素的大小，单位是字节
      ngx_uint_t   nalloc; //数组的容量
      ngx_pool_t  *pool; //该数组用来分配内存的内存池
  };
</code></pre></li>
<li><p>ngx_hash_t</p>

<ul>
<li>ngx_hash_t不像其他的hash表的实现，可以插入删除元素，只能一次初始化。</li>
<li><p>解决冲突使用的是开链法，但实际上是开了一段连续的存储空间，和数组差不多。            <br/>
      ngx_int_t ngx_hash_init(ngx_hash_init_t <em>hinit, ngx_hash_key_t </em>names,ngx_uint_t nelts);//ngx_hash_t的初始化。</p>

<pre><code>  ngx_hash_init_t提供了初始化一个hash表所需要的一些基本信息
  typedef struct {
      ngx_hash_t       *hash; //指向hash表
      ngx_hash_key_pt   key; //指向从字符串生成hash值的hash函数。默认的实现为ngx_hash_key_lc
      ngx_uint_t        max_size; //hash表中的桶的个数
      ngx_uint_t        bucket_size; //每个桶的最大限制大小，单位是字节
      char             *name; //hash表的名字
      ngx_pool_t       *pool; //hash表分配内存使用的pool
      ngx_pool_t       *temp_pool; //使用的临时pool,初始化完成后，可以释放和销毁
  } ngx_hash_init_t;

  typedef struct {                ngx_str_t         key;              ngx_uint_t        key_hash;             void             *value;            } ngx_hash_key_t;
  void *ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len); //在hash里面查找key对应的value。          
</code></pre></li>
</ul>
</li>
<li><p>ngx_chain_t</p></li>
</ul>


<p>nginx的filter模块在处理从别的filter模块或者是handler模块传递过来的数据，数据一个链表的形式（ngx_chain_t）进行传递。</p>

<pre><code>        struct ngx_chain_s {
            ngx_buf_t    *buf;
            ngx_chain_t  *next;
        };
</code></pre>

<p>创建ngx_chain_t对象</p>

<pre><code>        ngx_chain_t *ngx_alloc_chain_link(ngx_pool_t *pool);
</code></pre>

<p>释放一个ngx_chain_t类型的对象。如果要释放整个chain，则迭代此链表，对每个节点使用此宏即可。</p>

<pre><code>        释放一个ngx_chain_t类型的对象。如果要释放整个chain，则迭代此链表，对每个节点使用此宏即可。
</code></pre>

<ul>
<li>ngx_buf_t</li>
</ul>


<p>ngx_buf_t是ngx_chain_t的数据结点</p>

<pre><code>    struct ngx_buf_s {
        u_char          *pos;
        u_char          *last;
        off_t            file_pos;
        off_t            file_last;

        u_char          *start;         /* start of buffer */
        u_char          *end;           /* end of buffer */
        ngx_buf_tag_t    tag;
        ngx_file_t      *file;
        ngx_buf_t       *shadow;


        /* the buf's content could be changed */
        unsigned         temporary:1;

        /*
        * the buf's content is in a memory cache or in a read only memory
        * and must not be changed
        */
        unsigned         memory:1;

        /* the buf's content is mmap()ed and must not be changed */
        unsigned         mmap:1;

        unsigned         recycled:1;
        unsigned         in_file:1;
        unsigned         flush:1;
        unsigned         sync:1;
        unsigned         last_buf:1;
        unsigned         last_in_chain:1;

        unsigned         last_shadow:1;
        unsigned         temp_file:1;

        /* STUB */ int   num;
    };
</code></pre>

<ul>
<li>ngx_list_t</li>
</ul>


<p>和普通的链表实现相比，它的节点是一个固定大小的数组。在初始化的时候，我们需要设定元素需要占用的空间大小，每个节点数组的容量大小。在添加元素到这个list里面的时候，会在最尾部的节点里的数组上添加元素，如果这个节点的数组存满了，就再增加一个新的节点到这个list里面去。</p>

<pre><code>    typedef struct {
        ngx_list_part_t  *last; //指向该链表的最后一个节点
        ngx_list_part_t   part; //指向该链表首个存放具体元素的节点
        size_t            size; //链表中存放的具体元素所需内存大小
        ngx_uint_t        nalloc; //每个节点所含的固定大小的数组的容量
        ngx_pool_t       *pool; //该list使用的分配内存的pool
    } ngx_list_t;

    struct ngx_list_part_s {
        void             *elts; //节点中存放具体元素的内存的开始地址   
        ngx_uint_t        nelts; //节点中已有元素个数，不能大于 nalloc
        ngx_list_part_t  *next; //指向下一个节点
    };

    ngx_list_t *ngx_list_create(ngx_pool_t *pool, ngx_uint_t n, size_t size); //创建一个ngx_list_t类型的对象,并对该list的第一个节点分配存放元素的内存空间。

    pool:   分配内存使用的pool。
    n:  每个节点固定长度的数组的长度。
    size:   存放的具体元素的个数。
</code></pre>

<ul>
<li><p>ngx_queue_t</p>

<pre><code>  struct ngx_queue_s {
      ngx_queue_t  *prev;
      ngx_queue_t  *next;
  };
</code></pre></li>
</ul>


<p>链表节点的数据成员并没有声明在链表节点的结构体中，只是声明了前向和后向指针。使用的时候需要定义一个哨兵节点。具体存放数据的节点称之为数据节点。对于数据节点，需要在数据结构体中加入一个类型为ngx_queue_t的域。使用下面的函数进行数据插入，其中x为数据节点的ngx_queue_t域。</p>

<pre><code>    #define ngx_queue_insert_head(h, x)                         \
        (x)-&gt;next = (h)-&gt;next;                                  \
        (x)-&gt;next-&gt;prev = x;                                    \
        (x)-&gt;prev = h;                                          \
        (h)-&gt;next = x

    #define ngx_queue_insert_after   ngx_queue_insert_head

    #define ngx_queue_insert_tail(h, x)                          \
        (x)-&gt;prev = (h)-&gt;prev;                                   \
        (x)-&gt;prev-&gt;next = x;                                     \
        (x)-&gt;next = h;                                           \
        (h)-&gt;prev = x

    获得数据时，使用ngx_queue_data()宏。其中type是数据节点类型，link是数据节点中ngx_queue_t的域名字。
    #define ngx_queue_data(q, type, link)                        \
        (type *) ((u_char *) q - offsetof(type, link))
</code></pre>
]]></content>
  </entry>
  
</feed>
