Nginx源码分析

Published: by Creative Commons Licence

  • Tags:

说明

本文所分析的源码版本是stable-1.18

原理架构

Nginx进程

Nginx启动时,会有一个master进程,以及多个worker进程,一般情况下,worker进程的数目与服务器的核心数相同。master进程负责监控管理worker进程,而worker进程则通过共享内存、原子操作等一些进程间通信机制来实现负载均衡操作。

多进程的方式,当任意一个worker进程由于错误而导致coredump的时候,master进程会立刻启动新的worker进程继续服务。由于Nginx的每个worker进程都可以同时处理多个请求,请求的上限仅受到内存的限制。进程之间几乎没有并发锁控制,因此进程几乎不会陷入睡眠,这时候进程数与CPU核数一样的时候,会得到最大的性能。

启动

nginx的启动入口在nginx.c文件中:

//nginx入口
int ngx_cdecl
main(int argc, char *const *argv)
{
  ...
  return 0;
}

启动时首先会处理命令行参数。

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

处理参数的代码还是挺值得学习的:

//处理命令行参数
static ngx_int_t
ngx_get_options(int argc, char *const *argv)
{
    u_char     *p;
    ngx_int_t   i;

    for (i = 1; i < argc; i++) {

        p = (u_char *) argv[i];

        //参数以-开始
        if (*p++ != '-') {
            ngx_log_stderr(0, "invalid option: \"%s\"", argv[i]);
            return NGX_ERROR;
        }

        //处理到文件结尾
        while (*p) {
            
            switch (*p++) {
            
            case '?':
            case 'h':
                //输出帮助信息
                ngx_show_version = 1;
                ngx_show_help = 1;
                break;

            case 'v':
                //输出版本信息
                ngx_show_version = 1;
                break;

            case 'V':
                //输出版本和控制信息
                ngx_show_version = 1;
                ngx_show_configure = 1;
                break;

            case 't':
                //测试
                ngx_test_config = 1;
                break;

            case 'T':
                ngx_test_config = 1;
                ngx_dump_config = 1;
                break;

            case 'q':
                //静默模式
                ngx_quiet_mode = 1;
                break;

            case 'p':
                if (*p) {
                    ngx_prefix = p;
                    goto next;
                }

                if (argv[++i]) {
                    //设置根路径
                    ngx_prefix = (u_char *) argv[i];
                    goto next;
                }

                ngx_log_stderr(0, "option \"-p\" requires directory name");
                return NGX_ERROR;

            case 'c':
                if (*p) {
                    ngx_conf_file = p;
                    goto next;
                }

                if (argv[++i]) {
                    ngx_conf_file = (u_char *) argv[i];
                    goto next;
                }

                ngx_log_stderr(0, "option \"-c\" requires file name");
                return NGX_ERROR;

            case 'g':
                if (*p) {
                    ngx_conf_params = p;
                    goto next;
                }

                if (argv[++i]) {
                    ngx_conf_params = (u_char *) argv[i];
                    goto next;
                }

                ngx_log_stderr(0, "option \"-g\" requires parameter");
                return NGX_ERROR;

            case 's':
                if (*p) {
                    ngx_signal = (char *) p;

                } else if (argv[++i]) {
                    ngx_signal = argv[i];

                } else {
                    ngx_log_stderr(0, "option \"-s\" requires parameter");
                    return NGX_ERROR;
                }

                if (ngx_strcmp(ngx_signal, "stop") == 0
                    || ngx_strcmp(ngx_signal, "quit") == 0
                    || ngx_strcmp(ngx_signal, "reopen") == 0
                    || ngx_strcmp(ngx_signal, "reload") == 0)
                {
                    ngx_process = NGX_PROCESS_SIGNALLER;
                    goto next;
                }

                ngx_log_stderr(0, "invalid option: \"-s %s\"", ngx_signal);
                return NGX_ERROR;

            default:
                ngx_log_stderr(0, "invalid option: \"%c\"", *(p - 1));
                return NGX_ERROR;
            }
        }

    next:

        continue;
    }

    return NGX_OK;
}

