vxlan学习笔记

1. 报文格式

具体参照RFC7348
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1

Outer Ethernet Header:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Outer Destination MAC Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Outer Destination MAC Address | Outer Source MAC Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Outer Source MAC Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|OptnlEthtype = C-Tag 802.1Q | Outer.VLAN Tag Information |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Ethertype = 0x0800 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Outer IPv4 Header:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| IHL |Type of Service| Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identification |Flags| Fragment Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Time to Live |Protocl=17(UDP)| Header Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Outer Source IPv4 Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Outer Destination IPv4 Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Outer UDP Header:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Dest Port = VXLAN Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| UDP Length | UDP Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

VXLAN Header:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|R|R|R|R|I|R|R|R| Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| VXLAN Network Identifier (VNI) | Reserved |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Inner Ethernet Header:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Inner Destination MAC Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Inner Destination MAC Address | Inner Source MAC Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Inner Source MAC Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|OptnlEthtype = C-Tag 802.1Q | Inner.VLAN Tag Information |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Payload:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Ethertype of Original Payload | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| Original Ethernet Payload |
| |
|(Note that the original Ethernet Frame’s FCS is not included) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Frame Check Sequence:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| New FCS (Frame Check Sequence) for Outer Ethernet Frame |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

2. 字段说明

Outer MAC Header:封装外层以太头,14字节,如果有VLAN TAG则为18字节。其中,源MAC地址(Outer Source MAC Address)为源VM所属VTEP的MAC地址,目的MAC地址(Outer Destination MAC Address)为到达目的VTEP的路径上下一跳设备的MAC地址。类型字段为0x0800,指示内层封装的是IP报文。
Outer IP Header:封装外层IP头,20字节。其中,源IP地址(Outer Source IP Address)为源VM所属VTEP的IP地址,目的IP地址(Outer Destination IP Address)为目的VM所属VTEP的IP地址。协议字段为0x11,指示内层封装的是UDP报文。
UDP Header:UDP报文头,8字节。其中,UDP目的端口号(UDP Destination Port)固定为4789,指示内层封装报文为VXLAN报文。UDP源端口号(UDP Source Port)为随机任意值,可以用于VTEP之间多路径负载分担的计算。
VXLAN Header:VXLAN协议新定义的VXLAN头,8字节。
Flags:8 bit,RRRRIRRR。”I”位为1时,表示VXLAN头中的VXLAN ID有效;为0,表示VXLAN ID无效。”R”位保留未用,设置为0。
VXLAN ID(VNI):24 bit,用于标识一个单独的VXLAN网络。
Reserved:分别为24 bit和8 bit。保留位。
Original L2 Frame:原始以太网报文。

FFmpeg学习笔记

1、组件

1.1、avcodec:编解码(最重要的库)

1.2、avdevice:各种设备的输入输出

1.3、avfilter:滤镜特效处理

1.4、avformat:封装格式处理

1.5、avutil:工具库(大部分库都需要这个库的支持)

1.6、postproc:后加工

1.7、swresample:音频采样数据格式转换

1.8、swscale:视频像素数据格式转换

2、命令行工具

2.1、ffmpeg

fmpeg是用于转码的应用程序,常用命令可用ffmpeg -h显示

2.2、ffplay

ffplay用 SDL和FFmpeg库开发的一个简单的媒体播放器,支持格式众多,可支持udp、rtp、hls、rtsp等

2.3、ffprobe

查看多媒体文件信息的模块,此模块可以用来查看多媒体文件格式以及编码。

2.4、ffserver

基于HTTP、RTSP用于实时广播的多媒体服务器.也支持时间平移

Nginx的共享内存详解

1. 简述

1.1 应用介绍

nginx使用共享内存的模块有ngx_http_file_cache_module、ngx_http_limit_conn_module、ngx_http_limit_req_module等模块。无一例外,这几个模块都是使用nginx实现的红黑树,基于共享内存来保存他们所需要的数据。

2 源码详解

2.1 首先来看看相关结构体定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
typedef struct ngx_slab_page_s  ngx_slab_page_t;

struct ngx_slab_page_s {
uintptr_t slab;
ngx_slab_page_t *next; //下一个page页
uintptr_t prev; //上一个page页
};

// slab状态结构体
typedef struct {
ngx_uint_t total;
ngx_uint_t used;

ngx_uint_t reqs;
ngx_uint_t fails;
} ngx_slab_stat_t;


typedef struct {
ngx_shmtx_sh_t lock; //mutex锁

size_t min_size; //设定的最小内存块长度;
size_t min_shift; //ngx_init_zone_pool中默认为3

ngx_slab_page_t *pages; //每一页对应一个ngx_slab_page_t页描述结构体,所有的ngx_slab_page_t存放在连续的内存中构成数组,而pages就是数组首地址
ngx_slab_page_t *last; //最后页面地址
ngx_slab_page_t free; //所有的空闲页组成一个链表挂在free成员上

ngx_slab_stat_t *stats;
ngx_uint_t pfree; // 剩余页数

u_char *start; //第一页的首地址
u_char *end; //指向这段共享内存的尾部

ngx_shmtx_t mutex; //Nginx封装的互斥锁

u_char *log_ctx; // slab操作失败时会记录日志,为区别是哪个slab共享内存出错,可以在slab中分配一段内存存放描述的字符串,然后再用
log_ctx指向这个字符串
u_char zero; // 表示空字符串防止出错

unsigned log_nomem:1;

void *data; //slab的模块自由使用,slab管理内存时不会用到它
void *addr; //指向所属的ngx_shm_zone_t里的ngx_shm_t成员的addr成员,一般用于指示一段共享内存块的起始位置
} ngx_slab_pool_t;

2.2 相关函数及其介绍

主要涉及到共享内存的初始化,加锁申请,不加锁申请,加锁释放,不加锁释放等函数。首先来看下初始化的两个函数ngx_slab_sizes_init、ngx_slab_init,ngx_slab_sizes_init函数使用操作系统内存页初始化slab_max_size。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85

void
ngx_slab_sizes_init(void)
{
ngx_uint_t n;

ngx_slab_max_size = ngx_pagesize / 2;
ngx_slab_exact_size = ngx_pagesize / (8 * sizeof(uintptr_t));
for (n = ngx_slab_exact_size; n >>= 1; ngx_slab_exact_shift++) {
/* void */
}
}

void
ngx_slab_init(ngx_slab_pool_t *pool)
{
u_char *p;
size_t size;
ngx_int_t m;
ngx_uint_t i, n, pages;
ngx_slab_page_t *slots, *page;
//最小分配的空间是8byte
pool->min_size = (size_t) 1 << pool->min_shift;

slots = ngx_slab_slots(pool);

p = (u_char *) slots;
size = pool->end - p;

ngx_slab_junk(p, size);

n = ngx_pagesize_shift - pool->min_shift;

for (i = 0; i < n; i++) {
/* only "next" is used in list head */
slots[i].slab = 0;
slots[i].next = &slots[i];
slots[i].prev = 0;
}
//跳过上面那些slab page
p += n * sizeof(ngx_slab_page_t);

pool->stats = (ngx_slab_stat_t *) p;
ngx_memzero(pool->stats, n * sizeof(ngx_slab_stat_t));

p += n * sizeof(ngx_slab_stat_t);

size -= n * (sizeof(ngx_slab_page_t) + sizeof(ngx_slab_stat_t));

pages = (ngx_uint_t) (size / (ngx_pagesize + sizeof(ngx_slab_page_t)));

pool->pages = (ngx_slab_page_t *) p;
ngx_memzero(pool->pages, pages * sizeof(ngx_slab_page_t));

page = pool->pages;

/* only "next" is used in list head */
//初始化free,free.next是下次分配页时候的入口
pool->free.slab = 0;
pool->free.next = page;
pool->free.prev = 0;
//更新第一个slab page的状态,这儿slab成员记录了整个缓存区的页数目
page->slab = pages;
page->next = &pool->free;
page->prev = (uintptr_t) &pool->free;
//实际缓存区(页)的开头,对齐
//因为对齐的原因,使得m_page数组和数据区域之间可能有些内存无法使用
pool->start = ngx_align_ptr(p + pages * sizeof(ngx_slab_page_t),
ngx_pagesize);

m = pages - (pool->end - pool->start) / ngx_pagesize;
if (m > 0) {
pages -= m;
page->slab = pages;
}
//跳过pages * sizeof(ngx_slab_page_t)
pool->last = pool->pages + pages;
pool->pfree = pages;

pool->log_nomem = 1;
pool->log_ctx = &pool->zero;
pool->zero = '\0';
}


接下来看看申请内存的相关函数ngx_slab_alloc、ngx_slab_alloc_locked、ngx_slab_calloc、ngx_slab_calloc_locked

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338

void *
ngx_slab_alloc(ngx_slab_pool_t *pool, size_t size)
{
void *p;
//由于是共享内存,所以在进程间需要用锁来保持同步
ngx_shmtx_lock(&pool->mutex);

p = ngx_slab_alloc_locked(pool, size);

ngx_shmtx_unlock(&pool->mutex);

return p;
}

void *
ngx_slab_alloc_locked(ngx_slab_pool_t *pool, size_t size)
{
size_t s;
uintptr_t p, m, mask, *bitmap;
ngx_uint_t i, n, slot, shift, map;
ngx_slab_page_t *page, *prev, *slots;

if (size > ngx_slab_max_size) {

ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0,
"slab alloc: %uz", size);

page = ngx_slab_alloc_pages(pool, (size >> ngx_pagesize_shift)
+ ((size % ngx_pagesize) ? 1 : 0));
if (page) {
p = ngx_slab_page_addr(pool, page);

} else {
p = 0;
}

goto done;
}

if (size > pool->min_size) {
shift = 1;
for (s = size - 1; s >>= 1; shift++) { /* void */ }
slot = shift - pool->min_shift;

} else {
shift = pool->min_shift;
slot = 0;
}

pool->stats[slot].reqs++;

ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0,
"slab alloc: %uz slot: %ui", size, slot);

slots = ngx_slab_slots(pool);
page = slots[slot].next;

