资讯专栏INFORMATION COLUMN

Nginx源码:利用C语言tricky构建函数链

wind5o / 1256人阅读

摘要:请注意这里的和是全局变量,而和是模块的静态变量是模块级的全局变量,这一点很重要,后面会详细分析。当编译进一个模块的时候,就被赋值为当前模块的处理函数。所以整体看来,就像用全局变量组成的一条单向链表。

最近开始使用Nginx的第三方扩展解决实际的问题,对Nginx的扩展开发产生了一些兴趣,在阅读第三方代码时产生了一些心得和体会。本文详细分析了进行Nginx过滤器开发的时候,Nginx提供的注册过滤器的精妙机制。参考Nginx开发从入门到精通-过滤模块

原文:Nginx源码:利用C语言tricky构建函数链

过滤模块简介

Nginx本身就是模块化的设计,在处理HTTP请求的过程中,就是由各种不同的模块在不同的时机参与处理请求和回发响应。模块就像流水线上的工人一样,在特定的位置做特定的事情,如果想要对请求做新的处理,只需要添加新的工人。工人处理完自己的工作后,就交给下一个工人处理,直到全部处理完。过滤模块是一类模块,它们即可以处理请求头部,也可以处理请求体。

Nginx的另一个特点是,所有的模块都是通过编译,直接生成在Nginx的可执行文件中的,并不是动态加载的,这也是Nginx维持高性能的原因之一。

开发一个过滤模块

注册一个过滤模块时,通常都需要执行类似下面的初始化代码:

static ngx_int_t ngx_http_zip_header_filter(ngx_http_request_t *r);
static ngx_int_t ngx_http_zip_body_filter(ngx_http_request_t *r, ngx_chain_t *in);

static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;

static ngx_int_t ngx_http_zip_init(ngx_conf_t *cf)
{
    ngx_http_next_header_filter = ngx_http_top_header_filter;
    ngx_http_top_header_filter = ngx_http_zip_header_filter;

    ngx_http_next_body_filter = ngx_http_top_body_filter;
    ngx_http_top_body_filter = ngx_http_zip_body_filter;

    return NGX_OK;
}

任何过滤模块的初始化代码都会被Nginx在初始化时调用。请注意:这里的ngx_http_top_header_filterngx_http_top_body_filter是全局变量,而ngx_http_next_header_filterngx_http_next_body_filter是模块的静态变量(是模块级的全局变量),这一点很重要,后面会详细分析。

通过如下调用,将请求交由下一个过滤模块处理:

return ngx_http_next_body_filter(r, in);

表面上看这里的ngx_http_next_body_filter似乎就是本模块的ngx_http_zip_body_filter啊,怎么是调用其他模块的处理函数呢?

  

ngx_http_top_header_filter是一个全局变量。当编译进一个filter模块的时候,就被赋值为当前filter模块的处理函数。而ngx_http_next_header_filter是一个局部全局变量,它保存了编译前上一个filter模块的处理函数。所以整体看来,就像用全局变量组成的一条单向链表。

上面对这个单向链表的解释有些笼统,对于我这种业余选手,理解起来有些困难。下面从C编译器的工作原理角度详细分析一下

详细分析模块的编译

为了简化描述,我们只考虑header过滤器,而且用top表示ngx_http_top_header_filter,用next表示ngx_http_next_header_filter

假设我们有3个模块a.c,b.c,c.c,大致都是按照上面初始化代码编写的,比如a.c模块伪代码如下(忽略各种函数传参):

static function a();

static function next();

static ngx_int_t init()
{
    next = top;
    top = a;

    return NGX_OK;
}

b.cc.c的代码也是如此,只是将a函数分别变成bc

编译

由于top是nginx定义的全局函数指针变量,属于unsolved symbol,next是静态变量,只在c语言模块中有效,所以c编译器在完成模块编译后,生成的a.o大致是这样的:

如上图:top在未解决符号表,等待链接器处理,next在模块变量部分,next静态变量在编译时默认值为0,假设a函数在a.o模块中的地址(偏移量)是0xaaainit函数中,将top所在的内存中的值赋值给next的值简化为next.value=top.value,将a函数赋值给top,等同于将a函数在编译时的地址值0xaaa写入top所在的内存。

相应的b.oc.o大致是这样的:

链接

