Nginx源码分析(1)

Nginx是一个高性能的静态HTTP服务器和反向代理服务器,Nginx本身不支持现在流行的 JSP、ASP、PHP等动态页面,但是它可以通过反向代理将请求发送到后端的服务器,例如 Tomcat、Apache等来完成动态页面的请求处理。

结合Nginx开发从入门到精通一书和Nginx源码学习服务器的高并发处理。

服务器的架构大同小异,而性能的差异主要来自对数据的处理方式上,也即进程模型和事件模型上。一个客户端请求的数据如何进行接收、存储、解析、返回是服务器做的最主要的工作。


##源码文件结构 源码位于src目录下,分为七个部分。

├── core   #core module代码,nginx服务入口   
├── event  #事件处理逻辑的封装
├── http   #作为web/http/proxy server运行时核心模块
├── mail   #作为pop3/imap/smtp proxy server核心模块
├── misc   #一些utils,定义test个profiler外围模块逻辑
├── os     #对各个平台抽象逻辑的封装
└── stream #  

最基础的数据结构

C++之所以不被用来写网站是因为,网站的核心是处理字符串。而C++的字符串处理实在比较弱,还要不断惦记GC和内存泄漏。

而Nginx首先封装了一下字符串的数据结构和API。定义位于nginx/src/core/nginx_string.h中。

typedef struct {
    size_t      len;
    u_char     *data;
} ngx_str_t;

Nginx中一个字符串被表示为指针data指示首地址+len指示长度的方式,这种方式唯一定位一个字符串。和标准的glibc API\0的方式标识字符串结束不同,它有很多好处。

  • 通过长度表示字符串长度,减少计算字符串长度的步骤
  • 可以重复引用一段字符串内存,减少不必要的内存分配

这样的表示是不是似曾相识?在Redis的sds中有类似的处理。

struct sdshdr {
    unsigned int len;
    unsigned int free;
    char buf[];
};

再看看它的API,API以宏的方式定义。

#define ngx_string(str)     { sizeof(str) - 1, (u_char *) str }

#define ngx_null_string     { 0, NULL }

#define ngx_str_set(str, text)     \
(str)->len = sizeof(text) - 1; (str)->data = (u_char *) text

#define ngx_str_null(str)   (str)->len = 0; (str)->data = NULL

第一个宏定义一个nginx的字符串,用法是

int main()
{
    ngx_str_t stringA = ngx_string("Stop");
    printf("len: %d \n", stringA.len);
    printf("address: %p \n", stringA.data);

    char* p = (char*)("HELLO WORLD");
    printf("address: %p \n", p);
    printf("string %s \n", p);
    return 0; 
}

这里直接是宏展开,对结构体进行赋值。先生成了一个临时变量"stop",然后把临时变量的长度和地址赋值给结构体。ngx_stringngx_null_string{,}格式的,而结构体只能在初始化时进行整体赋值,因而API只能用于赋值时初始化。还需要注意str必须是常量字符串,因为sizeof是以\0为结束标志的。

打印结果

len: 4 
address: 0x8048530 
address: 0x804854d 
string HELLO WORLD

内存管理池ngx_pool_t

ngx_pool_t是一个资源管理池。它提供一种机制帮助管理一系列的资源(如内存、文件等),接管这些资源的所有权,负责资源的使用和释放。类似于C++中的shared_ptr智能指针

ngx_pool_t定义在文件nginx/src/core/ngx_palloc.h中,相关的内存分配函数(如ngx_alloc)定义在nginx/src/os/unix/ngx_alloc.h中。

typedef struct ngx_pool_s   ngx_pool_t; //core.h中

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

ngx_pool_data_t指示资源池数据块的位置信息。

size_t是数据块的最大值

整个内存管理如图所示: 图片

再看一下相关的API函数。

  • ngx_alloc使用malloc进行内存分配,同时把错误信息写到log文件。
void * 
ngx_alloc(size_t size, ngx_log_t *log)
{
    void  *p;
    p = malloc(size);
    if (p == NULL) {
    ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
    "malloc(%uz) failed", size);
    }
    ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, 
    "malloc: %p:%uz", p, size);
    return p;
}
  • ngx_create_pool使用posix_memalign()申请大小为NGX_POOL_ALIGNMENT字节对其的内存。
ngx_pool_t *
ngx_create_pool(size_t size, ngx_log_t *log)
{
    ngx_pool_t  *p;
    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;
}

p->d.last位移ngx_pool_t大小指向数据区段未使用部分的开始。

p->d.last指向数据区段未使用部分的结束。

可见分配的size大小的内存一部分要分给ngx_pool_t结构。

p->max表示分配的最大内存为NGX_MAX_ALLOC_FROM_POOL

#define NGX_MAX_ALLOC_FROM_POOL  (ngx_pagesize -1)
ngx_pagesize =  getpagesize();

最大限制为一个分页大小,在x86上其返回值应为4096Bytes = 4KB。


##参考

http://tengine.taobao.org/book/chapter_02.html

https://github.com/antirez/redis/blob/unstable/src/sds.h

https://code.google.com/p/nginxsrp/wiki/NginxCodeReview

http://www.evanmiller.org/nginx-modules-guide.html

http://www.ibm.com/developerworks/cn/web/wa-lo-nginx/



Previous     Next
tuzhi /
Published under (CC) BY-NC-SA in categories 服务器  tagged with