if (page->next != page) {

if (shift < ngx_slab_exact_shift) {

bitmap = (uintptr_t *) ngx_slab_page_addr(pool, page);

map = (ngx_pagesize >> shift) / (8 * sizeof(uintptr_t));

for (n = 0; n < map; n++) {

if (bitmap[n] != NGX_SLAB_BUSY) {

for (m = 1, i = 0; m; m <<= 1, i++) {
if (bitmap[n] & m) {
continue;
}

bitmap[n] |= m;

i = (n * 8 * sizeof(uintptr_t) + i) << shift;

p = (uintptr_t) bitmap + i;

pool->stats[slot].used++;

if (bitmap[n] == NGX_SLAB_BUSY) {
for (n = n + 1; n < map; n++) {
if (bitmap[n] != NGX_SLAB_BUSY) {
goto done;
}
}

prev = ngx_slab_page_prev(page);
prev->next = page->next;
page->next->prev = page->prev;

page->next = NULL;
page->prev = NGX_SLAB_SMALL;
}

goto done;
}
}
}

} else if (shift == ngx_slab_exact_shift) {

for (m = 1, i = 0; m; m <<= 1, i++) {
if (page->slab & m) {
continue;
}

page->slab |= m;

if (page->slab == NGX_SLAB_BUSY) {
prev = ngx_slab_page_prev(page);
prev->next = page->next;
page->next->prev = page->prev;

page->next = NULL;
page->prev = NGX_SLAB_EXACT;
}

p = ngx_slab_page_addr(pool, page) + (i << shift);

pool->stats[slot].used++;

goto done;
}

} else { /* shift > ngx_slab_exact_shift */

mask = ((uintptr_t) 1 << (ngx_pagesize >> shift)) - 1;
mask <<= NGX_SLAB_MAP_SHIFT;

for (m = (uintptr_t) 1 << NGX_SLAB_MAP_SHIFT, i = 0;
m & mask;
m <<= 1, i++)
{
if (page->slab & m) {
continue;
}

page->slab |= m;

if ((page->slab & NGX_SLAB_MAP_MASK) == mask) {
prev = ngx_slab_page_prev(page);
prev->next = page->next;
page->next->prev = page->prev;

page->next = NULL;
page->prev = NGX_SLAB_BIG;
}

p = ngx_slab_page_addr(pool, page) + (i << shift);

pool->stats[slot].used++;

goto done;
}
}

ngx_slab_error(pool, NGX_LOG_ALERT, "ngx_slab_alloc(): page is busy");
ngx_debug_point();
}
//分出一页加入到m_slot数组对应元素中
page = ngx_slab_alloc_pages(pool, 1);

if (page) {
if (shift < ngx_slab_exact_shift) {
bitmap = (uintptr_t *) ngx_slab_page_addr(pool, page);

n = (ngx_pagesize >> shift) / ((1 << shift) * 8);

if (n == 0) {
n = 1;
}

/* "n" elements for bitmap, plus one requested */

for (i = 0; i < (n + 1) / (8 * sizeof(uintptr_t)); i++) {
bitmap[i] = NGX_SLAB_BUSY;
}

m = ((uintptr_t) 1 << ((n + 1) % (8 * sizeof(uintptr_t)))) - 1;
bitmap[i] = m;

map = (ngx_pagesize >> shift) / (8 * sizeof(uintptr_t));

for (i = i + 1; i < map; i++) {
bitmap[i] = 0;
}

page->slab = shift;
page->next = &slots[slot];
page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_SMALL;

slots[slot].next = page;

pool->stats[slot].total += (ngx_pagesize >> shift) - n;

p = ngx_slab_page_addr(pool, page) + (n << shift);

pool->stats[slot].used++;

goto done;

} else if (shift == ngx_slab_exact_shift) {

page->slab = 1;
page->next = &slots[slot];
page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_EXACT;

slots[slot].next = page;

pool->stats[slot].total += 8 * sizeof(uintptr_t);

p = ngx_slab_page_addr(pool, page);

pool->stats[slot].used++;

goto done;

} else { /* shift > ngx_slab_exact_shift */

page->slab = ((uintptr_t) 1 << NGX_SLAB_MAP_SHIFT) | shift;
page->next = &slots[slot];
page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_BIG;

slots[slot].next = page;

pool->stats[slot].total += ngx_pagesize >> shift;

p = ngx_slab_page_addr(pool, page);

pool->stats[slot].used++;

goto done;
}
}

p = 0;

pool->stats[slot].fails++;

done:

ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0,
"slab alloc: %p", (void *) p);

return (void *) p;
}

/* 由于是共享内存,所以在进程间需要用锁来保持同步 */
void *
ngx_slab_calloc(ngx_slab_pool_t *pool, size_t size)
{
void *p;

ngx_shmtx_lock(&pool->mutex);

p = ngx_slab_calloc_locked(pool, size);

ngx_shmtx_unlock(&pool->mutex);

return p;
}

void *
ngx_slab_calloc_locked(ngx_slab_pool_t *pool, size_t size)
{
void *p;

p = ngx_slab_alloc_locked(pool, size);
if (p) {
ngx_memzero(p, size);
}

return p;
}


static ngx_slab_page_t *
ngx_slab_alloc_pages(ngx_slab_pool_t *pool, ngx_uint_t pages)
{
ngx_slab_page_t *page, *p;
//初始化的时候pool->free.next默认指向第一个pool->pages
//从pool->free.next开始,每次取(slab page) page = page->next
for (page = pool->free.next; page != &pool->free; page = page->next) {

if (page->slab >= pages) {

if (page->slab > pages) {
page[page->slab - 1].prev = (uintptr_t) &page[pages];

page[pages].slab = page->slab - pages;
page[pages].next = page->next;
page[pages].prev = page->prev;

p = (ngx_slab_page_t *) page->prev;
p->next = &page[pages];
page->next->prev = (uintptr_t) &page[pages];

} else {//page页不够用了,则free的next和prev都指向自己
p = (ngx_slab_page_t *) page->prev;
p->next = page->next;
page->next->prev = page->prev;
}
//NGX_SLAB_PAGE_START标记page是分配的pages个页的第一个页,并在第一个页page中记录出其后连续的pages个页是一起分配的
page->slab = pages | NGX_SLAB_PAGE_START;
page->next = NULL;
page->prev = NGX_SLAB_PAGE;

pool->pfree -= pages;
//pages为1。则直接返回该page
if (--pages == 0) {
return page;
}

for (p = page + 1; pages; pages--) {
//如果分配的页数pages>1,更新后面page slab的slab成员为NGX_SLAB_PAGE_BUSY
p->slab = NGX_SLAB_PAGE_BUSY;
p->next = NULL;
p->prev = NGX_SLAB_PAGE;
p++;
}

return page;
}
}

if (pool->log_nomem) {
ngx_slab_error(pool, NGX_LOG_CRIT,
"ngx_slab_alloc() failed: no memory");
}

return NULL;
}


最后来看看释放内存相关函数ngx_slab_free、ngx_slab_free_locked

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228

void
ngx_slab_free(ngx_slab_pool_t *pool, void *p)
{
ngx_shmtx_lock(&pool->mutex);

ngx_slab_free_locked(pool, p);

ngx_shmtx_unlock(&pool->mutex);
}

void
ngx_slab_free_locked(ngx_slab_pool_t *pool, void *p)
{
size_t size;
uintptr_t slab, m, *bitmap;
ngx_uint_t i, n, type, slot, shift, map;
ngx_slab_page_t *slots, *page;

ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, ngx_cycle->log, 0, "slab free: %p", p);

if ((u_char *) p < pool->start || (u_char *) p > pool->end) {
ngx_slab_error(pool, NGX_LOG_ALERT, "ngx_slab_free(): outside of pool");
goto fail;
}
//根据p找到需要释放的m_page元素
n = ((u_char *) p - pool->start) >> ngx_pagesize_shift;
page = &pool->pages[n];
//如果分配的时候一次性分配多个page,则第一个page的slab指定本次一次性分配了多少个页page
slab = page->slab;
type = ngx_slab_page_type(page);

switch (type) {

case NGX_SLAB_SMALL:

shift = slab & NGX_SLAB_SHIFT_MASK;
size = (size_t) 1 << shift;

if ((uintptr_t) p & (size - 1)) {
goto wrong_chunk;
}

n = ((uintptr_t) p & (ngx_pagesize - 1)) >> shift;
m = (uintptr_t) 1 << (n % (8 * sizeof(uintptr_t)));
n /= 8 * sizeof(uintptr_t);
bitmap = (uintptr_t *)
((uintptr_t) p & ~((uintptr_t) ngx_pagesize - 1));

if (bitmap[n] & m) {
slot = shift - pool->min_shift;

if (page->next == NULL) {
slots = ngx_slab_slots(pool);

page->next = slots[slot].next;
slots[slot].next = page;

page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_SMALL;
page->next->prev = (uintptr_t) page | NGX_SLAB_SMALL;
}

bitmap[n] &= ~m;

n = (ngx_pagesize >> shift) / ((1 << shift) * 8);

if (n == 0) {
n = 1;
}

i = n / (8 * sizeof(uintptr_t));
m = ((uintptr_t) 1 << (n % (8 * sizeof(uintptr_t)))) - 1;

if (bitmap[i] & ~m) {
goto done;
}

map = (ngx_pagesize >> shift) / (8 * sizeof(uintptr_t));

for (i = i + 1; i < map; i++) {
if (bitmap[i]) {
goto done;
}
}

ngx_slab_free_pages(pool, page, 1);

pool->stats[slot].total -= (ngx_pagesize >> shift) - n;

goto done;
}

goto chunk_already_free;

case NGX_SLAB_EXACT:

m = (uintptr_t) 1 <<
(((uintptr_t) p & (ngx_pagesize - 1)) >> ngx_slab_exact_shift);
size = ngx_slab_exact_size;

if ((uintptr_t) p & (size - 1)) {
goto wrong_chunk;
}
//slab(位图)中对应的位为1
if (slab & m) {
slot = ngx_slab_exact_shift - pool->min_shift;

if (slab == NGX_SLAB_BUSY) {
slots = ngx_slab_slots(pool);

page->next = slots[slot].next;
slots[slot].next = page;

page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_EXACT;
page->next->prev = (uintptr_t) page | NGX_SLAB_EXACT;
}

page->slab &= ~m;

if (page->slab) {
goto done;
}

ngx_slab_free_pages(pool, page, 1);

pool->stats[slot].total -= 8 * sizeof(uintptr_t);

goto done;
}

goto chunk_already_free;

case NGX_SLAB_BIG:
//slab的高16位是slot块的位图,低16位用于存储slot块大小的偏移
shift = slab & NGX_SLAB_SHIFT_MASK;
size = (size_t) 1 << shift;

if ((uintptr_t) p & (size - 1)) {
goto wrong_chunk;
}

m = (uintptr_t) 1 << ((((uintptr_t) p & (ngx_pagesize - 1)) >> shift)
+ NGX_SLAB_MAP_SHIFT);
//该slab块确实正在被使用
if (slab & m) {
slot = shift - pool->min_shift;

if (page->next == NULL) {
slots = ngx_slab_slots(pool);

page->next = slots[slot].next;
slots[slot].next = page;

page->prev = (uintptr_t) &slots[slot] | NGX_SLAB_BIG;
page->next->prev = (uintptr_t) page | NGX_SLAB_BIG;
}

page->slab &= ~m;

if (page->slab & NGX_SLAB_MAP_MASK) {
goto done;
}
//如果page页中所有slab块都不在使用就将该页面链入free中
ngx_slab_free_pages(pool, page, 1);

pool->stats[slot].total -= ngx_pagesize >> shift;

goto done;
}

goto chunk_already_free;
//用户归还整个页面
case NGX_SLAB_PAGE:

if ((uintptr_t) p & (ngx_pagesize - 1)) {
goto wrong_chunk;
}

if (!(slab & NGX_SLAB_PAGE_START)) {
ngx_slab_error(pool, NGX_LOG_ALERT,
"ngx_slab_free(): page is already free");
goto fail;
}

if (slab == NGX_SLAB_PAGE_BUSY) {
ngx_slab_error(pool, NGX_LOG_ALERT,
"ngx_slab_free(): pointer to wrong page");
goto fail;
}
//计算归还page的个数
size = slab & ~NGX_SLAB_PAGE_START;
//归还页面
ngx_slab_free_pages(pool, page, size);

ngx_slab_junk(p, size << ngx_pagesize_shift);

return;
}

