Nginx 如何控制某个特性是否打开
提到 Nginx,大家首先会想到它的高性能、事件框架、模块化、upstream 等耳熟能详的技术实现。这些确实也是 Nginx 的核心,但作为一个优秀的开源项目,Nginx 可以供我们借鉴的远不止这些,例如本文的话题:如何控制某个特性是否打开?
我们知道,在 Linux 下用源码安装方式编译安装一个软件时,标准情况下是有一个 configure 的动作,这个动作即是在编译前对环境进行检查,服务于后面的编译和安装,Nginx当然也不例外。
Nginx 的 configure 文件是一个入口,在里面调用了很多其他脚本,这些脚本都位于源代码的 auto
目录下。本文重点涉及其中两个脚本:auto/have
和 auto/define
。
它们的内容极其简单,分别如下:
- auto/have:
# Copyright (C) Igor Sysoev
# Copyright (C) Nginx, Inc.
cat << END >> $NGX_AUTO_CONFIG_H
#ifndef $have
#define $have 1
#endif
END
- 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_memalign
在 ngx_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_MEMALIGN
和 NGX_HAVE_MEMALIGN
又是什么时候被定义呢?
过程如下:
在 auto/unix
脚本,列出需要检查的接口,这里就是 memalign
和 posix_memalign
了,交给 auto/feature
脚本来逐一处理,auto/feature
脚本会对针对每一个带检查的接口,生成一个最基本的临时 c 源文件,该源文件会调用该接口。feature
脚本然后对该源文件进行编译并判断最终生成的目标文件的可执行性。用这种动态检测的方法来判断该接口在当前系统中是否支持。
如果 feature
脚本验证出 posix_memalign
和 memalign
接口在当前系统中都可用后,则会逐一调用 have
脚本:
have=$ngx_have_feature . auto/have
这里,变量 ngx_have_feature
即是 NGX_HAVE_POSIX_MEMALIGN
和 NGX_HAVE_MEMALIGN
。
最终在 configure、auto/unix、auto/feature、auto/have 各个脚本的通力合作下,在 objs/ngx_auto_config.h
中就有了对 NGX_HAVE_POSIX_MEMALIGN
和 NGX_HAVE_MEMALIGN
的定义,进而影响到 ngx_memalign
接口的实现。
让我们从需求出发,将接口 ngx_memalign
的需求描述一遍:
- 如果系统支持
posix_memalign
,则ngx_memalign
是对posix_memalign
的简单封装。 - 如果系统不支持
posix_memalign
,但支持memalign
,则ngx_memalign
是对memalign
的简单封装。 - 如果系统竟然对
posix_memalign
和memalign
都不支持,则ngx_memalign
是对malloc
的简单封装。
实现需求很简单,但如何实现的优雅,则是另一回事。Nginx 向我们展示了,一个在 C 上尽力把服务器性能做到极致的项目,是如何在脚本上也尽最大努力做得漂亮。