nginx源码分析

来源:互联网 发布:床帘 淘宝 可爱 编辑:程序博客网 时间:2024/05/01 00:14

linux下eclipse调试

1 新建"c project",工程名输入nginx,点“Use default  location”,选择代码目录,project  type 选"makefile project"下的"empty  project",编译器选"linux gcc",创建成功后,选工程属性,点“C/C++ General”,删除"Build(Incremental build)"后的"ALL"。

2 代码configure后,CFLAGS要加上"-O0“。

3 第一次要修改nginx.conf如下:

daemon off;  #关闭守护进程,使之在前台运行
master_process off;  #关闭主进程,使只有一个进程

4 eclipse不能ctrl+c,

Eclipse -- Windows->Preferences->General->Editors->Text Editors->Hyperlinking,直接去掉这个选项:Enable on demand hyperlinks style navigation。

函数分析

ngx_http_map_uri_to_path
将最终资源的路径保存在path当中去。

ngx_http_discard_request_body

        当一个模块想要主动的丢弃客户端发过的请求体body,可以调用nginx核心提供的ngx_http_discard_request_body()接口,主动丢弃的原因可能有很多种,如模块的业务逻辑压根不需要请求体 ,客户端发送的请求体是非法的等。

ngx_http_finalize_request

因为Nginx是事件驱动, 在读request body时, 第一个数据包一般发送不全, 需要异步读取余下的,当在主handler部分调用ngx_http_read_client_request_body时, 需要注册一个回调的handler, 然后主handler直接返回NGX_DONE, 告诉Nginx留下这个请求的事件注册, 继续接收数据包,经过若干次循环最终读完request body后, Nginx会去回调被注册的handler, 在此函数中产生响应, 最后调用ngx_http_finalize_request结束整个请求。

字符串函数分析

ngx_strlcasestrn  在大字符串中查找子字符串,返回在大字符串中的地址。

ngx_create_temp_buf
ngx_create_temp_buf直接从pool上创建一个缓冲区的buf,buf大小可以自定义。buf的数据结构和buf内存块都会被创建到pool内存池上。
ngx_alloc_chain_link
创建一个缓冲区的链表结构。
ngx_create_chain_of_bufs
批量创建多个缓冲区,并且用链表串起来。
ngx_chain_add_copy
将其它缓冲区链表放到已有缓冲区链表结构的尾部。
ngx_free_chain
释放缓冲区链表。
ngx_chain_update_chains
可以交还给自定义的空闲链表上。

struct ngx_buf_s {
    /*
     * pos通常是用来告诉使用者本次应该从pos这个位置开始处理内存中的数据,这样设置是因为同一个
     * ngx_buf_t可能被多次反复处理。当然,pos的含义是由使用它的模板定义的
     */
    u_char  *pos;  
    /* last通常表示有效的内容到此为止,注意,pos与last之间的内存是希望nginx处理的内容 */
    u_char  *last;                 
    /*
     * 处理文件时,file_pos与file_last的含义与处理内存时的pos与last相同,
     * file_pos表示将要处理的文件位置,file_last表示截至的文件位置。
     */
    off_t   file_pos;
    off_t   file_last;
                               
    /* 如果ngx_buf_t缓冲区用于内存,那么start指向这段内存的起始地址 */
    u_char  *start;
                               
    /* 与start成员对应,指向缓冲区内存的末尾 */
    u_char  *end;
                               
    /* 表示当前缓冲区的类型,例如由哪个模块使用就指向这个模块ngx_module_t变量的地址 */
    ngx_buf_tag_t  tag;
                               
    /* 引用的文件 */
    ngx_file_t  *file;
                               
    /*
     * 当前缓冲区的影子缓冲区,该成员很少用到。当缓冲区转发上游服务器的响应时才使用了shadow成员,
     * 这是因为nginx太节约内存了,分配一块内存并使用ngx_buf_t表示接收到的上游服务器响应后,
     * 在向下游客户端转发时可能会把这块内存存储到文件中,也可能直接向下游发送,此时nginx绝对不会
     * 重新复制一份内存用于新的目的,而是再次建立一个ngx_buf_t结构体指向原内存,这样多个ngx_buf_t
     * 结构体指向了同一份内存,它们之间的关系就通过shadow成员来引用,一般不建议使用。
     */
    ngx_buf_t   *shadow;
                               