/* not reached */

return;

done:

pool->stats[slot].used--;

ngx_slab_junk(p, size);

return;

wrong_chunk:

ngx_slab_error(pool, NGX_LOG_ALERT,
"ngx_slab_free(): pointer to wrong chunk");

goto fail;

chunk_already_free:

ngx_slab_error(pool, NGX_LOG_ALERT,
"ngx_slab_free(): chunk is already free");

fail:

return;
}

3 总结

以上就是slab相关函数实现,nginx的slab共享内存借鉴了linux内核的内存管理的实现。

Nginx限流模块详解

1.相关指令

1
2
3
Syntax: limit_conn_zone key zone=name:size;
Default: —
Context: http

此指令设置限流所需共享内存的名称和size,key指定限流的标志,比如$binary_remote_addr。

1
2
3
4
5

Syntax: limit_conn zone number;
Default: —
Context: http, server, location

此指令指定共享内存名以及限制多少连接,比如limit_conn_zone指令key设置为$binary_remote_addr,zone设置为limit_zone,那么来自于$binary_remote_addr的连接超过number,nginx直接给客户端响应503。

2.源码解析

2.1 配置解析函数

  1. 首先来看下limit_conn_zone指令的解析函数ngx_http_limit_conn_zone,这个函数的功能主要是解析共享内存的name和size,然后使用这两个值作为参数调用ngx_shared_memory_add函数,将共享内存添加到nginx共享内存管理模块中,设置共享内存的data以及初始化函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
static char *
ngx_http_limit_conn_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
u_char *p;
ssize_t size;
ngx_str_t *value, name, s;
ngx_uint_t i;
ngx_shm_zone_t *shm_zone;
ngx_http_limit_conn_ctx_t *ctx;
ngx_http_compile_complex_value_t ccv;

value = cf->args->elts;

ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_conn_ctx_t));
if (ctx == NULL) {
return NGX_CONF_ERROR;
}

ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

ccv.cf = cf;
ccv.value = &value[1];
ccv.complex_value = &ctx->key;

if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
return NGX_CONF_ERROR;
}

size = 0;
name.len = 0;

for (i = 2; i < cf->args->nelts; i++) {

if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {

name.data = value[i].data + 5;

p = (u_char *) ngx_strchr(name.data, ':');

if (p == NULL) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid zone size \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}

name.len = p - name.data;

s.data = p + 1;
s.len = value[i].data + value[i].len - s.data;

size = ngx_parse_size(&s);

if (size == NGX_ERROR) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid zone size \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}

if (size < (ssize_t) (8 * ngx_pagesize)) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"zone \"%V\" is too small", &value[i]);
return NGX_CONF_ERROR;
}

continue;
}

ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid parameter \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}

if (name.len == 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"\"%V\" must have \"zone\" parameter",
&cmd->name);
return NGX_CONF_ERROR;
}

shm_zone = ngx_shared_memory_add(cf, &name, size,
&ngx_http_limit_conn_module);
if (shm_zone == NULL) {
return NGX_CONF_ERROR;
}

if (shm_zone->data) {
ctx = shm_zone->data;

ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"%V \"%V\" is already bound to key \"%V\"",
&cmd->name, &name, &ctx->key.value);
return NGX_CONF_ERROR;
}

shm_zone->init = ngx_http_limit_conn_init_zone;
shm_zone->data = ctx;

return NGX_CONF_OK;
}

  1. 接下来看下limit_conn指令解析函数ngx_http_limit_conn,这个函数主要是保存配置的共享内存名以及限制连接的个数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63

static char *
ngx_http_limit_conn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_shm_zone_t *shm_zone;
ngx_http_limit_conn_conf_t *lccf = conf;
ngx_http_limit_conn_limit_t *limit, *limits;

ngx_str_t *value;
ngx_int_t n;
ngx_uint_t i;

value = cf->args->elts;

shm_zone = ngx_shared_memory_add(cf, &value[1], 0,
&ngx_http_limit_conn_module);
if (shm_zone == NULL) {
return NGX_CONF_ERROR;
}

limits = lccf->limits.elts;

if (limits == NULL) {
if (ngx_array_init(&lccf->limits, cf->pool, 1,
sizeof(ngx_http_limit_conn_limit_t))
!= NGX_OK)
{
return NGX_CONF_ERROR;
}
}

for (i = 0; i < lccf->limits.nelts; i++) {
if (shm_zone == limits[i].shm_zone) {
return "is duplicate";
}
}

// 解析配置种被限制的连接数
n = ngx_atoi(value[2].data, value[2].len);
if (n <= 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid number of connections \"%V\"", &value[2]);
return NGX_CONF_ERROR;
}

if (n > 65535) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"connection limit must be less 65536");
return NGX_CONF_ERROR;
}

limit = ngx_array_push(&lccf->limits);
if (limit == NULL) {
return NGX_CONF_ERROR;
}

limit->conn = n;
limit->shm_zone = shm_zone;

return NGX_CONF_OK;
}


  1. 接下来就来看下nginx是如何实现限流的,这个功能由ngx_http_limit_conn_handler实现,主要功能就是统计各个进程与key对应的连接数,超过则返回503,在统计各个进程数据的时候使用了共享内存。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117

static ngx_int_t
ngx_http_limit_conn_handler(ngx_http_request_t *r)
{
size_t n;
uint32_t hash;
ngx_str_t key;
ngx_uint_t i;
ngx_slab_pool_t *shpool;
ngx_rbtree_node_t *node;
ngx_pool_cleanup_t *cln;
ngx_http_limit_conn_ctx_t *ctx;
ngx_http_limit_conn_node_t *lc;
ngx_http_limit_conn_conf_t *lccf;
ngx_http_limit_conn_limit_t *limits;
ngx_http_limit_conn_cleanup_t *lccln;

if (r->main->limit_conn_set) {
return NGX_DECLINED;
}

lccf = ngx_http_get_module_loc_conf(r, ngx_http_limit_conn_module);
limits = lccf->limits.elts;

for (i = 0; i < lccf->limits.nelts; i++) {
ctx = limits[i].shm_zone->data;

//获取limit_conn_zone指令后key的值
if (ngx_http_complex_value(r, &ctx->key, &key) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}

if (key.len == 0) {
continue;
}

if (key.len > 255) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"the value of the \"%V\" key "
"is more than 255 bytes: \"%V\"",
&ctx->key.value, &key);
continue;
}

r->main->limit_conn_set = 1;
//对key进行crc计算,用于在红黑树中查找结点
hash = ngx_crc32_short(key.data, key.len);

shpool = (ngx_slab_pool_t *) limits[i].shm_zone->shm.addr;

ngx_shmtx_lock(&shpool->mutex);
// 查找符合要求的结点
node = ngx_http_limit_conn_lookup(ctx->rbtree, &key, hash);
// 没有找到对应的结点,将此次请求插入到红黑树中
if (node == NULL) {
//计算结点所需内存
n = offsetof(ngx_rbtree_node_t, color)
+ offsetof(ngx_http_limit_conn_node_t, data)
+ key.len;
// 申请结点内存
node = ngx_slab_alloc_locked(shpool, n);

if (node == NULL) {
ngx_shmtx_unlock(&shpool->mutex);
ngx_http_limit_conn_cleanup_all(r->pool);
return lccf->status_code;
}

lc = (ngx_http_limit_conn_node_t *) &node->color;
// 赋值key,用于下次请求到来查询
node->key = hash;
lc->len = (u_char) key.len;
lc->conn = 1;
ngx_memcpy(lc->data, key.data, key.len);

ngx_rbtree_insert(ctx->rbtree, node);

} else {

lc = (ngx_http_limit_conn_node_t *) &node->color;
// 判断key标记的connections是否超过配置,如果超过则返回错误码
if ((ngx_uint_t) lc->conn >= limits[i].conn) {

ngx_shmtx_unlock(&shpool->mutex);

ngx_log_error(lccf->log_level, r->connection->log, 0,
"limiting connections by zone \"%V\"",
&limits[i].shm_zone->shm.name);

ngx_http_limit_conn_cleanup_all(r->pool);
return lccf->status_code;
}
//连接数量+1
lc->conn++;
}

ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"limit conn: %08Xi %d", node->key, lc->conn);

ngx_shmtx_unlock(&shpool->mutex);
//注册内存池清理函数
cln = ngx_pool_cleanup_add(r->pool,
sizeof(ngx_http_limit_conn_cleanup_t));
if (cln == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}

// 绑定内存清理函数
cln->handler = ngx_http_limit_conn_cleanup;
lccln = cln->data;

lccln->shm_zone = limits[i].shm_zone;
lccln->node = node;
}

return NGX_DECLINED;
}
  1. 结点插入函数,限流模块是将每个连接都存进一颗红黑树中,nginx提供一个默认的插入函数,限流模块自己实现了一个插入函数。

3. 总结

以上就是nginx限流模块的基本实现,其他限制请求数的模块类似。

GDB调试技巧

1 预备工作

1.1 添加编译参数

1
gcc -g

只有在编译的时候添加了-g这个参数,才能够使用gdb进行调试。可以使用-O0,避免某些变量被优化

2 调试过程

2.1 调试新进程

gdb 程序名称

2.2 调试已有进程

使用命令 gdb attach pid,跟踪现有进程,也可使用gdb -p pid, 输入bt,直接查看当前进程运行到哪里