之后如果发现命令行参数中要求输出帮助信息,则打印到stderr中。

if (ngx_show_version) {
      //输出帮助信息
      ngx_show_version_info();

      if (!ngx_test_config) {
        return 0;
        }
    }

之后nginx会初始化时间信息。

    //设置时间信息
    ngx_time_init();

ngx_time_init的代码如下

void
ngx_time_init(void)
{
    //计算需要的最大空间
    ngx_cached_err_log_time.len = sizeof("1970/09/28 12:00:00") - 1;
    ngx_cached_http_time.len = sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1;
    ngx_cached_http_log_time.len = sizeof("28/Sep/1970:12:00:00 +0600") - 1;
    ngx_cached_http_log_iso8601.len = sizeof("1970-09-28T12:00:00+06:00") - 1;
    ngx_cached_syslog_time.len = sizeof("Sep 28 12:00:00") - 1;

    ngx_cached_time = &cached_time[0];

    ngx_time_update();
}

ngx_time_update的代码如下,具体就是会更新最新的时间。

void
ngx_time_update(void)
{
    u_char          *p0, *p1, *p2, *p3, *p4;
    ngx_tm_t         tm, gmt;
    time_t           sec;
    ngx_uint_t       msec;
    ngx_time_t      *tp;
    struct timeval   tv;

    //对时间锁进行上锁
    if (!ngx_trylock(&ngx_time_lock)) {
        return;
    }

    //tv存储日期
    ngx_gettimeofday(&tv);

    //计算秒
    sec = tv.tv_sec;
    //计算毫秒
    msec = tv.tv_usec / 1000;

    //获取EPOC到现在的秒数
    ngx_current_msec = ngx_monotonic_time(sec, msec);

    tp = &cached_time[slot];

    if (tp->sec == sec) {
        //只有毫秒不同
        tp->msec = msec;
        ngx_unlock(&ngx_time_lock);
        return;
    }

    //如果秒改变了,则需要重新设置格式化时间
    //SLOT进行旋转
    if (slot == NGX_TIME_SLOTS - 1) {
        slot = 0;
    } else {
        slot++;
    }

    tp = &cached_time[slot];

    tp->sec = sec;
    tp->msec = msec;

    ngx_gmtime(sec, &gmt);


    p0 = &cached_http_time[slot][0];

    (void) ngx_sprintf(p0, "%s, %02d %s %4d %02d:%02d:%02d GMT",
                       week[gmt.ngx_tm_wday], gmt.ngx_tm_mday,
                       months[gmt.ngx_tm_mon - 1], gmt.ngx_tm_year,
                       gmt.ngx_tm_hour, gmt.ngx_tm_min, gmt.ngx_tm_sec);

#if (NGX_HAVE_GETTIMEZONE)

    tp->gmtoff = ngx_gettimezone();
    ngx_gmtime(sec + tp->gmtoff * 60, &tm);

#elif (NGX_HAVE_GMTOFF)

    ngx_localtime(sec, &tm);
    cached_gmtoff = (ngx_int_t) (tm.ngx_tm_gmtoff / 60);
    tp->gmtoff = cached_gmtoff;

#else

    ngx_localtime(sec, &tm);
    cached_gmtoff = ngx_timezone(tm.ngx_tm_isdst);
    tp->gmtoff = cached_gmtoff;

#endif


    p1 = &cached_err_log_time[slot][0];

    (void) ngx_sprintf(p1, "%4d/%02d/%02d %02d:%02d:%02d",
                       tm.ngx_tm_year, tm.ngx_tm_mon,
                       tm.ngx_tm_mday, tm.ngx_tm_hour,
                       tm.ngx_tm_min, tm.ngx_tm_sec);


    p2 = &cached_http_log_time[slot][0];

    (void) ngx_sprintf(p2, "%02d/%s/%d:%02d:%02d:%02d %c%02i%02i",
                       tm.ngx_tm_mday, months[tm.ngx_tm_mon - 1],
                       tm.ngx_tm_year, tm.ngx_tm_hour,
                       tm.ngx_tm_min, tm.ngx_tm_sec,
                       tp->gmtoff < 0 ? '-' : '+',
                       ngx_abs(tp->gmtoff / 60), ngx_abs(tp->gmtoff % 60));

    p3 = &cached_http_log_iso8601[slot][0];

    (void) ngx_sprintf(p3, "%4d-%02d-%02dT%02d:%02d:%02d%c%02i:%02i",
                       tm.ngx_tm_year, tm.ngx_tm_mon,
                       tm.ngx_tm_mday, tm.ngx_tm_hour,
                       tm.ngx_tm_min, tm.ngx_tm_sec,
                       tp->gmtoff < 0 ? '-' : '+',
                       ngx_abs(tp->gmtoff / 60), ngx_abs(tp->gmtoff % 60));

    p4 = &cached_syslog_time[slot][0];

    (void) ngx_sprintf(p4, "%s %2d %02d:%02d:%02d",
                       months[tm.ngx_tm_mon - 1], tm.ngx_tm_mday,
                       tm.ngx_tm_hour, tm.ngx_tm_min, tm.ngx_tm_sec);

    //内存屏障
    ngx_memory_barrier();

    //赋值
    ngx_cached_time = tp;
    ngx_cached_http_time.data = p0;
    ngx_cached_err_log_time.data = p1;
    ngx_cached_http_log_time.data = p2;
    ngx_cached_http_log_iso8601.data = p3;
    ngx_cached_syslog_time.data = p4;

    //解除锁
    ngx_unlock(&ngx_time_lock);
}