    /* 临时内存标志位,为1时表示数据在内存中且这段内存可以修改 */
    unsigned    temporay:1;
                               
    /* 标志位,为1时表示数据在内存中且这段内存不可以修改 */
    unsigned    memory:1;
                               
    /* 标志位,为1时表示这段内存是用nmap系统调用映射过来的,不可以修改 */
    unsigned    mmap:1;
                               
    /* 标志位,为1时表示可回收 */
    unsigned    recycled:1;
                               
    /* 标志位,为1时表示这段缓冲区处理的是文件而不是内存 */
    unsigned    in_file:1;
                               
    /* 标志位,为1时表示需要执行flush操作 */
    unsigned    flush:1;
                               
    /*
     * 标志位,对于操作这块缓冲区时是否使用同步方式,需谨慎考虑,这可能会阻塞nginx进程,nginx中所有
     * 操作几乎都是异步的,这是它支持高并发的关键。有些框架代码在sync为1时可能会有阻塞的方式进行I/O
     * 操作,它的意义视使用它的nginx模块而定。
     */
    unsigned    sync:1;
                               
    /*
     * 标志位,表示是否是最后一块缓冲区,因为ngx_buf_t可以由ngx_chain_t链表串联起来,因此为1时,
     * 表示当前是最后一块待处理的缓冲区。   
     */
    unsigned    last_buf:1;
                               
    /* 标志位,表示是否是ngx_chain_t中的最后一块缓冲区,last_in_chain: 在当前的chain里面,此buf是最后一个。特别要注意的是last_in_chain的buf不一定是last_buf,但是last_buf的buf一定是last_in_chain的。这是因为数据会被以多个chain传递给某个filter模块。 */
    unsigned    last_in_chain:1;   
    /* 标志位,表示是否是最后一个影子缓冲区,与shadow域配合使用。通常不建议使用它 */
    unsigned    last_shadow:1;
                               
    /* 标志位,表示当前缓冲区是否属于临时文件 */
    unsigned    temp_file:1;
}

nginx内存池管理

Nginx的内存池实现得很精巧,代码也很简洁。总的来说,所有的内存池基本都一个宗旨:申请大块内存,避免“细水长流”。

一、创建一个内存池

        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;            //可以挂一个chain结构

ngx_pool_large_t               *large;            //分配大块内存用,即超过max的内存请求

ngx_pool_cleanup_t           *cleanup;         //挂载一些内存池释放的时候,同时释放的资源。

ngx_log_t                            *log;

};

        有了上面的两个结构,就可以创建一个内存池了,nginx用来创建一个内存池的接口是:ngx_pool_t*ngx_create_pool(size_t size, ngx_log_t *log)(位于src/core/ngx_palloc.c中);调用这个函数就可以创建一个大小为size的内存池了。这里,我用内存池的结构图来展示,就不做具体的代码分析了。

        ngx_create_pool接口函数就是分配上图这样的一大块内存,然后初始化好各个头部字段(上图中的彩色部分)。红色表示的四个字段就是来 自于上述的第一个结构,维护数据部分,由图可知:last是用户从内存池分配新内存的开始位置,end是这块内存池的结束位置,所有分配的内存都不能超过 end。蓝色表示的max字段的值等于整个数据部分的长度,用户请求的内存大于max时,就认为用户请求的是一个大内存,此时需要在紫色表示的large 字段下面单独分配;用户请求的内存不大于max的话,就是小内存申请,直接在数据部分分配,此时将会移动last指针。

二、分配小块内存(size <= max)

        上面创建好了一个可用的内存池了,也提到了小块内存的分配问题。nginx提供给用户使用的内存分配接口有:

void *ngx_palloc(ngx_pool_t *pool, size_t size);

void *ngx_pnalloc(ngx_pool_t *pool, size_t size);

void *ngx_pcalloc(ngx_pool_t *pool, size_t size);