2.3 常用命令

  1. 单步调式 s,全称step,意味着每个函数都会进入
  2. 单步调试 n, 全称next,不会进入函数
  3. 打印变量 p, 全称print,可以打印各种变量的值,打印二进制 p/t xxx
  4. 继续进程 c, 全称continue,设置好断点后,继续运行,直到发生断点
  5. 设置断点 b, 全称breakpoint,可设置函数断点也可以设置代码行号断点
  6. 打印堆栈 bt, 全称backtrace, 打印当前函数调用栈
  7. 退出当前函数栈 finish, 当使用s命令进入到某个函数后,可以使用finish指令退出当前函数
  8. 设置命令行参数 set args, 若函数需要argv,args,可以使用此命令设置
  9. 跳转到指定调用栈 f (0…N), 后面跟随栈层,伴随bt命令使用,首先使用bt参看当前函数调用栈,然后使用f命令查看对应栈的信息
  10. 显示隐藏字符串set print elements 0,字符串过长的情况下使用p打印会以…结束,无法完整显示,使用此命令可以显示完全
  11. 条件断点 b point if condition,条件满足才会触发中断
  12. 监控某个值 watch,值改变就会触发中断,也可监控某个内存地址,watch *(内存地址),适用于跟踪一些全局的结构体指针。
  13. 跳转到指定行 until 行号,对于跳出循环很有用
  14. 格式化打印printf fmt, var,类似于C语言的库函数 printf “%s”, str
  15. 打印错误号: p errno
  16. 设置变量set $a
  17. while循环以end结束
  18. layout src, 加载源码
  19. info local, 打印当前帧所有局部变量
  20. p ‘a.c’::xx,打印其他文件的全局变量
  21. p *a@123, 打印以a开始的123字节,需要配合10使用
  22. info args,打印函数入参
  23. r,运行程序

3 总结

以上就是gdb调试常用命令,大多数场景都能应用。

Nginx缓存清理

1、相关配置

1
2
3

proxy_cache_path /dev/sda1/data inactive=60m max_size=10G;

proxy_cache_path指令的inactive参数和max_size参数用来维护缓存队列大小

2、源码解析

  1. 首先来看看配置解析函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
char *
ngx_http_file_cache_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
char *confp = conf;

off_t max_size;
u_char *last, *p;
time_t inactive;
ssize_t size;
ngx_str_t s, name, *value;
ngx_int_t loader_files, manager_files;
ngx_msec_t loader_sleep, manager_sleep, loader_threshold,
manager_threshold;
ngx_uint_t i, n, use_temp_path;
ngx_array_t *caches;
ngx_http_file_cache_t *cache, **ce;

cache = ngx_pcalloc(cf->pool, sizeof(ngx_http_file_cache_t));
if (cache == NULL) {
return NGX_CONF_ERROR;
}

cache->path = ngx_pcalloc(cf->pool, sizeof(ngx_path_t));
if (cache->path == NULL) {
return NGX_CONF_ERROR;
}

use_temp_path = 1;

inactive = 600;

loader_files = 100;
loader_sleep = 50;
loader_threshold = 200;

manager_files = 100;
manager_sleep = 50;
manager_threshold = 200;

name.len = 0;
size = 0;
max_size = NGX_MAX_OFF_T_VALUE;

value = cf->args->elts;
//指定路径名称
cache->path->name = value[1];

if (cache->path->name.data[cache->path->name.len - 1] == '/') {
cache->path->name.len--;
}

if (ngx_conf_full_name(cf->cycle, &cache->path->name, 0) != NGX_OK) {
return NGX_CONF_ERROR;
}

//解析proxy_cache_path指令后的参数
for (i = 2; i < cf->args->nelts; i++) {
//解析目录层级参数
if (ngx_strncmp(value[i].data, "levels=", 7) == 0) {

p = value[i].data + 7;
last = value[i].data + value[i].len;

for (n = 0; n < NGX_MAX_PATH_LEVEL && p < last; n++) {

if (*p > '0' && *p < '3') {

cache->path->level[n] = *p++ - '0';
cache->path->len += cache->path->level[n] + 1;

if (p == last) {
break;
}

if (*p++ == ':' && n < NGX_MAX_PATH_LEVEL - 1 && p < last) {
continue;
}

goto invalid_levels;
}

goto invalid_levels;
}

if (cache->path->len < 10 + NGX_MAX_PATH_LEVEL) {
continue;
}

invalid_levels:

ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid \"levels\" \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}

if (ngx_strncmp(value[i].data, "use_temp_path=", 14) == 0) {

if (ngx_strcmp(&value[i].data[14], "on") == 0) {
use_temp_path = 1;

} else if (ngx_strcmp(&value[i].data[14], "off") == 0) {
use_temp_path = 0;

} else {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid use_temp_path value \"%V\", "
"it must be \"on\" or \"off\"",
&value[i]);
return NGX_CONF_ERROR;
}

continue;
}
//解析共享内存名称参数
if (ngx_strncmp(value[i].data, "keys_zone=", 10) == 0) {

name.data = value[i].data + 10;

p = (u_char *) ngx_strchr(name.data, ':');

if (p == NULL) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid keys zone size \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}

name.len = p - name.data;

s.data = p + 1;
s.len = value[i].data + value[i].len - s.data;

size = ngx_parse_size(&s);

if (size == NGX_ERROR) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid keys zone size \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}

if (size < (ssize_t) (2 * ngx_pagesize)) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"keys zone \"%V\" is too small", &value[i]);
return NGX_CONF_ERROR;
}

continue;
}
//解析缓存分片活跃时间
if (ngx_strncmp(value[i].data, "inactive=", 9) == 0) {

s.len = value[i].len - 9;
s.data = value[i].data + 9;

inactive = ngx_parse_time(&s, 1);
if (inactive == (time_t) NGX_ERROR) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid inactive value \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}

continue;
}
//解析配置的目录最大能保存多少缓存分片
if (ngx_strncmp(value[i].data, "max_size=", 9) == 0) {

s.len = value[i].len - 9;
s.data = value[i].data + 9;

max_size = ngx_parse_offset(&s);
if (max_size < 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid max_size value \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}

continue;
}
//解析缓存加载进程暂停阈值
if (ngx_strncmp(value[i].data, "loader_files=", 13) == 0) {

loader_files = ngx_atoi(value[i].data + 13, value[i].len - 13);
if (loader_files == NGX_ERROR) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid loader_files value \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}

continue;
}
//解析缓存加载进程暂停时间
if (ngx_strncmp(value[i].data, "loader_sleep=", 13) == 0) {

s.len = value[i].len - 13;
s.data = value[i].data + 13;

loader_sleep = ngx_parse_time(&s, 0);
if (loader_sleep == (ngx_msec_t) NGX_ERROR) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid loader_sleep value \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}

continue;
}

if (ngx_strncmp(value[i].data, "loader_threshold=", 17) == 0) {

s.len = value[i].len - 17;
s.data = value[i].data + 17;

loader_threshold = ngx_parse_time(&s, 0);
if (loader_threshold == (ngx_msec_t) NGX_ERROR) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid loader_threshold value \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}

continue;
}
//解析缓存管理进程暂停阈值
if (ngx_strncmp(value[i].data, "manager_files=", 14) == 0) {

manager_files = ngx_atoi(value[i].data + 14, value[i].len - 14);
if (manager_files == NGX_ERROR) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid manager_files value \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}

continue;
}

//manager进程达到阈值后的睡眠时间
if (ngx_strncmp(value[i].data, "manager_sleep=", 14) == 0) {

s.len = value[i].len - 14;
s.data = value[i].data + 14;

manager_sleep = ngx_parse_time(&s, 0);
if (manager_sleep == (ngx_msec_t) NGX_ERROR) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid manager_sleep value \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}

continue;
}

if (ngx_strncmp(value[i].data, "manager_threshold=", 18) == 0) {

s.len = value[i].len - 18;
s.data = value[i].data + 18;

manager_threshold = ngx_parse_time(&s, 0);
if (manager_threshold == (ngx_msec_t) NGX_ERROR) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid manager_threshold value \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}

continue;
}

ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid parameter \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}

if (name.len == 0 || size == 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"\"%V\" must have \"keys_zone\" parameter",
&cmd->name);
return NGX_CONF_ERROR;
}

//设置loader跟manager进程的回调函数
cache->path->manager = ngx_http_file_cache_manager;
cache->path->loader = ngx_http_file_cache_loader;
cache->path->data = cache;
cache->path->conf_file = cf->conf_file->file.name.data;
cache->path->line = cf->conf_file->line;
cache->loader_files = loader_files;
cache->loader_sleep = loader_sleep;
cache->loader_threshold = loader_threshold;
cache->manager_files = manager_files;
cache->manager_sleep = manager_sleep;
cache->manager_threshold = manager_threshold;
//将缓存路径添加到全局管理
if (ngx_add_path(cf, &cache->path) != NGX_OK) {
return NGX_CONF_ERROR;
}
//添加共享内存名称
cache->shm_zone = ngx_shared_memory_add(cf, &name, size, cmd->post);
if (cache->shm_zone == NULL) {
return NGX_CONF_ERROR;
}

if (cache->shm_zone->data) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"duplicate zone \"%V\"", &name);
return NGX_CONF_ERROR;
}

//设置共享内存初始化函数指针
cache->shm_zone->init = ngx_http_file_cache_init;
cache->shm_zone->data = cache;

cache->use_temp_path = use_temp_path;

cache->inactive = inactive;
cache->max_size = max_size;

caches = (ngx_array_t *) (confp + cmd->offset);
//保存cache结构体
ce = ngx_array_push(caches);
if (ce == NULL) {
return NGX_CONF_ERROR;
}

*ce = cache;

return NGX_CONF_OK;
}
  1. 上述代码中我们只关注inactive,max_size参数解析、cache->path相关成员赋值以及loader和manager相关的赋值代码,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

cache->path->manager = ngx_http_file_cache_manager;
cache->path->loader = ngx_http_file_cache_loader;
cache->path->data = cache;
cache->path->conf_file = cf->conf_file->file.name.data;
cache->path->line = cf->conf_file->line;
cache->loader_files = loader_files;
cache->loader_sleep = loader_sleep;
cache->loader_threshold = loader_threshold;
cache->manager_files = manager_files;
cache->manager_sleep = manager_sleep;
cache->manager_threshold = manager_threshold;

if (ngx_add_path(cf, &cache->path) != NGX_OK) {
return NGX_CONF_ERROR;
}
//添加共享内存名称
cache->shm_zone = ngx_shared_memory_add(cf, &name, size, cmd->post);
if (cache->shm_zone == NULL) {
return NGX_CONF_ERROR;
}

if (cache->shm_zone->data) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"duplicate zone \"%V\"", &name);
return NGX_CONF_ERROR;
}

//设置共享内存初始化函数
cache->shm_zone->init = ngx_http_file_cache_init;
cache->shm_zone->data = cache;

cache->use_temp_path = use_temp_path;

cache->inactive = inactive;
cache->max_size = max_size;
//保存cache
caches = (ngx_array_t *) (confp + cmd->offset);

ce = ngx_array_push(caches);
if (ce == NULL) {
return NGX_CONF_ERROR;
}

*ce = cache;

  1. path结构体初始化完成之后,ngx_add_path函数将配置的路径添加到nginx全局路径管理器后,配置解析完成之后会统一管理,接下来我们来看看几个跟目录相关的回调函数。