之后会初始化日志句柄。

    log = ngx_log_init(ngx_prefix);
    if (log == NULL) {
        return 1;
    }

ngx_log_init的代码如下:

//初始化日志
ngx_log_t *
ngx_log_init(u_char *prefix)
{
    u_char  *p, *name;
    size_t   nlen, plen;

    //设置默认信息
    ngx_log.file = &ngx_log_file;
    ngx_log.log_level = NGX_LOG_NOTICE;

    name = (u_char *) NGX_ERROR_LOG_PATH;

    /*
     * we use ngx_strlen() here since BCC warns about
     * condition is always false and unreachable code
     */

    nlen = ngx_strlen(name);

    
    if (nlen == 0) {
        //如果不设置日志文件,则将日志重定向到stderr
        ngx_log_file.fd = ngx_stderr;
        return &ngx_log;
    }

    p = NULL;

#if (NGX_WIN32)
    if (name[1] != ':') {
#else
    if (name[0] != '/') {
#endif
        //如果不是绝对路径
        if (prefix) {
            //前缀长度
            plen = ngx_strlen(prefix);

        } else {
#ifdef NGX_PREFIX
            prefix = (u_char *) NGX_PREFIX;
            plen = ngx_strlen(prefix);
#else
            plen = 0;
#endif
        }

        if (plen) {
            //名字前面拼接prefix
            name = malloc(plen + nlen + 2);
            if (name == NULL) {
                return NULL;
            }

            p = ngx_cpymem(name, prefix, plen);

            //如果prefix没有以'/'结束,则加上
            if (!ngx_path_separator(*(p - 1))) {
                *p++ = '/';
            }

            //路径合并
            ngx_cpystrn(p, (u_char *) NGX_ERROR_LOG_PATH, nlen + 1);

            //重置p
            p = name;
        }
    }

    //打开日志文件,保留句柄
    ngx_log_file.fd = ngx_open_file(name, NGX_FILE_APPEND,
                                    NGX_FILE_CREATE_OR_OPEN,
                                    NGX_FILE_DEFAULT_ACCESS);

    //打开日志文件失败
    if (ngx_log_file.fd == NGX_INVALID_FILE) {
        ngx_log_stderr(ngx_errno,
                       "[alert] could not open error log file: "
                       ngx_open_file_n " \"%s\" failed", name);
#if (NGX_WIN32)
        ngx_event_log(ngx_errno,
                       "could not open error log file: "
                       ngx_open_file_n " \"%s\" failed", name);
#endif

        //重定向回stderr
        ngx_log_file.fd = ngx_stderr;
    }

    //如果中间重新分配的内存,则释放
    if (p) {
        ngx_free(p);
    }

    return &ngx_log;
}

之后会初始化一下OPENSSL。

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

接下来会初始化init_cycle对象。

    /*
     * init_cycle->log is required for signal handlers and
     * ngx_process_options()
     */

    //将init_cycle清零
    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;
    }

    //记录当前main函数参数
    if (ngx_save_argv(&init_cycle, argc, argv) != NGX_OK) {
        return 1;
    }
    
    //将参数放入init_cycle对象中
    if (ngx_process_options(&init_cycle) != NGX_OK) {
        return 1;
    }

