nginx是個(gè)優(yōu)秀的web server,然而對(duì)nginx源碼進(jìn)行深入分析的文章并不多。末尾的參考文獻(xiàn)列表中列出在網(wǎng)上找到的文章,在此補(bǔ)充一些自己的學(xué)習(xí)心得,與這些文章形成互補(bǔ)。
對(duì)于純C寫成的服務(wù)器程序,基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)都是必備的。nginx的內(nèi)存分配都是在自己實(shí)現(xiàn)的內(nèi)存池(在ngx_palloc.c/.h文件中)的基礎(chǔ)上, 具體實(shí)現(xiàn)原理參見[4]。nginx的內(nèi)存池是按需分配,統(tǒng)一釋放,也就是在一個(gè)pool的生命周期內(nèi),已分配的內(nèi)存空間不會(huì)釋放(大塊空間除外,超過一個(gè)pagesize為大塊空間),也就是說內(nèi)存使用量一直在增長,直到pool銷毀。因此,小塊內(nèi)存分配后,不需要free操作,減少了內(nèi)存泄露的機(jī)會(huì)。
nginx還有很多地方的設(shè)計(jì)思路都是用空間換時(shí)間,犧牲一點(diǎn)內(nèi)存使用量換取更高的時(shí)間效率。
1. ngx_str_t 字符串結(jié)構(gòu)
ngx_string.c/.h中定義了字符串操作函數(shù)。
typedef struct {
size_t len;
u_char *data;
} ngx_str_t;
#define ngx_string(str) { sizeof(str) - 1, (u_char *) str } //定義常量字符串
2. ngx_array_t
struct ngx_array_s {
void *elts; //數(shù)據(jù)塊
ngx_uint_t nelts; //有效的項(xiàng)數(shù)
size_t size; //單位數(shù)據(jù)的size
ngx_uint_t nalloc; //已分配的項(xiàng)數(shù)
ngx_pool_t *pool; //所屬緩沖池
};
void *
ngx_array_push(ngx_array_t *a)
{
void *elt, *new;
size_t size;
ngx_pool_t *p;
if (a->nelts == a->nalloc) {
/* the array is full */
size = a->size * a->nalloc;
p = a->pool;
if ((u_char *) a->elts + size == p->d.last
&& p->d.last + a->size <= p->d.end)
{
/*
* the array allocation is the last in the pool
* and there is space for new allocation
*/
p->d.last += a->size;
a->nalloc++;
} else {
/* allocate a new array */
new = ngx_palloc(p, 2 * size);
if (new == NULL) {
return NULL;
}
ngx_memcpy(new, a->elts, size);
a->elts = new;
a->nalloc *= 2;
}
}
elt = (u_char *) a->elts + a->size * a->nelts;
a->nelts++;
return elt;
}
ngx_array_push返回一個(gè)指針指向數(shù)組中可插入數(shù)據(jù)的有效位置,調(diào)用者把該指針轉(zhuǎn)成對(duì)應(yīng)的結(jié)構(gòu)體指針,然后賦值便完成插入。nginx的其他數(shù)據(jù)結(jié)構(gòu)使用都遵循這個(gè)方式。記得以前自己實(shí)現(xiàn)的動(dòng)態(tài)數(shù)組,插入元素是先建立一個(gè)臨時(shí)結(jié)構(gòu)體變量,對(duì)結(jié)構(gòu)體賦值,然后push_back到數(shù)組中,這樣數(shù)據(jù)就復(fù)制了兩次,不優(yōu)雅且低效。
該數(shù)組沒有實(shí)現(xiàn)元素刪除的接口。在nginx的模塊中一般沒有此需要。
PS: nginx的變量名elt, 我猜是element的縮寫吧,很多變量都用此命名,表示數(shù)據(jù)項(xiàng)。
3. ngx_list_t 鏈表
單鏈表
struct ngx_list_part_s {
void *elts; //數(shù)據(jù)
ngx_uint_t nelts; //有效的數(shù)據(jù)項(xiàng)數(shù)
ngx_list_part_t *next; //next指針(next part)
};
typedef struct {
ngx_list_part_t *last; //最后一個(gè)part
ngx_list_part_t part;
size_t size; //單位數(shù)據(jù)項(xiàng)大小
ngx_uint_t nalloc; //已分配的項(xiàng)目
ngx_pool_t *pool;
} ngx_list_t;
該鏈表也只實(shí)現(xiàn)了插入接口,沒有實(shí)現(xiàn)刪除(需在外部自行實(shí)現(xiàn))。nginx每個(gè)鏈表項(xiàng)是一個(gè)part(ngx_list_part_t), 每個(gè)part可以容納若干個(gè)數(shù)據(jù)。因此,遍歷鏈表的代碼就變成:
/*
*
* the iteration through the list:
*
* part = &list.part;
* data = part->elts;
*
* for (i = 0 ;; i++) {
*
* if (i >= part->nelts) {
* if (part->next == NULL) {
* break;
* }
*
* part = part->next;
* data = part->elts;
* i = 0;
* }
*
* 訪問
data[i] 
*
* }
*/
4. ngx_buf_t.
buffer的結(jié)構(gòu)參見文獻(xiàn)[4]. 有幾個(gè)指針用于管理buffer, 用于發(fā)送/接收緩沖管理。
+----------已分配的內(nèi)存----------------+
+----已操作-----+---待操作----+--------+
| | | |
start pos last end
5. ngx_queue_t, ngx_rbtree_t
隊(duì)列和紅黑樹,都是經(jīng)典的實(shí)現(xiàn)方式,沒什么好講的。紅黑樹實(shí)現(xiàn)參見本博客另一篇文章。
6. ngx_hash_t
hash函數(shù)
key = 0;
for (i = 0; i < len; i++) {
key = key*31 + data[i];
}
typedef struct {
void *value;
u_char len; //指定name的長度
u_char name[1]; //配對(duì)關(guān)鍵字, 不定長度.
} ngx_hash_elt_t;
typedef struct {
ngx_hash_elt_t **buckets;
ngx_uint_t size;
} ngx_hash_t;
void *
ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len)
{
ngx_uint_t i;
ngx_hash_elt_t *elt;
elt = hash->buckets[key % hash->size];
if (elt == NULL) {
return NULL;
}
while (elt->value) {
if (len != (size_t) elt->len) {
goto next;
}
for (i = 0; i < len; i++) {
if (name[i] != elt->name[i]) {
goto next;
}
}
return elt->value;
next:
elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,
sizeof(void *));
continue;
}
return NULL;
}
參考文獻(xiàn)
[1]
啃餅的博客. 對(duì)nginx有一系列的分析文章 [2] Joshua Zhu在廣州技術(shù)沙龍的《nginx源碼分析》交流。有PPT和視頻. http://blog.zhuzhaoyuan.com/
[3] nginx的分析文章, 鎖,內(nèi)存管理,進(jìn)程模型等. http://simohayha.javaeye.com/category/101180
[4] nginx的內(nèi)存管理. http://simohayha.javaeye.com/blog/545192