链接就是把.o文件拼接在一起,在拼接过程中需要做两件重要的事情:一个是地址偏移重定向,这个过程可以确定全局变量在代码段中的位置。二是将各个模块中所有的未解决符号引用改成实际的地址。在这个例子中top作为全局变量,在链接的时候ngx_xxx.o被链接进来,并确定了其在最后可执行文件中的位置,我们假设是0x111,然后各个模块对top的引用都将修改成这个地址,最后在可执行文件中是这样的(忽略地址偏移重定向):

初始化

上面说过Nginx会在初始化的时候,执行各个模块的init,假设这里依次执行a.initb.initc.init,内存如下:

注意图中红色的变量的变化。最后,top这个函数指针指向了c模块的c函数(c函数的偏移地址为0xccc),而c模块的next这个函数指针指向了b模块的b函数(b函数的偏移地址为0xbbb),而b模块的next这个函数指针指向了a模块的a函数(a函数的偏移地址为0xaaa),a模块的next指针为0。这样在b函数中调用本模块bnext,却执行了a模块的a函数,而这里的a,b,c函数都是过滤器的实际处理函数,因此,过滤器处理函数如同一条链一样通过各自模块的next彼此相连。Nginx只需要调用top,就可以按照c()->b()—>a()将所有的处理函数都执行一遍(当然前提是处理函数都会调用next)

不得不承认此种方法的精妙。

问:32位的内存表示不应该是0xaaaaaaaa,怎么只有0xaaa??

答:好吧,css看多了。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/39076.html

相关文章

  • Nginx源码分析】Nginx的内存管理

    摘要:而对于堆内存,通常需要程序员进行管理。我们通常说的内存管理亦是只堆空间内存管理。内存管理整体可以分为个部分,第一部分是常规的内存池,用于进程平时所需的内存管理第二部分是共享内存的管理。将内存块按照的整数次幂进行划分最小为最大为。 施洪宝 一. 概述 应用程序的内存可以简单分为堆内存,栈内存。对于栈内存而言,在函数编译时,编译器会插入移动栈当前指针位置的代码,实现栈空间的自管理。而对于...

    raise_yang 评论0 收藏0
  • Nginx源码研究】内存管理部分

    摘要:而对于堆内存,通常需要程序员进行管理。二内存池管理说明本部分使用的版本为具体源码参见文件实现使用流程内存池的使用较为简单可以分为步,调用函数获取指针。将内存块按照的整数次幂进行划分最小为最大为。 运营研发团队 施洪宝 一. 概述 应用程序的内存可以简单分为堆内存,栈内存。对于栈内存而言,在函数编译时,编译器会插入移动栈当前指针位置的代码,实现栈空间的自管理。而对于堆内存,通常需要程序...

    sarva 评论0 收藏0
  • Nginx 源码分析:ngx_pool_t

    摘要:源代码路径版本主要作用分析提供了一种机制,帮助进行资源管理内存文件。用来标记该使用时分配失败次数。根据以上思路,可以很容易明白源码里关于创建链表的代码函数声明说明输入要分配的节点大小,返回一个的指针。 源代码路径 版本:1.8.0 srccoreNgx_palloc.h srccoreNgx_palloc.c 主要作用分析 提供了一种机制,帮助进行资源管理(内存、文件)。可以...

    codergarden 评论0 收藏0
  • 分析Nginx 源码 - ngx_palloc文件总结

    摘要:关于是自身实现的一个内存池模块,其遍及整个的源码之中,也是能简洁高效处理各个请求的基础所在。它本身是一个记录表,其中记录了整个内内存池的内存分配信息链的头指针。 关于 palloc是nginx自身实现的一个内存池模块,其遍及整个nginx的源码之中,也是nginx能简洁高效处理各个请求的基础所在。本文先从ngx_alloc和ngx_palloc2个文件来解读内存模块。 ngx_all...

    Steve_Wang_ 评论0 收藏0
  • Nginx 源码分析:ngx_hash_t(上)

    摘要:现在使用的各种哈希函数基本上只能保证较小概率出现两个不同的其相同的情况。而出现两个值对应的相同的情况,称为哈希冲突。中的哈希表需要指出的是,中自造的哈希表属于内部使用的数据结构,因此,并不是一个通用的哈希表。 源文件路径 版本:1.8.0 csrccoreNgx_hash.h srccoreNgx_hash.c 关于hash表 Nginx实现的hash表和常见的hash表大体...

    waruqi 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<