接下来会nginx会获取操作系统信息。

    //设置操作系统信息
    if (ngx_os_init(log) != NGX_OK) {
        return 1;
    }

其在linux下实现如下:

//初始化操作系统设置
ngx_int_t
ngx_os_init(ngx_log_t *log)
{
    ngx_time_t  *tp;
    ngx_uint_t   n;
#if (NGX_HAVE_LEVEL1_DCACHE_LINESIZE)
    long         size;
#endif

#if (NGX_HAVE_OS_SPECIFIC_INIT)
    if (ngx_os_specific_init(log) != NGX_OK) {
        return NGX_ERROR;
    }
#endif

    //设置进程的标题
    if (ngx_init_setproctitle(log) != NGX_OK) {
        return NGX_ERROR;
    }

    //页大小
    ngx_pagesize = getpagesize();
    //缓存行大小
    ngx_cacheline_size = NGX_CPU_CACHE_LINE;

    for (n = ngx_pagesize; n >>= 1; ngx_pagesize_shift++) { /* void */ }

#if (NGX_HAVE_SC_NPROCESSORS_ONLN)
    //设置CPU数目
    if (ngx_ncpu == 0) {
        ngx_ncpu = sysconf(_SC_NPROCESSORS_ONLN);
    }
#endif

    //CPU至少有一个CPU
    if (ngx_ncpu < 1) {
        ngx_ncpu = 1;
    }

#if (NGX_HAVE_LEVEL1_DCACHE_LINESIZE)
    //一级缓存
    size = sysconf(_SC_LEVEL1_DCACHE_LINESIZE);
    if (size > 0) {
        ngx_cacheline_size = size;
    }
#endif

    //获取CPU信息
    ngx_cpuinfo();

    //获取最大打开文件描述符数量
    if (getrlimit(RLIMIT_NOFILE, &rlmt) == -1) {
        ngx_log_error(NGX_LOG_ALERT, log, errno,
                      "getrlimit(RLIMIT_NOFILE) failed");
        return NGX_ERROR;
    }

    //可以打开的最大socket数目(Soft limit)
    ngx_max_sockets = (ngx_int_t) rlmt.rlim_cur;

#if (NGX_HAVE_INHERITED_NONBLOCK || NGX_HAVE_ACCEPT4)
    ngx_inherited_nonblocking = 1;
#else
    ngx_inherited_nonblocking = 0;
#endif

    tp = ngx_timeofday();
    //设置随机数种子
    srandom(((unsigned) ngx_pid << 16) ^ tp->sec ^ tp->msec);

    return NGX_OK;
}

之后需要初始化crc32数组。

   //初始化crc32数组
    if (ngx_crc32_table_init() != NGX_OK) {
        return 1;
    }

其主要工作就是将ngx_crc32_table_short与缓存行对齐。