1
2
3
4
5

ngx_http_file_cache_manager
//加载本地已有缓存函数
ngx_http_file_cache_loader

  1. 首先来看看ngx_http_file_cache_manager函数的具体实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

static ngx_msec_t
ngx_http_file_cache_manager(void *data)
{
ngx_http_file_cache_t *cache = data;

off_t size;
time_t wait;
ngx_msec_t elapsed, next;
ngx_uint_t count, watermark;

cache->last = ngx_current_msec;
cache->files = 0;
//查找过期缓存结点
next = (ngx_msec_t) ngx_http_file_cache_expire(cache) * 1000;

if (next == 0) {
next = cache->manager_sleep;
goto done;
}

for ( ;; ) {
ngx_shmtx_lock(&cache->shpool->mutex);

size = cache->sh->size;
count = cache->sh->count;
watermark = cache->sh->watermark;

ngx_shmtx_unlock(&cache->shpool->mutex);

ngx_log_debug3(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
"http file cache size: %O c:%ui w:%i",
size, count, (ngx_int_t) watermark);

if (size < cache->max_size && count < watermark) {
break;
}
//处理缓存目录已经满了的情况
wait = ngx_http_file_cache_forced_expire(cache);

if (wait > 0) {
next = (ngx_msec_t) wait * 1000;
break;
}

if (ngx_quit || ngx_terminate) {
break;
}

if (++cache->files >= cache->manager_files) {
next = cache->manager_sleep;
break;
}

ngx_time_update();

elapsed = ngx_abs((ngx_msec_int_t) (ngx_current_msec - cache->last));

if (elapsed >= cache->manager_threshold) {
next = cache->manager_sleep;
break;
}
}

done:

elapsed = ngx_abs((ngx_msec_int_t) (ngx_current_msec - cache->last));

ngx_log_debug3(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
"http file cache manager: %ui e:%M n:%M",
cache->files, elapsed, next);

return next;
}


  1. 这个函数主要的功能就是遍历缓存结点数,找出过期结点,并删除,接下来看看ngx_http_file_cache_loader函数实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
 
static void
ngx_http_file_cache_loader(void *data)
{
ngx_http_file_cache_t *cache = data;

ngx_tree_ctx_t tree;

if (!cache->sh->cold || cache->sh->loading) {
return;
}

if (!ngx_atomic_cmp_set(&cache->sh->loading, 0, ngx_pid)) {
return;
}

ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
"http file cache loader");
//设置遍历目录时所需的函数指针
tree.init_handler = NULL;
//遍历目录时,遇到文件处理函数
tree.file_handler = ngx_http_file_cache_manage_file;
//遍历目录时,遇到文件处理函数
tree.pre_tree_handler = ngx_http_file_cache_manage_directory;
tree.post_tree_handler = ngx_http_file_cache_noop;
//缓存目录中有不符合规则的缓存文件,会删除文件
tree.spec_handler = ngx_http_file_cache_delete_file;
tree.data = cache;
tree.alloc = 0;
tree.log = ngx_cycle->log;

cache->last = ngx_current_msec;
cache->files = 0;
//遍历配置路径下所有文件
if (ngx_walk_tree(&tree, &cache->path->name) == NGX_ABORT) {
cache->sh->loading = 0;
return;
}

cache->sh->cold = 0;
cache->sh->loading = 0;

ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0,
"http file cache: %V %.3fM, bsize: %uz",
&cache->path->name,
((double) cache->sh->size * cache->bsize) / (1024 * 1024),
cache->bsize);
}

>6. 此函数功能就是绑定回调函数,然后遍历配置的目录,根据目录中不同的对象调用不同的回调函数

3、总结

以上就是nginx缓存管理的主要代码,总的来说就是在解析配置时设置ngx_path_t结构体的回调函数,cache_manager进程定时启动扫描LRU队列,删除过期结点

Nginx的return模块

1 相关配置

return模块配置指令如下:

1
return value;

详细用法参考nginx官方文档

2 源码解析

2.1 首先看看指令解析函数,这个函数的主要功能就是把return指令后面的文本以nginx变量的形式保存,绑定此阶段的处理函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static char *
ngx_stream_return(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_stream_return_srv_conf_t *rscf = conf;

ngx_str_t *value;
ngx_stream_core_srv_conf_t *cscf;
ngx_stream_compile_complex_value_t ccv;

if (rscf->text.value.data) {
return "is duplicate";
}

value = cf->args->elts;

ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t));

ccv.cf = cf;
ccv.value = &value[1];
ccv.complex_value = &rscf->text;

if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) {
return NGX_CONF_ERROR;
}

cscf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_core_module);

cscf->handler = ngx_stream_return_handler;

return NGX_CONF_OK;
}

2.2 在上一步设置好处理函数后,nginx运行到此阶段就会调用ngx_stream_return_handler函数,并给客户端响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

static void
ngx_stream_return_handler(ngx_stream_session_t *s)
{
ngx_str_t text;
ngx_buf_t *b;
ngx_connection_t *c;
ngx_stream_return_ctx_t *ctx;
ngx_stream_return_srv_conf_t *rscf;

c = s->connection;

c->log->action = "returning text";

rscf = ngx_stream_get_module_srv_conf(s, ngx_stream_return_module);

// 获取变量值
if (ngx_stream_complex_value(s, &rscf->text, &text) != NGX_OK) {
ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
return;
}

ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0,
"stream return text: \"%V\"", &text);

if (text.len == 0) {
ngx_stream_finalize_session(s, NGX_STREAM_OK);
return;
}

// 分配上下文内存
ctx = ngx_pcalloc(c->pool, sizeof(ngx_stream_return_ctx_t));
if (ctx == NULL) {
ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
return;
}

// 设置模块上下文
ngx_stream_set_ctx(s, ctx, ngx_stream_return_module);

b = ngx_calloc_buf(c->pool);
if (b == NULL) {
ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
return;
}

b->memory = 1;
b->pos = text.data;
b->last = text.data + text.len;
b->last_buf = 1;

ctx->out = ngx_alloc_chain_link(c->pool);
if (ctx->out == NULL) {
ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
return;
}

// 挂载输出buf
ctx->out->buf = b;
ctx->out->next = NULL;

// 设置写回调函数
c->write->handler = ngx_stream_return_write_handler;

// 调用写回调函数
ngx_stream_return_write_handler(c->write);
}

ngx_stream_return_handler函数首先解析出return指令后面的字符串值,然后为当前模块的ngx_stream_return_ctx_t申请内存,主要用来挂载发送数据的chain,再为发送数据的chain以及buf申请内存,设置写事件的handler,最后发送响应到客户端。

3 总结

return模块属于stream模块,在stream模块中处于第4阶段,所以需要绑定handler函数,将return指令后的变量或者纯文本响应给客户端。

Nginx的geo模块

1、相关配置

geo模块配置指令如下:

1
2
3
4
5

geo $remote_addr $var {

}

详细用法参考nginx官方文档

2、源码详解

2.1 首先来看看geo指令配置解析函数,这个函数的主要功能就是解析geo指令之后的变量,然后再调用ngx_http_geo解析{}中的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
static char *
ngx_http_geo_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
char *rv;
size_t len;
ngx_str_t *value, name;
ngx_uint_t i;
ngx_conf_t save;
ngx_pool_t *pool;
ngx_array_t *a;
ngx_http_variable_t *var;
ngx_http_geo_ctx_t *geo;
ngx_http_geo_conf_ctx_t ctx;
#if (NGX_HAVE_INET6)
static struct in6_addr zero;
#endif

value = cf->args->elts;

geo = ngx_palloc(cf->pool, sizeof(ngx_http_geo_ctx_t));
if (geo == NULL) {
return NGX_CONF_ERROR;
}

name = value[1];

if (name.data[0] != '$') {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid variable name \"%V\"", &name);
return NGX_CONF_ERROR;
}

name.len--;
name.data++;

if (cf->args->nelts == 3) {

geo->index = ngx_http_get_variable_index(cf, &name);
if (geo->index == NGX_ERROR) {
return NGX_CONF_ERROR;
}

name = value[2];

if (name.data[0] != '$') {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid variable name \"%V\"", &name);
return NGX_CONF_ERROR;
}

name.len--;
name.data++;

} else {
geo->index = -1;
}

var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE);
if (var == NULL) {
return NGX_CONF_ERROR;
}

pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log);
if (pool == NULL) {
return NGX_CONF_ERROR;
}

ngx_memzero(&ctx, sizeof(ngx_http_geo_conf_ctx_t));

ctx.temp_pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log);
if (ctx.temp_pool == NULL) {
return NGX_CONF_ERROR;
}

ngx_rbtree_init(&ctx.rbtree, &ctx.sentinel, ngx_str_rbtree_insert_value);

ctx.pool = cf->pool;
ctx.data_size = sizeof(ngx_http_geo_header_t)
+ sizeof(ngx_http_variable_value_t)
+ 0x10000 * sizeof(ngx_http_geo_range_t *);
ctx.allow_binary_include = 1;

save = *cf;
cf->pool = pool;
cf->ctx = &ctx;
cf->handler = ngx_http_geo;
cf->handler_conf = conf;

rv = ngx_conf_parse(cf, NULL);

*cf = save;

geo->proxies = ctx.proxies;
geo->proxy_recursive = ctx.proxy_recursive;

if (ctx.ranges) {

if (ctx.high.low && !ctx.binary_include) {
for (i = 0; i < 0x10000; i++) {
a = (ngx_array_t *) ctx.high.low[i];

if (a == NULL || a->nelts == 0) {
continue;
}

len = a->nelts * sizeof(ngx_http_geo_range_t);

ctx.high.low[i] = ngx_palloc(cf->pool, len + sizeof(void *));
if (ctx.high.low[i] == NULL) {
return NGX_CONF_ERROR;
}

ngx_memcpy(ctx.high.low[i], a->elts, len);
ctx.high.low[i][a->nelts].value = NULL;
ctx.data_size += len + sizeof(void *);
}

if (ctx.allow_binary_include
&& !ctx.outside_entries
&& ctx.entries > 100000
&& ctx.includes == 1)
{
ngx_http_geo_create_binary_base(&ctx);
}
}

if (ctx.high.default_value == NULL) {
ctx.high.default_value = &ngx_http_variable_null_value;
}

geo->u.high = ctx.high;

var->get_handler = ngx_http_geo_range_variable;
var->data = (uintptr_t) geo;

ngx_destroy_pool(ctx.temp_pool);
ngx_destroy_pool(pool);

} else {
if (ctx.tree == NULL) {
ctx.tree = ngx_radix_tree_create(cf->pool, -1);
if (ctx.tree == NULL) {
return NGX_CONF_ERROR;
}
}

geo->u.trees.tree = ctx.tree;

#if (NGX_HAVE_INET6)
if (ctx.tree6 == NULL) {
ctx.tree6 = ngx_radix_tree_create(cf->pool, -1);
if (ctx.tree6 == NULL) {
return NGX_CONF_ERROR;
}
}

geo->u.trees.tree6 = ctx.tree6;
#endif

var->get_handler = ngx_http_geo_cidr_variable;
var->data = (uintptr_t) geo;

ngx_destroy_pool(ctx.temp_pool);
ngx_destroy_pool(pool);

if (ngx_radix32tree_insert(ctx.tree, 0, 0,
(uintptr_t) &ngx_http_variable_null_value)
== NGX_ERROR)
{
return NGX_CONF_ERROR;
}

/* NGX_BUSY is okay (default was set explicitly) */

#if (NGX_HAVE_INET6)
if (ngx_radix128tree_insert(ctx.tree6, zero.s6_addr, zero.s6_addr,
(uintptr_t) &ngx_http_variable_null_value)
== NGX_ERROR)
{
return NGX_CONF_ERROR;
}
#endif
}

