闲不下来-nginx基本数据结构(五)

1,067 阅读7分钟

闲不下来-nginx 基本数据结构

在阅读 nginx 源码之前,务必先了解一下其定义的基本数据结构,若是不了解,则会阅读的很吃力,且一头雾水。

nginx基本数据结构-xTBsfv

基本数据类型

整型

无非就是判断是否是 64 位,我们看一下代码中是如何定义:

首先在src/core/ngx_config.h定义了基本的数据映射

typedef intptr_t        ngx_int_t;
typedef uintptr_t       ngx_uint_t;
typedef intptr_t        ngx_flag_t;

其中intptr_tuintptr_t在文件/Library/Developer/CommandLineTools/SDKs/MacOSX12.0.sdk/usr/include/sys/_types/_intptr_t.h中:

typedef __darwin_intptr_t       intptr_t;

注意:本阅读源码的环境:macOS 12

我们继续点击__darwin_intptr_t可以看到:

typedef long                    __darwin_intptr_t;

显然是long,长整型,以此类推,我们看一下uintptr_t

typedef unsigned long           uintptr_t;

从源码可知,nginx 定义的基本数据类型无非就是longunsigend long,再次声明:源码版本是:0.5

字符串类型

聊完整型,字符串也是常见的基本数据类型,所以我们来看看 nginx 是如何定义的,那么首先在src/core/ngx_string.h这个文件中,能看到:

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

ngx_str_t结构无非是在 c 语言的基础上包装了一个长度,size_t,接下来继续看一下ngx_keyvalue_t

typedef struct {
    ngx_str_t   key;
    ngx_str_t   value;
} ngx_keyval_t;

ngx_keyval_t结构包含了keyvalue,其内部结构分别是ngx_str_t,都是字符串的基本类型

typedef struct {
    unsigned    len:29;

    unsigned    valid:1;
    unsigned    no_cacheable:1;
    unsigned    not_found:1;

    u_char     *data;
} ngx_variable_value_t;

这个ngx_variable_value_t的结构体看起来比较复杂,包含了较多的字段,我们一一来看一地下:

尽管,我对 c 语言不是很熟悉,但是从这个形式和格式来看,大概是能猜出来什么意思,所以我们阅读源码不要怕,可以猜一猜,然后从书上或者互联网找到对应的答案进行核对你的猜想,所以,不要怕,尽管猜,不要怂,尽管动脑,这样不仅提高了在面对自己头一次阅读大型优秀框架的源码的勇气,而且自学的能力进一步的增强。

  • len:经过前面的预热,显然是该结构中有效的长度,长度的大小定义为 29,毕竟无符号,且并不是long,自然 29
  • valid:是否有效,大胆猜一猜,该字段定义的原因不就是增加了开关?控制该变量可用不可用,要不然其结构的名称为什么是可变的
  • no_cacheable:是否开启缓存,
  • not_found:是否可见,和其上面两个字段可看做标志位
  • *data:可理解为内容...

我们接着看几个对字符串初始化或者赋值的一些方法:

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

// 说明一下,ngx_str_set和ngx_str_null 0.5版本是没有的,0.8版本才出现...
#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

虽然ngx_str_setngx_str_null在 0.8 版本才出现,但是不妨碍我们去了解一下它们...

若已经定义了 Nginx 字符串变量之后再赋值,则必须使用 ngx_str_set, ngx_str_null 宏定义,比如:

/* 正确写法*/
ngx_str_t str1 = ngx_string("hello nginx");
ngx_str_t str2 = ngx_null_string;

/* 错误写法*/
ngx_str_t str1, str2;
str1 = ngx_string("hello nginx");   /* 编译出错 */
str2 = ngx_null_string;

/*如果想按照第二种写法,那么使用以下方法*/
ngx_str_t str1, str2;
ngx_str_set(&str1, "hello nginx");
ngx_str_null(&str2);
/* 注意:ngx_string 和 ngx_str_set 字符串参数必须是常量字符串,不能是变量字符串 */

内存池类型

这个ngx_pool_t类型也是 nginx 源码较为常见的类型,我们有必要看一下:

首先在文件core/ngx_core.h定义ngx_pool_tngx_chain_t结构体

typedef struct ngx_pool_s   ngx_pool_t;
typedef struct ngx_chain_s  ngx_chain_t;

接着在文件core/ngx_palloc.h中:

struct ngx_pool_s {
    u_char               *last;
    u_char               *end;
    ngx_pool_t           *current;
    ngx_chain_t          *chain;
    ngx_pool_t           *next;
    ngx_pool_large_t     *large;
    ngx_pool_cleanup_t   *cleanup;
    ngx_log_t            *log;
};
  • *last:当前内存分配的结束位置,即下一段可分配内存的起始位置
  • *end:内存池的结束位置
  • *current:当前内存池
  • *next:指向下一个内存池
  • *chain:指向一个 ngx_chain_t 结构
  • *large:大块内存链表,即分配空间超过 max 的内存
  • *cleanup:析构函数,释放内存池
  • *log:内存分配相关的日志信息