//保证ngx_crc32_table_short与缓存行对齐
ngx_int_t
ngx_crc32_table_init(void)
{
    void  *p;

    //如果与缓存行对齐
    if (((uintptr_t) ngx_crc32_table_short
          & ~((uintptr_t) ngx_cacheline_size - 1))
        == (uintptr_t) ngx_crc32_table_short)
    {
        return NGX_OK;
    }

    //没对齐,额外分配缓存行长度的存储空间
    p = ngx_alloc(16 * sizeof(uint32_t) + ngx_cacheline_size, ngx_cycle->log);
    if (p == NULL) {
        return NGX_ERROR;
    }

    //做对齐
    p = ngx_align_ptr(p, ngx_cacheline_size);

    ngx_memcpy(p, ngx_crc32_table16, 16 * sizeof(uint32_t));

    ngx_crc32_table_short = p;

    return NGX_OK;
}

内存池

nginx内部实现了内存池,实现还是比较有趣的,当然也有一些问题。

内存池的定义如下:

typedef struct {
    u_char               *last; //上一次分配结束为止
    u_char               *end; //内存块结尾
    ngx_pool_t           *next; //下一个内存池
    ngx_uint_t            failed; //总共经历的分配失败次数
} ngx_pool_data_t;


//内存池对象
//内存布局,在对象结尾,就是内存池可分配的内存空间
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; //日志句柄组成的链表
};

typedef struct ngx_pool_s            ngx_pool_t;

内存池的布局是这样的,一段连续的空间,其中前sizeof(ngx_pool_s)个字节头部,用于存储内存池的基本信息。后面的所有空间都是内存池实际可以对外分配的空间。

我们先来看一下内存池是如何申请的:

//创建一个容量为size-sizeof(ngx_pool_t)的内存池
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t  *p;

    //分配大小为size的指针
    p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
    if (p == NULL) {
        return NULL;
    }

    //记录内存池的开始位置
    p->d.last = (u_char *) p + sizeof(ngx_pool_t);
    //记录内存池的可用位置
    p->d.end = (u_char *) p + size;
    p->d.next = NULL;
    p->d.failed = 0;

    //实际容量
    size = size - sizeof(ngx_pool_t);
    //允许从内存池中分配的上限
    p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;

    p->current = p;
    p->chain = NULL;
    p->large = NULL;
    p->cleanup = NULL;
    p->log = log;

    return p;
}

我们看一下分配内存的实现。

//自适应分配内存
void *
ngx_palloc(ngx_pool_t *pool, size_t size)
{
#if !(NGX_DEBUG_PALLOC)
    if (size <= pool->max) {
        return ngx_palloc_small(pool, size, 1);
    }
#endif

    return ngx_palloc_large(pool, size);
}

这里分配内存的时候,会根据size是否超过阈值来判断具体实现。先看一下对于较小的内存块的分配的实现。

//从pool中分配大小为size的内存,align表示是否做内存对齐
static ngx_inline void *
ngx_palloc_small(ngx_pool_t *pool, size_t size, ngx_uint_t align)
{
    u_char      *m;
    ngx_pool_t  *p;

    //从第一个可用结点开始
    p = pool->current;

    do {
        m = p->d.last;

        if (align) {
            //内存对齐
            m = ngx_align_ptr(m, NGX_ALIGNMENT);
        }

        //如果内存够用,就直接分配
        if ((size_t) (p->d.end - m) >= size) {
            //记录上一次分配的结束为止
            p->d.last = m + size;

            return m;
        }

        //这里分配失败,最好还是将fail+1,否则平均分配时间复杂度可能劣化到O(n)
        //内存不足,就用下一个
        p = p->d.next;

    } while (p);

    //都失败了,就重新分配一个内存池
    return ngx_palloc_block(pool, size);
}

可以看到,分配小块内存的实现实际上就是遍历所有的可用结点之后逐个尝试分配内存。如果都无法分配出要求的内存的话,就会在内存池链表的尾部再创建一个新的链表。