return rv;
}

2.2 ngx_http_geo解析{}中的内容,处理ranges等指令和IP地址段或者CIDR地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

static char *
ngx_http_geo(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
{
char *rv;
ngx_str_t *value;
ngx_cidr_t cidr;
ngx_http_geo_conf_ctx_t *ctx;

ctx = cf->ctx;

value = cf->args->elts;

if (cf->args->nelts == 1) {

if (ngx_strcmp(value[0].data, "ranges") == 0) {

if (ctx->tree
#if (NGX_HAVE_INET6)
|| ctx->tree6
#endif
)
{
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"the \"ranges\" directive must be "
"the first directive inside \"geo\" block");
goto failed;
}

ctx->ranges = 1;

rv = NGX_CONF_OK;

goto done;
}

else if (ngx_strcmp(value[0].data, "proxy_recursive") == 0) {
ctx->proxy_recursive = 1;
rv = NGX_CONF_OK;
goto done;
}
}

if (cf->args->nelts != 2) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid number of the geo parameters");
goto failed;
}

if (ngx_strcmp(value[0].data, "include") == 0) {

rv = ngx_http_geo_include(cf, ctx, &value[1]);

goto done;

} else if (ngx_strcmp(value[0].data, "proxy") == 0) {

if (ngx_http_geo_cidr_value(cf, &value[1], &cidr) != NGX_OK) {
goto failed;
}

rv = ngx_http_geo_add_proxy(cf, ctx, &cidr);

goto done;
}

if (ctx->ranges) {
rv = ngx_http_geo_range(cf, ctx, value);

} else {
rv = ngx_http_geo_cidr(cf, ctx, value);
}

done:

ngx_reset_pool(cf->pool);

return rv;

failed:

ngx_reset_pool(cf->pool);

return NGX_CONF_ERROR;
}


2.3 处理range地址,调用ngx_http_geo_range函数实现,接下来看看这个函数的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110

static char *
ngx_http_geo_range(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
ngx_str_t *value)
{
u_char *p, *last;
in_addr_t start, end;
ngx_str_t *net;
ngx_uint_t del;

if (ngx_strcmp(value[0].data, "default") == 0) {

if (ctx->high.default_value) {
ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
"duplicate default geo range value: \"%V\", old value: \"%v\"",
&value[1], ctx->high.default_value);
}

ctx->high.default_value = ngx_http_geo_value(cf, ctx, &value[1]);
if (ctx->high.default_value == NULL) {
return NGX_CONF_ERROR;
}

return NGX_CONF_OK;
}

if (ctx->binary_include) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"binary geo range base \"%s\" cannot be mixed with usual entries",
ctx->include_name.data);
return NGX_CONF_ERROR;
}

if (ctx->high.low == NULL) {
ctx->high.low = ngx_pcalloc(ctx->pool,
0x10000 * sizeof(ngx_http_geo_range_t *));
if (ctx->high.low == NULL) {
return NGX_CONF_ERROR;
}
}

ctx->entries++;
ctx->outside_entries = 1;

if (ngx_strcmp(value[0].data, "delete") == 0) {
net = &value[1];
del = 1;

} else {
net = &value[0];
del = 0;
}

last = net->data + net->len;

p = ngx_strlchr(net->data, last, '-');

if (p == NULL) {
goto invalid;
}

start = ngx_inet_addr(net->data, p - net->data);

if (start == INADDR_NONE) {
goto invalid;
}

start = ntohl(start);

p++;

end = ngx_inet_addr(p, last - p);

if (end == INADDR_NONE) {
goto invalid;
}

end = ntohl(end);

if (start > end) {
goto invalid;
}

if (del) {
if (ngx_http_geo_delete_range(cf, ctx, start, end)) {
ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
"no address range \"%V\" to delete", net);
}

return NGX_CONF_OK;
}

ctx->value = ngx_http_geo_value(cf, ctx, &value[1]);

if (ctx->value == NULL) {
return NGX_CONF_ERROR;
}

ctx->net = net;

return ngx_http_geo_add_range(cf, ctx, start, end);

invalid:

ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid range \"%V\"", net);

return NGX_CONF_ERROR;
}


2.4 处理CIDR地址,调用ngx_http_geo_cidr函数实现,接下来看看这个函数的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97

static char *
ngx_http_geo_cidr(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx,
ngx_str_t *value)
{
char *rv;
ngx_int_t rc, del;
ngx_str_t *net;
ngx_cidr_t cidr;

if (ctx->tree == NULL) {
ctx->tree = ngx_radix_tree_create(ctx->pool, -1);
if (ctx->tree == NULL) {
return NGX_CONF_ERROR;
}
}

#if (NGX_HAVE_INET6)
if (ctx->tree6 == NULL) {
ctx->tree6 = ngx_radix_tree_create(ctx->pool, -1);
if (ctx->tree6 == NULL) {
return NGX_CONF_ERROR;
}
}
#endif

if (ngx_strcmp(value[0].data, "default") == 0) {
cidr.family = AF_INET;
cidr.u.in.addr = 0;
cidr.u.in.mask = 0;

rv = ngx_http_geo_cidr_add(cf, ctx, &cidr, &value[1], &value[0]);

if (rv != NGX_CONF_OK) {
return rv;
}

#if (NGX_HAVE_INET6)
cidr.family = AF_INET6;
ngx_memzero(&cidr.u.in6, sizeof(ngx_in6_cidr_t));

rv = ngx_http_geo_cidr_add(cf, ctx, &cidr, &value[1], &value[0]);

if (rv != NGX_CONF_OK) {
return rv;
}
#endif

return NGX_CONF_OK;
}

if (ngx_strcmp(value[0].data, "delete") == 0) {
net = &value[1];
del = 1;

} else {
net = &value[0];
del = 0;
}

if (ngx_http_geo_cidr_value(cf, net, &cidr) != NGX_OK) {
return NGX_CONF_ERROR;
}

if (cidr.family == AF_INET) {
cidr.u.in.addr = ntohl(cidr.u.in.addr);
cidr.u.in.mask = ntohl(cidr.u.in.mask);
}

if (del) {
switch (cidr.family) {

#if (NGX_HAVE_INET6)
case AF_INET6:
rc = ngx_radix128tree_delete(ctx->tree6,
cidr.u.in6.addr.s6_addr,
cidr.u.in6.mask.s6_addr);
break;
#endif

default: /* AF_INET */
rc = ngx_radix32tree_delete(ctx->tree, cidr.u.in.addr,
cidr.u.in.mask);
break;
}

if (rc != NGX_OK) {
ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
"no network \"%V\" to delete", net);
}

return NGX_CONF_OK;
}

return ngx_http_geo_cidr_add(cf, ctx, &cidr, &value[1], net);
}

2.5 获取解析好的变量值,调用ngx_http_geo_range_variable函数实现,接下来看看这个函数的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

static ngx_int_t
ngx_http_geo_range_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
uintptr_t data)
{
ngx_http_geo_ctx_t *ctx = (ngx_http_geo_ctx_t *) data;

in_addr_t inaddr;
ngx_addr_t addr;
ngx_uint_t n;
struct sockaddr_in *sin;
ngx_http_geo_range_t *range;
#if (NGX_HAVE_INET6)
u_char *p;
struct in6_addr *inaddr6;
#endif

*v = *ctx->u.high.default_value;

if (ngx_http_geo_addr(r, ctx, &addr) == NGX_OK) {

switch (addr.sockaddr->sa_family) {

#if (NGX_HAVE_INET6)
case AF_INET6:
inaddr6 = &((struct sockaddr_in6 *) addr.sockaddr)->sin6_addr;

if (IN6_IS_ADDR_V4MAPPED(inaddr6)) {
p = inaddr6->s6_addr;

inaddr = p[12] << 24;
inaddr += p[13] << 16;
inaddr += p[14] << 8;
inaddr += p[15];

} else {
inaddr = INADDR_NONE;
}

break;
#endif

#if (NGX_HAVE_UNIX_DOMAIN)
case AF_UNIX:
inaddr = INADDR_NONE;
break;
#endif

default: /* AF_INET */
sin = (struct sockaddr_in *) addr.sockaddr;
inaddr = ntohl(sin->sin_addr.s_addr);
break;
}

} else {
inaddr = INADDR_NONE;
}

if (ctx->u.high.low) {
range = ctx->u.high.low[inaddr >> 16];

if (range) {
n = inaddr & 0xffff;
do {
if (n >= (ngx_uint_t) range->start
&& n <= (ngx_uint_t) range->end)
{
*v = *range->value;
break;
}
} while ((++range)->value);
}
}

ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http geo: %v", v);

return NGX_OK;
}


3、总结

以上就是整个geo模块的核心,stream下的geo模块实现方式类似。s

Nginx的map模块

1、相关配置

geo模块配置指令如下:

1
2
3
4
5

map $var1 $var2 {

}

详细用法参考nginx官方文档,此模块的功能是将$var1通过{}中的匹配条件生成$var2的值,支持正则表达式。

2、代码详解

2.1、首先来看看配置解析函数,负责解析map指令后的两个变量,并设置获取变量值的函数指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199

static char *
ngx_http_map_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_map_conf_t *mcf = conf;

char *rv;
ngx_str_t *value, name;
ngx_conf_t save;
ngx_pool_t *pool;
ngx_hash_init_t hash;
ngx_http_map_ctx_t *map;
ngx_http_variable_t *var;
ngx_http_map_conf_ctx_t ctx;
ngx_http_compile_complex_value_t ccv;
//设置hash桶最大值
if (mcf->hash_max_size == NGX_CONF_UNSET_UINT) {
mcf->hash_max_size = 2048;
}

if (mcf->hash_bucket_size == NGX_CONF_UNSET_UINT) {
mcf->hash_bucket_size = ngx_cacheline_size;

} else {
mcf->hash_bucket_size = ngx_align(mcf->hash_bucket_size,
ngx_cacheline_size);
}

