提到 Nginx,大家首先会想到它的高性能、事件框架、模块化、upstream 等耳熟能详的技术实现。这些确实也是 Nginx 的核心,但作为一个优秀的开源项目,Nginx 可以供我们借鉴的远不止这些,例如本文的话题:如何控制某个特性是否打开?

我们知道,在 Linux 下用源码安装方式编译安装一个软件时,标准情况下是有一个 configure 的动作,这个动作即是在编译前对环境进行检查,服务于后面的编译和安装,Nginx当然也不例外。

Nginx 的 configure 文件是一个入口,在里面调用了很多其他脚本,这些脚本都位于源代码的 auto 目录下。本文重点涉及其中两个脚本:auto/haveauto/define

它们的内容极其简单,分别如下:

  1. auto/have:
# Copyright (C) Igor Sysoev
# Copyright (C) Nginx, Inc.  


cat << END >> $NGX_AUTO_CONFIG_H  
   
#ifndef $have  
#define $have 1  
#endif  

END
  1. auto/define
# Copyright (C) Igor Sysoev  
# Copyright (C) Nginx, Inc.  


cat << END >> $NGX_AUTO_CONFIG_H  
   
#ifndef $have  
#define $have $value  
#endif  

END  

可见,两个脚本只有一处不同,是将 have 变量定义成 value 变量还是将其定义为 1,所以后面仅仅以 have 脚本为例进行说明。

have 脚本的用法:

have=XXXX  . auto/have

就会在 $NGX_AUTO_CONFIG_H 所代表的文件里(默认为 objs/ngx_auto_config.h)追加如下内容:

#ifndef XXXX  
#define XXXX 1  
#endif  

这样,就可以用 XXXX 宏是否定义来控制编译的过程。

下面以 ngx_memalign 这个 Nginx 内部接口为例来进行详细的说明。

ngx_memalign 是 Nginx 里最基本的接口之一,经常会被调用。从接口的名字就可以看出,这个接口是用来处理内存分配和对齐的。其定义在 ngx_alloc.h 中:

#if (NGX_HAVE_POSIX_MEMALIGN || NGX_HAVE_MEMALIGN)  
void *ngx_memalign(size_t alignment, size_t size, ngx_log_t *log); 
#else 
#define ngx_memalign(alignment, size, log) ngx_alloc(size, log) 
#endif  

上面代码的意图是,如果定义了 NGX_HAVE_POSIX_MEMALIGN 宏或 NGX_HAVE_MEMALIGN 宏,则声明函数 ngx_memalign,否则,简单的对 ngx_alloc 进行一下封装(ngx_alloc 是对 malloc 的简单封装)。

我们在来看函数 ngx_memalignngx_alloc.c 中的定义:

#if (NGX_HAVE_POSIX_MEMALIGN)  

void *  
ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)  
{  
    void *p;  
    int err;  

    err = posix_memalign(&p, alignment, size);  
      
    if (err) {  
        ngx_log_error(NGX_LOG_EMERG, log, err,  
                      "posix_memalign(%uz, %uz) failed", alignment, size);  
        p = NULL;  
    }  
      
    ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,  
                   "posix_memalign: %p:%uz @%uz", p, size, alignment);  
      
    return p;  
}  

#elif (NGX_HAVE_MEMALIGN)  

void *  
ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)  
{  
    void *p;  

    p = memalign(alignment, size);  
    if (p == NULL) {  
        ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,  
                      "memalign(%uz, %uz) failed", alignment, size);  
    }  
      
    ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,  
                   "memalign: %p:%uz @%uz", p, size, alignment);  
      
    return p;  
}  

#endif  

上面代码的意图为,如果定义了 NGX_HAVE_POSIX_MEMALIGN,那么 ngx_memalign 就是对 posix_memalign 的简单封装,否则,如果定义了 NGX_HAVE_MEMALIGN,则 ngx_memalign 是对 memalign 的简单封装。

那么 NGX_HAVE_POSIX_MEMALIGNNGX_HAVE_MEMALIGN 又是什么时候被定义呢?

过程如下:

auto/unix 脚本,列出需要检查的接口,这里就是 memalignposix_memalign 了,交给 auto/feature 脚本来逐一处理,auto/feature 脚本会对针对每一个带检查的接口,生成一个最基本的临时 c 源文件,该源文件会调用该接口。feature 脚本然后对该源文件进行编译并判断最终生成的目标文件的可执行性。用这种动态检测的方法来判断该接口在当前系统中是否支持。

如果 feature 脚本验证出 posix_memalignmemalign 接口在当前系统中都可用后,则会逐一调用 have 脚本:

have=$ngx_have_feature . auto/have  

这里,变量 ngx_have_feature 即是 NGX_HAVE_POSIX_MEMALIGNNGX_HAVE_MEMALIGN

最终在 configure、auto/unix、auto/feature、auto/have 各个脚本的通力合作下,在 objs/ngx_auto_config.h 中就有了对 NGX_HAVE_POSIX_MEMALIGNNGX_HAVE_MEMALIGN 的定义,进而影响到 ngx_memalign 接口的实现。

让我们从需求出发,将接口 ngx_memalign 的需求描述一遍:

  1. 如果系统支持 posix_memalign,则 ngx_memalign 是对 posix_memalign 的简单封装。
  2. 如果系统不支持 posix_memalign,但支持 memalign,则 ngx_memalign 是对 memalign 的简单封装。
  3. 如果系统竟然对 posix_memalignmemalign 都不支持,则 ngx_memalign 是对 malloc 的简单封装。

实现需求很简单,但如何实现的优雅,则是另一回事。Nginx 向我们展示了,一个在 C 上尽力把服务器性能做到极致的项目,是如何在脚本上也尽最大努力做得漂亮。