//分配一个新的内存池,并从内存池中分配size内存
static void *
ngx_palloc_block(ngx_pool_t *pool, size_t size)
{
    u_char      *m;
    size_t       psize;
    ngx_pool_t  *p, *new;

    //计算pool的大小
    psize = (size_t) (pool->d.end - (u_char *) pool);

    //分配一个和pool一样大的内存池
    m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
    if (m == NULL) {
        return NULL;
    }

    //设置内存池的属性
    new = (ngx_pool_t *) m;
    new->d.end = m + psize;
    new->d.next = NULL;
    new->d.failed = 0;

    m += sizeof(ngx_pool_data_t);
    m = ngx_align_ptr(m, NGX_ALIGNMENT);
    new->d.last = m + size;

    //这里用current来优化了链表的访问速度,之后的访问都会跳过经过4次以上分配失败的结点
    for (p = pool->current; p->d.next; p = p->d.next) {
        if (p->d.failed++ > 4) {
            pool->current = p->d.next;
        }
    }

    //把new挂在内存池的尾部
    p->d.next = new;

    return m;
}

新分配的内存池和原本的内存池拥有相同的大小。

接下来我们来看一下对于较大内存块是如何分配的:


//分配大块内存
static void *
ngx_palloc_large(ngx_pool_t *pool, size_t size)
{
    void              *p;
    ngx_uint_t         n;
    ngx_pool_large_t  *large;

    //分配一块大内存
    p = ngx_alloc(size, pool->log);
    if (p == NULL) {
        return NULL;
    }

    n = 0;

    for (large = pool->large; large; large = large->next) {
        if (large->alloc == NULL) {
            //如果pool的large对象还未分配,则指向自己
            large->alloc = p;
            return p;
        }

        //只考虑前4个,如果都已经挂载了大内存,就跳出
        if (n++ > 3) {
            break;
        }
    }

    //从内存池中分配一个large对象
    large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
    if (large == NULL) {
        //内存不足
        ngx_free(p);
        return NULL;
    }


    //将新分配的大内存块插入到链表头部
    large->alloc = p;
    large->next = pool->large;
    pool->large = large;

    return p;
}

分配大块内存的时候,要求的内存块实际上是通过标准库进行分配的。但是我们需要记录分配得到的内存,保证回收的时候能将其销毁。这里是通过一个专门的对象进行记录的:

typedef struct ngx_pool_large_s  ngx_pool_large_t;

//大内存块,用链表组织
struct ngx_pool_large_s {
    ngx_pool_large_t     *next;
    void                 *alloc;
};

大块的内存以链表的形式进行组织。在记录的时候会先检查前面几个链表结点是否没有引用任意内存块,如果是,就直接记在里面,否则就分配一个新的ngx_pool_large_s对象,并放在内存块链表头。

这里还需要注意就是ngx_pool_large_s对象是通过自己所在的内存池进行分配的,即它是通过ngx_palloc_small这个方法分配得到的,这也意味着我们不需要也不能通过标准库的free方法来释放这些内存。

还有一个比较简单的分配后清零的帮助方法。

//从内存池分配size大小空间,并清零
void *
ngx_pcalloc(ngx_pool_t *pool, size_t size)
{
    void *p;

    p = ngx_palloc(pool, size);
    if (p) {
        ngx_memzero(p, size);
    }

    return p;
}

内存池可以增加自定义的回收时的清理函数。

//新增一个清理事件,并为清理事件的data分配size大小的空间
ngx_pool_cleanup_t *
ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
{
    ngx_pool_cleanup_t  *c;

    c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
    if (c == NULL) {
        return NULL;
    }

    if (size) {
        c->data = ngx_palloc(p, size);
        if (c->data == NULL) {
            return NULL;
        }

    } else {
        c->data = NULL;
    }

    c->handler = NULL;
    c->next = p->cleanup;

    p->cleanup = c;

    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c);

    return c;
}

在注册清理函数的同时,你可以要求分配某个固定大小的内存块作为方法参数。这个内存块之后会作为回调参数传入。可以发现这个内存块也是在内存池中分配的,因此在销毁内存池的时候,需要先执行回调函数,再真正释放内存池管理的内存块才行。

默认的清理函数有关闭文件和删除文件两种。