map = ngx_pcalloc(cf->pool, sizeof(ngx_http_map_ctx_t));
if (map == NULL) {
return NGX_CONF_ERROR;
}

value = cf->args->elts;

ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

ccv.cf = cf;
ccv.value = &value[1];
ccv.complex_value = &map->value;

if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
return NGX_CONF_ERROR;
}

//解析第二个变量
name = value[2];

if (name.data[0] != '$') {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid variable name \"%V\"", &name);
return NGX_CONF_ERROR;
}

//保存$符号后面的字符串
name.len--;
name.data++;

// 把变量名添加到全局变量中
var = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE);
if (var == NULL) {
return NGX_CONF_ERROR;
}

//设置获取此变量值的handler
var->get_handler = ngx_http_map_variable;
var->data = (uintptr_t) map;

pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log);
if (pool == NULL) {
return NGX_CONF_ERROR;
}

ctx.keys.pool = cf->pool;
ctx.keys.temp_pool = pool;

if (ngx_hash_keys_array_init(&ctx.keys, NGX_HASH_LARGE) != NGX_OK) {
ngx_destroy_pool(pool);
return NGX_CONF_ERROR;
}

ctx.values_hash = ngx_pcalloc(pool, sizeof(ngx_array_t) * ctx.keys.hsize);
if (ctx.values_hash == NULL) {
ngx_destroy_pool(pool);
return NGX_CONF_ERROR;
}

// 初始化正则表达式
#if (NGX_PCRE)
if (ngx_array_init(&ctx.regexes, cf->pool, 2, sizeof(ngx_http_map_regex_t))
!= NGX_OK)
{
ngx_destroy_pool(pool);
return NGX_CONF_ERROR;
}
#endif

ctx.default_value = NULL;
ctx.cf = &save;
ctx.hostnames = 0;
ctx.no_cacheable = 0;

save = *cf;
cf->pool = pool;
cf->ctx = &ctx;
cf->handler = ngx_http_map;
cf->handler_conf = conf;

rv = ngx_conf_parse(cf, NULL);

*cf = save;

if (rv != NGX_CONF_OK) {
ngx_destroy_pool(pool);
return rv;
}

if (ctx.no_cacheable) {
var->flags |= NGX_HTTP_VAR_NOCACHEABLE;
}

map->default_value = ctx.default_value ? ctx.default_value:
&ngx_http_variable_null_value;

map->hostnames = ctx.hostnames;

hash.key = ngx_hash_key_lc;
hash.max_size = mcf->hash_max_size;
hash.bucket_size = mcf->hash_bucket_size;
hash.name = "map_hash";
hash.pool = cf->pool;

if (ctx.keys.keys.nelts) {
hash.hash = &map->map.hash.hash;
hash.temp_pool = NULL;

if (ngx_hash_init(&hash, ctx.keys.keys.elts, ctx.keys.keys.nelts)
!= NGX_OK)
{
ngx_destroy_pool(pool);
return NGX_CONF_ERROR;
}
}

if (ctx.keys.dns_wc_head.nelts) {

ngx_qsort(ctx.keys.dns_wc_head.elts,
(size_t) ctx.keys.dns_wc_head.nelts,
sizeof(ngx_hash_key_t), ngx_http_map_cmp_dns_wildcards);

hash.hash = NULL;
hash.temp_pool = pool;

if (ngx_hash_wildcard_init(&hash, ctx.keys.dns_wc_head.elts,
ctx.keys.dns_wc_head.nelts)
!= NGX_OK)
{
ngx_destroy_pool(pool);
return NGX_CONF_ERROR;
}

map->map.hash.wc_head = (ngx_hash_wildcard_t *) hash.hash;
}

if (ctx.keys.dns_wc_tail.nelts) {

ngx_qsort(ctx.keys.dns_wc_tail.elts,
(size_t) ctx.keys.dns_wc_tail.nelts,
sizeof(ngx_hash_key_t), ngx_http_map_cmp_dns_wildcards);

hash.hash = NULL;
hash.temp_pool = pool;

if (ngx_hash_wildcard_init(&hash, ctx.keys.dns_wc_tail.elts,
ctx.keys.dns_wc_tail.nelts)
!= NGX_OK)
{
ngx_destroy_pool(pool);
return NGX_CONF_ERROR;
}

map->map.hash.wc_tail = (ngx_hash_wildcard_t *) hash.hash;
}

#if (NGX_PCRE)

if (ctx.regexes.nelts) {
map->map.regex = ctx.regexes.elts;
map->map.nregex = ctx.regexes.nelts;
}

#endif

ngx_destroy_pool(pool);

return rv;
}


2.2、解析map块中的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
static char *
ngx_http_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
{
u_char *data;
size_t len;
ngx_int_t rv;
ngx_str_t *value, v;
ngx_uint_t i, key;
ngx_http_map_conf_ctx_t *ctx;
ngx_http_complex_value_t cv, *cvp;
ngx_http_variable_value_t *var, **vp;
ngx_http_compile_complex_value_t ccv;

ctx = cf->ctx;

value = cf->args->elts;

if (cf->args->nelts == 1
&& ngx_strcmp(value[0].data, "hostnames") == 0)
{
ctx->hostnames = 1;
return NGX_CONF_OK;
}

if (cf->args->nelts == 1
&& ngx_strcmp(value[0].data, "volatile") == 0)
{
ctx->no_cacheable = 1;
return NGX_CONF_OK;
}

if (cf->args->nelts != 2) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid number of the map parameters");
return NGX_CONF_ERROR;
}

if (ngx_strcmp(value[0].data, "include") == 0) {
return ngx_conf_include(cf, dummy, conf);
}

key = 0;

for (i = 0; i < value[1].len; i++) {
key = ngx_hash(key, value[1].data[i]);
}

key %= ctx->keys.hsize;

vp = ctx->values_hash[key].elts;

if (vp) {
for (i = 0; i < ctx->values_hash[key].nelts; i++) {

if (vp[i]->valid) {
data = vp[i]->data;
len = vp[i]->len;

} else {
cvp = (ngx_http_complex_value_t *) vp[i]->data;
data = cvp->value.data;
len = cvp->value.len;
}

if (value[1].len != len) {
continue;
}

if (ngx_strncmp(value[1].data, data, len) == 0) {
var = vp[i];
goto found;
}
}

} else {
if (ngx_array_init(&ctx->values_hash[key], cf->pool, 4,
sizeof(ngx_http_variable_value_t *))
!= NGX_OK)
{
return NGX_CONF_ERROR;
}
}

var = ngx_palloc(ctx->keys.pool, sizeof(ngx_http_variable_value_t));
if (var == NULL) {
return NGX_CONF_ERROR;
}

v.len = value[1].len;
v.data = ngx_pstrdup(ctx->keys.pool, &value[1]);
if (v.data == NULL) {
return NGX_CONF_ERROR;
}

ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

ccv.cf = ctx->cf;
ccv.value = &v;
ccv.complex_value = &cv;

if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
return NGX_CONF_ERROR;
}

if (cv.lengths != NULL) {
cvp = ngx_palloc(ctx->keys.pool, sizeof(ngx_http_complex_value_t));
if (cvp == NULL) {
return NGX_CONF_ERROR;
}

*cvp = cv;

var->len = 0;
var->data = (u_char *) cvp;
var->valid = 0;

} else {
var->len = v.len;
var->data = v.data;
var->valid = 1;
}

var->no_cacheable = 0;
var->not_found = 0;

vp = ngx_array_push(&ctx->values_hash[key]);
if (vp == NULL) {
return NGX_CONF_ERROR;
}

*vp = var;

found:

if (ngx_strcmp(value[0].data, "default") == 0) {

if (ctx->default_value) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"duplicate default map parameter");
return NGX_CONF_ERROR;
}

ctx->default_value = var;

return NGX_CONF_OK;
}

#if (NGX_PCRE)

if (value[0].len && value[0].data[0] == '~') {
ngx_regex_compile_t rc;
ngx_http_map_regex_t *regex;
u_char errstr[NGX_MAX_CONF_ERRSTR];

regex = ngx_array_push(&ctx->regexes);
if (regex == NULL) {
return NGX_CONF_ERROR;
}

value[0].len--;
value[0].data++;

ngx_memzero(&rc, sizeof(ngx_regex_compile_t));

if (value[0].data[0] == '*') {
value[0].len--;
value[0].data++;
rc.options = NGX_REGEX_CASELESS;
}

rc.pattern = value[0];
rc.err.len = NGX_MAX_CONF_ERRSTR;
rc.err.data = errstr;

regex->regex = ngx_http_regex_compile(ctx->cf, &rc);
if (regex->regex == NULL) {
return NGX_CONF_ERROR;
}

regex->value = var;

return NGX_CONF_OK;
}

#endif

if (value[0].len && value[0].data[0] == '\\') {
value[0].len--;
value[0].data++;
}

rv = ngx_hash_add_key(&ctx->keys, &value[0], var,
(ctx->hostnames) ? NGX_HASH_WILDCARD_KEY : 0);

if (rv == NGX_OK) {
return NGX_CONF_OK;
}

if (rv == NGX_DECLINED) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid hostname or wildcard \"%V\"", &value[0]);
}

if (rv == NGX_BUSY) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"conflicting parameter \"%V\"", &value[0]);
}

return NGX_CONF_ERROR;
}s

3、总结

map模块生成的变量遵循nginx的变量规则,获取变量值的方式也与nginx其他变量类似

Nginx变量详解

1、源码解析

1.1 变量分类

nginx变量分为两种,一种是nginx内置变量,可以通过官方网站查询到的含义,另外一种则是在nginx配置文件中通过set指令设置的变量,首先来看一下nginx的内置变量实现方式。

1.1.1 nginx内置变量

首先看看定义内置变量的结构体

1
2
3
4
5
6
7
8
struct ngx_http_variable_s {
ngx_str_t name; /* must be first to build the hash */
ngx_http_set_variable_pt set_handler;
ngx_http_get_variable_pt get_handler;
uintptr_t data;
ngx_uint_t flags;
ngx_uint_t index;
};

上面这个结构体中,name代表变量名称,比如$request_uri,$args等,set_handler暂时没有使用到,get_handler获取变量值,data一般表示为变量值来自于这里,flags表示此变量遵循的一些规则,比如NGX_HTTP_VAR_CHANGEABLE,index表示此变量在整个nginx变量系统中的索引。

接下来就是将变量添加到nginx的变量系统中了,这里以http模块的核心变量为例进行说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
ngx_http_variable_t *
ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags)
{
ngx_int_t rc;
ngx_uint_t i;
ngx_hash_key_t *key;
ngx_http_variable_t *v;
ngx_http_core_main_conf_t *cmcf;

if (name->len == 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid variable name \"$\"");
return NULL;
}

