Nginx源码学习(1) ---- 基础数据结构
nginx是个优秀的web server,然而对nginx源码进行深入分析的文章并不多。末尾的参考文献列表中列出在网上找到的文章,在此补充一些自己的学习心得,与这些文章形成互补。对于纯C写成的服务器程序,基础数据结构都是必备的。nginx的内存分配都是在自己实现的内存池(在ngx_palloc.c/.h文件中)的基础上, 具体实现原理参见[4]。nginx的内存池是按需分配,统一释放,也就是在一个pool的生命周期内,已分配的内存空间不会释放(大块空间除外,超过一个pagesize为大块空间),也就是说内存使用量一直在增长,直到pool销毁。因此,小块内存分配后,不需要free操作,减少了内存泄露的机会。
nginx还有很多地方的设计思路都是用空间换时间,牺牲一点内存使用量换取更高的时间效率。
1. ngx_str_t 字符串结构
ngx_string.c/.h中定义了字符串操作函数。
typedef
struct
{
size_t len;
u_char * data;
} ngx_str_t;
#define ngx_string(str) { sizeof(str) - 1, (u_char *) str } //定义常量字符串
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; // 数据块
ngx_uint_t nelts; // 有效的项数
size_t size; // 单位数据的size
ngx_uint_t nalloc; // 已分配的项数
ngx_pool_t * pool; // 所属缓冲池
};
void * elts; // 数据块
ngx_uint_t nelts; // 有效的项数
size_t size; // 单位数据的size
ngx_uint_t nalloc; // 已分配的项数
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(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返回一个指针指向数组中可插入数据的有效位置,调用者把该指针转成对应的结构体指针,然后赋值便完成插入。nginx的其他数据结构使用都遵循这个方式。记得以前自己实现的动态数组,插入元素是先建立一个临时结构体变量,对结构体赋值,然后push_back到数组中,这样数据就复制了两次,不优雅且低效。
该数组没有实现元素删除的接口。在nginx的模块中一般没有此需要。
PS: nginx的变量名elt, 我猜是element的缩写吧,很多变量都用此命名,表示数据项。
3. ngx_list_t 链表
单链表
struct
ngx_list_part_s {
void * elts; // 数据
ngx_uint_t nelts; // 有效的数据项数
ngx_list_part_t * next; // next指针(next part)
};
typedef struct {
ngx_list_part_t * last; // 最后一个part
ngx_list_part_t part;
size_t size; // 单位数据项大小
ngx_uint_t nalloc; // 已分配的项目
ngx_pool_t * pool;
} ngx_list_t;
void * elts; // 数据
ngx_uint_t nelts; // 有效的数据项数
ngx_list_part_t * next; // next指针(next part)
};
typedef struct {
ngx_list_part_t * last; // 最后一个part
ngx_list_part_t part;
size_t size; // 单位数据项大小
ngx_uint_t nalloc; // 已分配的项目
ngx_pool_t * pool;
} ngx_list_t;
该链表也只实现了插入接口,没有实现删除(需在外部自行实现)。nginx每个链表项是一个part(ngx_list_part_t), 每个part可以容纳若干个数据。因此,遍历链表的代码就变成:
/*
*
* 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]
*
* }
*/
*
* 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的结构参见文献[4]. 有几个指针用于管理buffer, 用于发送/接收缓冲管理。
+----------已分配的内存----------------+
+----已操作-----+---待操作----+--------+
| | | |
start pos last end
5. ngx_queue_t, ngx_rbtree_t
队列和红黑树,都是经典的实现方式,没什么好讲的。红黑树实现参见本博客另一篇文章。
6. ngx_hash_t
hash函数
key = 0 ;
for (i = 0 ; i < len; i ++ ) {
key = key * 31 + data[i];
}
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 ]; // 配对关键字, 不定长度.
} 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;
}
void * value;
u_char len; //指定name的长度
u_char name[ 1 ]; // 配对关键字, 不定长度.
} 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;
}
参考文献
[1] 啃饼的博客. 对nginx有一系列的分析文章
[2] Joshua Zhu在广州技术沙龙的《nginx源码分析》交流。有PPT和视频. http://blog.zhuzhaoyuan.com/
[3] nginx的分析文章, 锁,内存管理,进程模型等. http://simohayha.javaeye.com/category/101180
[4] nginx的内存管理. http://simohayha.javaeye.com/blog/545192