显然,内存池的结构可是比较复杂的,但是不妨碍我们看不懂呀?不就是加了一些链表的指向信息、释放内存、日志信息等

在了解ngx_chain_t的结构的前提,先了解缓冲区ngx_buf_t类型

缓冲区

不过,我想补充一下缓冲区的概念:

缓冲区(buffer),它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区,显然缓冲区是具有一定大小的。

为什么引入呢?

高速设备与低速设备的不匹配,势必会让高速设备花时间等待低速设备,我们可以在这两者之间设立一个缓冲区。

缓冲区的作用:

  1. 可以解除两者的制约关系,数据可以直接送往缓冲区,高速设备不用再等待低速设备,提高了计算机的效率。

比如,我们使用打印机(IO 设备)打印文档,由于打印机的打印速度相对较慢,我们可以先把一个一个文档输出到对应的缓冲区中,解放 cpu,让打印机去缓冲区拿数据自行逐步打印。

  1. 可以减少数据的读写次数,如果每次数据只传输一点数据,就需要传送很多次,这样会浪费较多的时间,因为开始读写与终止读写所需要的 IO 时间较长,假设我们将数据送往缓冲区,待缓冲区满了以后再进行传送,这样会大大减少读写次数,从而减少时间。

比如,我们想将数据写入到磁盘中,不是立马将数据写到磁盘中,而是先输入缓冲区中,当缓冲区满了以后,再将数据写入到磁盘中,装就可以减少磁盘的读写次数,什么情况下会将数据写入到磁盘中呢?很显然,日志呀?哪个框架没得日志?

所以,从简单来讲,缓冲区就是一块内存,它作用在 IO 设备和 CPU 之间,用来存储数据。它使得低速的 IO 设备和高速的 CPU 能够协调工作,避免低速的 IO 设备占用 CPU,释放出 CPU,使其能够高效率工作

毫无疑问,你也猜到了,在core/ngx_buf.h文件中:

typedef struct ngx_buf_s  ngx_buf_t;

struct ngx_buf_s {
    u_char          *pos;
    u_char          *last;
    off_t            file_pos;
    off_t            file_last;

    u_char          *start;         /* start of buffer */
    u_char          *end;           /* end of buffer */
    ngx_buf_tag_t    tag;
    ngx_file_t      *file;
    ngx_buf_t       *shadow;


    /* the buf's content could be changed */
    unsigned         temporary:1;

    /*
     * the buf's content is in a memory cache or in a read only memory
     * and must not be changed
     */
    unsigned         memory:1;

    /* the buf's content is mmap()ed and must not be changed */
    unsigned         mmap:1;

    /* 可回收,即这些buf可被释放 */
    unsigned         recycled:1;
    unsigned         in_file:1;
    unsigned         flush:1;
    unsigned         sync:1;
    unsigned         last_buf:1;
    unsigned         last_in_chain:1;

    unsigned         last_shadow:1;
    unsigned         temp_file:1;

    unsigned         zerocopy_busy:1;

    /* STUB */ int   num;
};

天呐,缓冲区猛一看,好复杂...

  • *pos:缓冲区数据在内存的起始位置
  • *last:缓冲区数据在内存的结束位置
  • file_posfile_last:服务于文件,作用类似于*pos*last而已
  • *start*end:因为实际数据可能被包含在多个缓冲区中,所以则缓冲区的 start 和 end 指向这块内存的开始地址和结束地址,而 pos 和 last 是指向本缓冲区实际包含的数据的开始和结尾,简单来说一个数据的起始和结束,一个内存的起始和结束
  • *file:指向 buffer 对应的文件对象
  • *shadow:当前缓冲区的一个影子缓冲区,即当一个缓冲区复制另一个缓冲区的数据,(理解为备份?)就会发生相互指向对方的 shadow 指针
  • temporary:为 1 时,表示该 buf 所包含的内容在用户创建的内存块中可以被 filter 处理变更
  • memory:为 1 时,表示该 buf 所包含的内容在内存中,不能被 filter 处理变更
  • mmap:为 1 时,表示该 buf 所包含的内容在内存中,可通过 mmap(后期讲一下 mmap)把文件映射到内存中,不能被 filter 处理变更

接下来,轮到了ngx_chain_t

ngx_chain_t 数据类型是与缓冲区类型 ngx_buf_t 相关的链表结构,定义如下:

struct ngx_chain_s {
    ngx_buf_t    *buf;
    ngx_chain_t  *next;
};
  • *buf:指向当前缓冲区
  • *chain:指向下一个 chain,形成 chain 链表

该结构的示意图如下:

nginx-chain-iBQ9Kw

小结

本文主要讲了 nginx 的基本结构,知道了数据结构才能对接下来的业务逻辑了解的更加透彻。

参考