// 前缀变量,例如http_,arg_ 等
if (flags & NGX_HTTP_VAR_PREFIX) {
return ngx_http_add_prefix_variable(cf, name, flags);
}

cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

key = cmcf->variables_keys->keys.elts;
for (i = 0; i < cmcf->variables_keys->keys.nelts; i++) {
if (name->len != key[i].key.len
|| ngx_strncasecmp(name->data, key[i].key.data, name->len) != 0)
{
continue;
}

v = key[i].value;

if (!(v->flags & NGX_HTTP_VAR_CHANGEABLE)) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"the duplicate \"%V\" variable", name);
return NULL;
}

if (!(flags & NGX_HTTP_VAR_WEAK)) {
v->flags &= ~NGX_HTTP_VAR_WEAK;
}

return v;
}

v = ngx_palloc(cf->pool, sizeof(ngx_http_variable_t));
if (v == NULL) {
return NULL;
}

v->name.len = name->len;
v->name.data = ngx_pnalloc(cf->pool, name->len);
if (v->name.data == NULL) {
return NULL;
}

ngx_strlow(v->name.data, name->data, name->len);

v->set_handler = NULL;
v->get_handler = NULL;
v->data = 0;
v->flags = flags;
v->index = 0;

rc = ngx_hash_add_key(cmcf->variables_keys, &v->name, v, 0);

if (rc == NGX_ERROR) {
return NULL;
}

if (rc == NGX_BUSY) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"conflicting variable name \"%V\"", name);
return NULL;
}

return v;
}

这个函数的作用就是将定义的变量添加到http核心配置中,为获取变量的值做好准备,其他变量类似。接下来就是如何获取变量值了,http的日志模块提供了一个很好的范本。
接下来看看ngx_http_variables_init_vars函数的实现,这个函数的主要功能是设置变量的get_handler,从各个模块初始化的变量key中寻找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
ngx_int_t
ngx_http_variables_init_vars(ngx_conf_t *cf)
{
size_t len;
ngx_uint_t i, n;
ngx_hash_key_t *key;
ngx_hash_init_t hash;
ngx_http_variable_t *v, *av, *pv;
ngx_http_core_main_conf_t *cmcf;

/* set the handlers for the indexed http variables */

cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

v = cmcf->variables.elts;
pv = cmcf->prefix_variables.elts;
key = cmcf->variables_keys->keys.elts;

// cmcf->variable变量是配置文件中使用的变量
for (i = 0; i < cmcf->variables.nelts; i++) {

// 这个key是所有模块定义的变量key
for (n = 0; n < cmcf->variables_keys->keys.nelts; n++) {

av = key[n].value;

if (v[i].name.len == key[n].key.len
&& ngx_strncmp(v[i].name.data, key[n].key.data, v[i].name.len)
== 0)
{
// 设置get_handler
v[i].get_handler = av->get_handler;
v[i].data = av->data;

av->flags |= NGX_HTTP_VAR_INDEXED;
v[i].flags = av->flags;

av->index = i;

if (av->get_handler == NULL
|| (av->flags & NGX_HTTP_VAR_WEAK))
{
break;
}

goto next;
}
}

len = 0;
av = NULL;

// 设置前缀变量get_handler
for (n = 0; n < cmcf->prefix_variables.nelts; n++) {
if (v[i].name.len >= pv[n].name.len && v[i].name.len > len
&& ngx_strncmp(v[i].name.data, pv[n].name.data, pv[n].name.len)
== 0)
{
av = &pv[n];
len = pv[n].name.len;
}
}

if (av) {
v[i].get_handler = av->get_handler;
v[i].data = (uintptr_t) &v[i].name;
v[i].flags = av->flags;

goto next;
}

// 如果变量get_handler为NULL,报错
if (v[i].get_handler == NULL) {
ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
"unknown \"%V\" variable", &v[i].name);

return NGX_ERROR;
}

next:
continue;
}

// 如果带有nohash的标识,则将key.data置为NULL,在ngx_hash_init就不会将该key放在hash桶里面,比如proxy模块的几个变量
for (n = 0; n < cmcf->variables_keys->keys.nelts; n++) {
av = key[n].value;

if (av->flags & NGX_HTTP_VAR_NOHASH) {
key[n].key.data = NULL;
}
}


hash.hash = &cmcf->variables_hash;
hash.key = ngx_hash_key;
hash.max_size = cmcf->variables_hash_max_size;
hash.bucket_size = cmcf->variables_hash_bucket_size;
hash.name = "variables_hash";
hash.pool = cf->pool;
hash.temp_pool = NULL;

// 开始初始化变量hash表
if (ngx_hash_init(&hash, cmcf->variables_keys->keys.elts,
cmcf->variables_keys->keys.nelts)
!= NGX_OK)
{
return NGX_ERROR;
}

cmcf->variables_keys = NULL;

return NGX_OK;
}

http模块如何获取变量,接下来看看ngx_http_get_variable函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
ngx_http_variable_value_t *
ngx_http_get_variable(ngx_http_request_t *r, ngx_str_t *name, ngx_uint_t key)
{
size_t len;
ngx_uint_t i, n;
ngx_http_variable_t *v;
ngx_http_variable_value_t *vv;
ngx_http_core_main_conf_t *cmcf;

cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
// 从全局变量hash表中根据key找到对应变量
v = ngx_hash_find(&cmcf->variables_hash, key, name->data, name->len);

if (v) {
if (v->flags & NGX_HTTP_VAR_INDEXED) {
return ngx_http_get_flushed_variable(r, v->index);
}

if (ngx_http_variable_depth == 0) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"cycle while evaluating variable \"%V\"", name);
return NULL;
}

ngx_http_variable_depth--;

vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t));

if (vv && v->get_handler(r, vv, v->data) == NGX_OK) {
ngx_http_variable_depth++;
return vv;
}

ngx_http_variable_depth++;
return NULL;
}

vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t));
if (vv == NULL) {
return NULL;
}

len = 0;

v = cmcf->prefix_variables.elts;
n = cmcf->prefix_variables.nelts;

// 从前缀变量中查找
for (i = 0; i < cmcf->prefix_variables.nelts; i++) {
if (name->len >= v[i].name.len && name->len > len
&& ngx_strncmp(name->data, v[i].name.data, v[i].name.len) == 0)
{
len = v[i].name.len;
n = i;
}
}

// 从前缀变量中找到该变量
if (n != cmcf->prefix_variables.nelts) {
if (v[n].get_handler(r, vv, (uintptr_t) name) == NGX_OK) {
return vv;
}

return NULL;
}

vv->not_found = 1;

return vv;
}

1.1.2 配置文件变量

配置文件变量是指set指令设置的变量,

1
set $a value;

具体可以参考set

首先来看看set指令解析函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
static char *
ngx_http_rewrite_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_rewrite_loc_conf_t *lcf = conf;

ngx_int_t index;
ngx_str_t *value;
ngx_http_variable_t *v;
ngx_http_script_var_code_t *vcode;
ngx_http_script_var_handler_code_t *vhcode;

value = cf->args->elts;

// 如果没有带$符号,则返回错误
if (value[1].data[0] != '$') {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid variable name \"%V\"", &value[1]);
return NGX_CONF_ERROR;
}

value[1].len--;
value[1].data++;

// 将变量添加到全局变量中
v = ngx_http_add_variable(cf, &value[1],
NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_WEAK);
if (v == NULL) {
return NGX_CONF_ERROR;
}

// 获取变量索引
index = ngx_http_get_variable_index(cf, &value[1]);
if (index == NGX_ERROR) {
return NGX_CONF_ERROR;
}

if (v->get_handler == NULL) {
v->get_handler = ngx_http_rewrite_var;
v->data = index;
}

if (ngx_http_rewrite_value(cf, lcf, &value[2]) != NGX_CONF_OK) {
return NGX_CONF_ERROR;
}

//处理set_handler
if (v->set_handler) {
vhcode = ngx_http_script_start_code(cf->pool, &lcf->codes,
sizeof(ngx_http_script_var_handler_code_t));
if (vhcode == NULL) {
return NGX_CONF_ERROR;
}

vhcode->code = ngx_http_script_var_set_handler_code;
vhcode->handler = v->set_handler;
vhcode->data = v->data;

return NGX_CONF_OK;
}

vcode = ngx_http_script_start_code(cf->pool, &lcf->codes,
sizeof(ngx_http_script_var_code_t));
if (vcode == NULL) {
return NGX_CONF_ERROR;
}

vcode->code = ngx_http_script_set_var_code;
vcode->index = (uintptr_t) index;

return NGX_CONF_OK;
}

这个函数首先解析出第一个变量的名字,并将其加入到nginx的变量系统中,其次解析作为第一个值的变量(第二个变量),但是第二个变量可能是一个普通的字符串也有可能是多个变量组成的字符串,都是在ngx_http_rewrite_value函数中进行处理,接下来看看ngx_http_rewrite_value函数实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

static char *
ngx_http_rewrite_value(ngx_conf_t *cf, ngx_http_rewrite_loc_conf_t *lcf,
ngx_str_t *value)
{
ngx_int_t n;
ngx_http_script_compile_t sc;
ngx_http_script_value_code_t *val;
ngx_http_script_complex_value_code_t *complex;

n = ngx_http_script_variables_count(value);

if (n == 0) {
val = ngx_http_script_start_code(cf->pool, &lcf->codes,
sizeof(ngx_http_script_value_code_t));
if (val == NULL) {
return NGX_CONF_ERROR;
}

n = ngx_atoi(value->data, value->len);

if (n == NGX_ERROR) {
n = 0;
}

val->code = ngx_http_script_value_code;
val->value = (uintptr_t) n;
val->text_len = (uintptr_t) value->len;
val->text_data = (uintptr_t) value->data;

return NGX_CONF_OK;
}
//带有$的变量,value也是变量,如set $aa $bb,这里的$bb就是变量,
complex = ngx_http_script_start_code(cf->pool, &lcf->codes,
sizeof(ngx_http_script_complex_value_code_t));
if (complex == NULL) {
return NGX_CONF_ERROR;
}

complex->code = ngx_http_script_complex_value_code;
complex->lengths = NULL;

ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));

sc.cf = cf;
sc.source = value;
sc.lengths = &complex->lengths;
sc.values = &lcf->codes;
sc.variables = n;
sc.complete_lengths = 1;

if (ngx_http_script_compile(&sc) != NGX_OK) {
return NGX_CONF_ERROR;
}

return NGX_CONF_OK;
}

这个函数中,n为0表示value不包含变量,为纯字符串,否则表示value中包含变量,采用复杂变量解析方式。

2、总结

以上就是Nginx变量的相关解释,Nginx变量在nginx.conf配置文件中有着至关重要的作用,可定制性强。