//关闭文件
void
ngx_pool_cleanup_file(void *data)
{
    ngx_pool_cleanup_file_t  *c = data;

    ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d",
                   c->fd);

    if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
                      ngx_close_file_n " \"%s\" failed", c->name);
    }
}

//删除文件
void
ngx_pool_delete_file(void *data)
{
    ngx_pool_cleanup_file_t  *c = data;

    ngx_err_t  err;

    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, c->log, 0, "file cleanup: fd:%d %s",
                   c->fd, c->name);

    if (ngx_delete_file(c->name) == NGX_FILE_ERROR) {
        err = ngx_errno;

        if (err != NGX_ENOENT) {
            ngx_log_error(NGX_LOG_CRIT, c->log, err,
                          ngx_delete_file_n " \"%s\" failed", c->name);
        }
    }

    if (ngx_close_file(c->fd) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
                      ngx_close_file_n " \"%s\" failed", c->name);
    }
}

大块内存由于比较昂贵,因此nginx中提供了专门回收大块内存的方法。

//释放大块内存p
ngx_int_t
ngx_pfree(ngx_pool_t *pool, void *p)
{
    ngx_pool_large_t  *l;

    for (l = pool->large; l; l = l->next) {
        if (p == l->alloc) {
            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                           "free: %p", l->alloc);
            //释放
            ngx_free(l->alloc);
            l->alloc = NULL;

            return NGX_OK;
        }
    }

    return NGX_DECLINED;
}

下面我们看一下如何重置内存池:

//重置内存池
void
ngx_reset_pool(ngx_pool_t *pool)
{
    ngx_pool_t        *p;
    ngx_pool_large_t  *l;

    for (l = pool->large; l; l = l->next) {
        //把大内存对象释放掉
        if (l->alloc) {
            ngx_free(l->alloc);
        }
    }

    for (p = pool; p; p = p->d.next) {
        //将last重置到可用空间头,失败次数重置
        p->d.last = (u_char *) p + sizeof(ngx_pool_t);
        p->d.failed = 0;
    }

    pool->current = pool;
    pool->chain = NULL;
    pool->large = NULL;
}

重置内存池的时候并不会执行清理函数,只有在下面销毁的时候才会触发。

//销毁内存池
void
ngx_destroy_pool(ngx_pool_t *pool)
{
    ngx_pool_t          *p, *n;
    ngx_pool_large_t    *l;
    ngx_pool_cleanup_t  *c;

    for (c = pool->cleanup; c; c = c->next) {
        if (c->handler) {
            ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                           "run cleanup: %p", c);
            //销毁时的回调
            c->handler(c->data);
        }
    }

#if (NGX_DEBUG)

    /*
     * we could allocate the pool->log from this pool
     * so we cannot use this log while free()ing the pool
     */

    for (l = pool->large; l; l = l->next) {
        ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0, "free: %p", l->alloc);
    }

    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
        ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
                       "free: %p, unused: %uz", p, p->d.end - p->d.last);

        if (n == NULL) {
            break;
        }
    }

#endif

    //释放大内存块(这里large对象不需要释放,因为large是从内存池中分配的,后面在回收内存池的时候就会被自动释放了)
    for (l = pool->large; l; l = l->next) {
        if (l->alloc) {
            ngx_free(l->alloc);
        }
    }

    //释放所有内存池
    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
        ngx_free(p);

        if (n == NULL) {
            break;
        }
    }
}

动态数组

Nginx实现了动态数组。

动态数组的定义如下:

//数组类型
typedef struct {
    void        *elts; //数组开始地址
    ngx_uint_t   nelts; //已经用掉的大小
    size_t       size; //每个元素的大小
    ngx_uint_t   nalloc; //实际分配的数组大小
    ngx_pool_t  *pool; //所属内存池
} ngx_array_t;

创建一个动态数组:

//创建一个数组,大小为n,每个元素大小为size
ngx_array_t *
ngx_array_create(ngx_pool_t *p, ngx_uint_t n, size_t size)
{
    ngx_array_t *a;

    a = ngx_palloc(p, sizeof(ngx_array_t));
    if (a == NULL) {
        return NULL;
    }

    if (ngx_array_init(a, p, n, size) != NGX_OK) {
        return NULL;
    }

    return a;
}

//创建一个数组,大小为n,每个元素大小为size
static ngx_inline ngx_int_t
ngx_array_init(ngx_array_t *array, ngx_pool_t *pool, ngx_uint_t n, size_t size)
{
    /*
     * set "array->nelts" before "array->elts", otherwise MSVC thinks
     * that "array->nelts" may be used without having been initialized
     */

    array->nelts = 0;
    array->size = size;
    array->nalloc = n;
    array->pool = pool;

    array->elts = ngx_palloc(pool, n * size);
    if (array->elts == NULL) {
        return NGX_ERROR;
    }

    return NGX_OK;
}

向数组尾部插入元素,如果空间不足,会选择倍增原来的空间,因此插入操作的摊还时间复杂度为$O(1)$。

//加入一个新的占位符,并返回地址
void *
ngx_array_push(ngx_array_t *a)
{
    void        *elt, *new;
    size_t       size;
    ngx_pool_t  *p;

    if (a->nelts == a->nalloc) {
        //数组满了,需要分配额外的空间
        /* the array is full */

        //size为当前数组占用的总字节
        size = a->size * a->nalloc;

        p = a->pool;

        if ((u_char *) a->elts + size == p->d.last
            && p->d.last + a->size <= p->d.end)
        {
            //如果数组尾部与内存池头部相同,就可以直接切出更多的空间,速度更快
            /*
             * the array allocation is the last in the pool
             * and there is space for new allocation
             */

            p->d.last += a->size;
            a->nalloc++;

        } else {
            /* allocate a new array */
            //分配新的空间
            new = ngx_palloc(p, 2 * size);
            if (new == NULL) {
                return NULL;
            }

            ngx_memcpy(new, a->elts, size);
            a->elts = new;
            a->nalloc *= 2;
        }
    }

    elt = (u_char *) a->elts + a->size * a->nelts;
    a->nelts++;

    return elt;
}

当然我们也可以一次性加入多个占位符,与上面的方法没太大区别。

void *
ngx_array_push_n(ngx_array_t *a, ngx_uint_t n)
{
    void        *elt, *new;
    size_t       size;
    ngx_uint_t   nalloc;
    ngx_pool_t  *p;

    size = n * a->size;

    if (a->nelts + n > a->nalloc) {

        /* the array is full */

        p = a->pool;

        if ((u_char *) a->elts + a->size * a->nalloc == p->d.last
            && p->d.last + size <= p->d.end)
        {
            /*
             * the array allocation is the last in the pool
             * and there is space for new allocation
             */

            p->d.last += size;
            a->nalloc += n;

        } else {
            /* allocate a new array */

            nalloc = 2 * ((n >= a->nalloc) ? n : a->nalloc);

            new = ngx_palloc(p, nalloc * a->size);
            if (new == NULL) {
                return NULL;
            }

            ngx_memcpy(new, a->elts, a->nelts * a->size);
            a->elts = new;
            a->nalloc = nalloc;
        }
    }

    elt = (u_char *) a->elts + a->size * a->nelts;
    a->nelts += n;

    return elt;
}

最后是销毁数组的代码。

//销毁数组
void
ngx_array_destroy(ngx_array_t *a)
{
    ngx_pool_t  *p;

    p = a->pool;

    //如果恰好是内存池已分配内存的尾部,调整内存池的尾部指针就可以实现释放了
    if ((u_char *) a->elts + a->size * a->nalloc == p->d.last) {
        p->d.last -= a->size * a->nalloc;
    }

    //数组也是相同的逻辑,但是由于数组对象分配时间早于内部的数组块,因此需要较晚释放
    if ((u_char *) a + sizeof(ngx_array_t) == p->d.last) {
        p->d.last = (u_char *) a;
    }
}