闲不下来-nginx 基本数据结构
在阅读 nginx 源码之前,务必先了解一下其定义的基本数据结构,若是不了解,则会阅读的很吃力,且一头雾水。
基本数据类型
整型
无非就是判断是否是 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_t
和uintptr_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 定义的基本数据类型无非就是long
和unsigend 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
结构包含了key
和value
,其内部结构分别是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
,自然 29valid
:是否有效,大胆猜一猜,该字段定义的原因不就是增加了开关?控制该变量可用不可用,要不然其结构的名称为什么是可变的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_set
和ngx_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_t
和ngx_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),它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区,显然缓冲区是具有一定大小的。
为什么引入呢?
高速设备与低速设备的不匹配,势必会让高速设备花时间等待低速设备,我们可以在这两者之间设立一个缓冲区。
缓冲区的作用:
- 可以解除两者的制约关系,数据可以直接送往缓冲区,高速设备不用再等待低速设备,提高了计算机的效率。
比如,我们使用打印机(IO 设备)打印文档,由于打印机的打印速度相对较慢,我们可以先把一个一个文档输出到对应的缓冲区中,解放 cpu,让打印机去缓冲区拿数据自行逐步打印。
- 可以减少数据的读写次数,如果每次数据只传输一点数据,就需要传送很多次,这样会浪费较多的时间,因为开始读写与终止读写所需要的 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_pos
和file_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 的基本结构,知道了数据结构才能对接下来的业务逻辑了解的更加透彻。