void *ngx_pmemalign(ngx_pool_t *pool, size_t size, size_t alignment);

        ngx_palloc和ngx_pnalloc都是从内存池里分配size大小内存,至于分得的是小块内存还是大块内存,将取决于size的大小; 他们的不同之处在于,palloc取得的内存是对齐的,pnalloc则否。ngx_pcalloc是直接调用palloc分配好内存,然后进行一次0初 始化操作。ngx_pmemalign将在分配size大小的内存并按alignment对齐,然后挂到large字段下,当做大块内存处理。下面用图形展示一下分配小块内存的模型:

        上图这个内存池模型是由上3个小内存池构成的,由于第一个内存池上剩余的内存不够分配了,于是就创建了第二个新的内存池,第三个内存池是由于前面两 个内存池的剩余部分都不够分配,所以创建了第三个内存池来满足用户的需求。由图可见:所有的小内存池是由一个单向链表维护在一起的。这里还有两个字段需要关注,failed和current字段。failed表示的是当前这个内存池的剩余可用内存不能满足用户分配请求的次数,即是说:一个分配请求到来后, 在这个内存池上分配不到想要的内存,那么就failed就会增加1;这个分配请求将会递交给下一个内存池去处理,如果下一个内存池也不能满足,那么它的 failed也会加1,然后将请求继续往下传递,直到满足请求为止(如果没有现成的内存池来满足,会再创建一个新的内存池)。current字段会随着 failed的增加而发生改变,如果current指向的内存池的failed达到了4的话,current就指向下一个内存池了。猜测:4这个值应该是 作者的经验值,或者是一个统计值。

三、大块内存的分配(size > max)

        大块内存的分配请求不会直接在内存池上分配内存来满足,而是直接向操作系统申请这么一块内存(就像直接使用malloc分配内存一样),然后将这块内存挂到内存池头部的large字段下。内存池的作用在于解决小块内存池的频繁申请问题,对于这种大块内存,是可以忍受直接申请的。同样,用图形展示大块内存申请模型:

        注意每块大内存都对应有一个头部结构(next&alloc),这个头部结构是用来将所有大内存串成一个链表用的。这个头部结构不是直接向 操作系统申请的,而是当做小块内存(头部结构没几个字节)直接在内存池里申请的。这样的大块内存在使用完后,可能需要第一时间释放,节省内存空间,因此 nginx提供了接口函数:ngx_int_t ngx_pfree(ngx_pool_t*pool, void *p);此函数专门用来释放某个内存池上的某个大块内存,p就是大内存的地址。ngx_pfree只会释放大内存,不会释放其对应的头部结构,毕竟头部结 构是当做小内存在内存池里申请的;遗留下来的头部结构会作下一次申请大内存之用。

四、cleanup资源

         可以看到所有挂载在内存池上的资源将形成一个循环链表,一路走来,发现链表这种看似简单的数据结构却被频繁使用。由图可知,每个需要清理的资源都对应有一个头部结构,这个结构中有一个关键的字段handler,handler是一个函数指针,在挂载一个资源到内存池上的时候,同时也会注册一个清理资源的函数到这个handler上。即是说,内存池在清理cleanup的时候,就是调用这个handler来清理对应的资源。比如:我们可以将一个开打的 文件描述符作为资源挂载到内存池上,同时提供一个关闭文件描述的函数注册到handler上,那么内存池在释放的时候,就会调用我们提供的关闭文件函数来 处理文件描述符资源了。

五、内存的释放

        只提供给了用户申请内存的接口,却没有释放内存的接口,那么nginx是如何完成内存释放的呢?总不能一直申请,用不释放啊。针对这个问 题,nginx利用了web server应用的特殊场景来完成;一个web server总是不停的接受connection和request,所以nginx就将内存池分了不同的等级,有进程级的内存池、connection级 的内存池、request级的内存池。也就是说,创建好一个worker进程的时候,同时为这个worker进程创建一个内存池,待有新的连接到来后,就在worker进程的内存池上为该连接创建起一个内存池;连接上到来一个request后,又在连接的内存池上为request创建起一个内存池。这样,在request被处理完后,就会释放request的整个内存池,连接断开后,就会释放连接的内存池。因而,就保证了内存有分配也有释放。

        总结:通过内存的分配和释放可以看出,nginx只是将小块内存的申请聚集到一起申请,然后一起释放。避免了频繁申请小内存,降低内存碎片的产生等问题。

 

0 0
原创粉丝点击