Nginx源码看看-数据结构
创建链表时候的内存创建和回收
创建缓冲区链表的函数:
typedef struct ngx_chain_s ngx_chain_t;
struct ngx_chain_s {
ngx_buf_t *buf; // 指向当前的buf缓冲区
ngx_chain_t *next; // 如果这是最后一个ngx_chain_t,需要把next置为NULL
};
ngx_chain_t *ngx_alloc_chain_link(ngx_pool_t *pool)
{
ngx_chain_t *cl;
cl = pool->chain; //从pool.chain 链表头部取一个出来
if (cl) //取到后pool.chain 链表指向下一个位置
{
pool->chain = cl->next;
return cl;
} //只有pool.chain 没有了才重新创建
cl = ngx_palloc(pool, sizeof(ngx_chain_t));
if (cl == NULL)
{
return NULL;
}
return cl;
}
该宏释放一个ngx_chain_t类型的对象:
#define ngx_free_chain(pool, cl)
cl->next = pool->chain;
pool->chain = cl;
//释放的对象被挂载在pool.chain链表的头部,需要使用的时候重这里再取出
这样子操作,感觉内存的利用率很好,完全不会做没意义的销毁,新创建对象。
如果要释放整个chain,则迭代此链表,对每个节点使用此宏即可。注意:对ngx_chaint_t类型的释放,并不是真的释放了内存,而仅仅是把这个对象挂在了这个pool对象的一个叫做chain的字段对应的chain上,以供下次从这个pool上分配ngx_chain_t类型对象的时候,快速的从这个pool->chain上取下链首元素就返回了,当然,如果这个链是空的,才会真的在这个pool上使用ngx_palloc函数进行分配。
字符大小写转换
#define ngx_tolower(c) (u_char) ((c >= 'A' && c <= 'Z') ? (c | 0x20) : c)
#define ngx_toupper(c) (u_char) ((c >= 'a' && c <= 'z') ? (c & ~0x20) : c)
/* 通过三元表达式实现字母的大小写转换 */
双向链表 ngx_queue_t 的结构
数据结构定义:
typedef struct ngx_queue_s ngx_queue_t;
struct ngx_queue_s {
ngx_queue_t *prev;
ngx_queue_t *next;
};
对,没有错就只是这个样子,只有前后两个指针,其他啥都没有了。。。。
必须用到的数据存储不见了,o((⊙﹏⊙))o ,接着看了下用法就发现,简直厉害啊!!
使用方法:
ngx_queue_t free; // 这是一个小哨兵,不存数据
ngx_queue_init(&free); // 初始化一下下 正常双向列表一样prev、next指向自己
struct node{
int data;
ngx_queue_t queue; // 把queue这个结构体乖乖的放在我们要创建链表的结构里
} node1;
ngx_queue_insert_head(&free, &node1.queue);
// 对!就这么用,先把queue放任意结构体里,再连到free这个哨兵上,都可以做链表用啦,写这个的人真是绝了
// 这里的ngx_queue_insert_head也是正常的操作,只是两个链表指针操作一下而已
到这里,已经可以看出来厉害了,可以随便加到任意结构体里面去,只是还不知道这个data数据怎么取出来。
获取节点内的数据data:
#define ngx_queue_data(q, type, link) \
(type *) ((u_char *) q - offsetof(type, link))
/* 依靠内存位置的off_t来得到相应节点元素数据的内存地址,
q为当前地址,type为相应结构体的类型,link为queue */
/* C 库宏 offsetof(type, member-designator) 会生成一个类型为 size_t 的整型常量,它是一个结构成员相对于结构开头的字节偏移量。成员是由 member-designator 给定的,结构的名称是在 type 中给定的。 */
果然最后,获取数据用到了偏移量。
这个样这个双向链表的,就可以当做类似接口一样使用了,想用哪里用哪里,厉害了o( ̄▽ ̄)d
Java 双向链表LinkedList对比
所谓没有对比就么有伤害,所以拿自己平常用的语言来对比一下[JDK1.8]
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
transient int size = 0;
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
transient Node<E> last;
。。。。。。。。。。。
/* 真正想看的核心Node的定义 是作为静态内部类定义的*/
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
。。。。。。。。。。。
/* 其他的方法就不看了,不是对比的重点。*/
Java 中使用的是泛型来实现各种数据的绑定,节点中是有data数据的。
然后想了一下,如果使用java能不能写一个像nginx那样的呢?
试了一下,结果写不粗来,主类的属性类中取不到主类的数据,(ノ`Д)ノ
(用接口,继承啥的都可以,但不是想要的。。。。。果然面对对象的语言跟C完全不一样)
就是不知道有没有大神能写的出来了。。。。感觉是写法本身跟Java的思想就不匹配
ngx_cycle 核心结构体
Nginx框架是围绕着ngx_cycle_t结构体来控制进程运行的。ngx_cycle_t结构体的prefix、conf_prefix、conf_file等字符串类型成员保存着Nginx配置文件的路径。Nginx在程序运行的时候会使用ngx_init_cycle方法来根据nginx.conf加载模块。
struct ngx_cycle_s {
void ****conf_ctx;
/* 保存着所有模块存储配置项的结构体的指针,它首先是一个数组,每个数组成员又是一个指针,
这个指针指向另一个存储着指针的数组 */
ngx_pool_t *pool;
/* 内存池 */
ngx_log_t *log;
ngx_log_t new_log;
/* 日志模块中提供了生成基本ngx_log_t日志对象的功能,
这里的log实际上是在还没有执行ngx_init_cycle方法前,如果有信息需要输出到日志,
就会暂时使用log对象,它会输出到屏幕。
* 在ngx_init_cycle方法执行后,将会根据nginx.conf配置文件中的配置项,构造出正确的日志文件,
此时会对log重新赋值(用new_log的地址覆盖上面的log指针)
*/
ngx_uint_t log_use_stderr;
/* unsigned log_use_stderr:1; */
ngx_uint_t files_n;
ngx_connection_t **files;
/* 对于poll、rtsig这样的事件模块,会以有效文件句柄数来预先建立这些ngx_connection_t结构体,
以加速事件的收集、分发。这时files就会保存所有ngx_connection_t的指针组成的数组,files_n是指针的总数。*/
ngx_connection_t *free_connections;
ngx_uint_t free_connection_n;
/* 空闲连接池,与free_connection_n配合使用 */
ngx_queue_t reusable_connections_queue;
/* 可重复使用的双向连接队列,成员类型是ngx_connection_t */
ngx_array_t listening;
/* 动态数组,表示监听端口及相关参数 */
ngx_array_t paths;
/* 保存Nginx要操作的所有目录,如果目录不存在,则会试图创建,而创建目录失败将导致Nginx启动失败。*/
ngx_list_t open_files;
/* 保存Nginx已经打开的所有文件,类型是ngx_open_file_t,Nginx不会主动添加文件,这是给模块自己用的,
如果需要的话,模块向其中添加文件路径名,Nginx框架会在ngx_init_cycle方法中打开这些文件 */
ngx_list_t shared_memory;
/* 单链表存储ngx_shm_zone_t,每个元素表示一块共享内存。*/
ngx_uint_t connection_n;
ngx_connection_t *connections;
ngx_event_t *read_events;
ngx_event_t *write_events;
/* 表示当前进程中所有的连接对象和它们的读写事件,connection_n是它们的数量 */
ngx_cycle_t *old_cycle;
/* 旧的ngx_cycle_t对象用于引用上一个ngx_cycle_t对象中的成员,例如ngx_init_cycle方法,
在启动初期,需要建立一个临时的ngx_cycle_t对象保存一些变量,在调用ngx_init_cycle方法时,
就可以把旧的ngx_cycle_t的对象传进去,而这时old_clcle对象就会保存这个前期的ngx_clcle_t对象。*/
ngx_str_t conf_file;
/* 配置文件相对于安装目录的路径名 */
ngx_str_t conf_param;
/* nginx处理配置文件时需要特殊处理的在命令行携带的参数,一般是-g选项携带的参数 */
ngx_str_t conf_prefix;
ngx_str_t prefix;
/* Nginx配置文件所在目录的路径和安装目录的路径 */
ngx_str_t lock_file;
/* 用于进程间同步的文件锁名称 */
ngx_str_t hostname;
/* 使用gethostname系统调用得到的主机名 */
};
ngx_command_t 核心模块指令
这个结构体在之前看模块开发的时候有用到,一直不理解是什么玩意儿,看了结构体定义,果然清晰多了!
typedef struct ngx_command_s ngx_command_t;
struct ngx_command_s {
ngx_str_t name; // 配置指令的名称
ngx_uint_t type; // 配置指令的类型(参数个数描述等)
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
/* 当nginx在解析配置的时候,如果遇到这个配置指令,将会把读取到的值传递给这个函数进行解析保存。
因为具体每个配置指令的值如何处理,只有定义这个配置指令的人是最清楚的,set可能复杂也可能很简单。
比如errlog模块的“error_log”指令就是调用ngx_error_log写一条日志,并不需要存储什么配置数据。
* cf: 保存从配置文件读取到的原始字符串以及相关信息。这个参数的args字段是一个ngx_str_t类型的数组,
该数组首元素是这个配置指令本身,第二个元素开始才是参数。
cmd: 这个配置指令对应的ngx_command_t结构。
conf: 就是定义的存储这个配置值的结构体
*/
ngx_uint_t conf;
/* 该字段被NGX_HTTP_MODULE类型模块所用,指定当前配置项存储的内存位置。实际上是使用哪个内存池的问题。
因为http模块对所有http模块所要保存的配置信息,划分了main, server和location三个地方进行存储,
每个地方都有一个内存池用来分配存储这些信息的内存。
这里可能的值为NGX_HTTP_MAIN_CONF_OFFSET、NGX_HTTP_SRV_CONF_OFFSET或NGX_HTTP_LOC_CONF_OFFSET。
当然也可以直接置为0,就是NGX_HTTP_MAIN_CONF_OFFSET。
*/
ngx_uint_t offset;
/* 指定该配置项值的精确存放位置,一般指定为某一个结构体变量的字段偏移。
比如我们定义了一个结构体A,该项配置的值需要存储到该结构体的b字段。那么在这里就可以填写offsetof(A, b)。
对于有些配置项,它的值不需要保存或者是需要保存到更为复杂的结构中时,这里可以设置为0。
*/
void *post;
/* 可指向任何一个在读取配置过程中需要的数据,以便于进行配置读取的处理。大多时候不需要,设为0即可。*/
};
static ngx_command_t ngx_core_commands[]; // 保存了核心指令
ngx_module_t 模块信息
在模块开发的时候要用到这个进行说明模块本身的信息。
从某种意义上来说,这是这个模块最重要的一个信息。它告诉了nginx这个模块的一些信息,上面定义的配置信息,还有模块上下文信息,都是通过这个结构来告诉nginx系统的,也就是加载模块的上层代码,都需要通过定义的这个结构,来获取这些信息。
typedef struct ngx_module_s ngx_module_t;
struct ngx_module_s {
ngx_uint_t ctx_index;
/* 在相应模块类的计数,nginx模块分为四种:core、event、http和mail
* ctx->loc_conf[ngx_modules[i]->ctx_index] = mconf;
*/
ngx_uint_t index;
/* 模块计数器,按照每个模块在ngx_modules[]数组中的声明顺序,从0开始依次给每个模块赋值 */
ngx_uint_t spare0;
ngx_uint_t spare1;
ngx_uint_t spare2;
ngx_uint_t spare3;
ngx_uint_t version;
/* 模块当前版本 */
void *ctx;
/* 模块上下文,不同类别模块有不同上下文,ngx_http_conf_ctx_t/ngx_mail_conf_ctx_t/... */
ngx_command_t *commands;
/* 该模块的命令集,是ngx_command_t数组 */
ngx_uint_t type;
/* 模块种类:core、event、http和mail */
ngx_int_t (*init_master)(ngx_log_t *log); // master进程初始化调用
ngx_int_t (*init_module)(ngx_cycle_t *cycle); // 模块初始化调用
ngx_int_t (*init_process)(ngx_cycle_t *cycle); // worker进程初始化调用
ngx_int_t (*init_thread)(ngx_cycle_t *cycle); // 线程初始化调用
void (*exit_thread)(ngx_cycle_t *cycle); // 线程退出调用
void (*exit_process)(ngx_cycle_t *cycle); // worker进程结束调用
void (*exit_master)(ngx_cycle_t *cycle); // master进程结束调用
/* callback:如果该模块需要发生这些行为执行特定的功能,可以通过这些回调函数指针注册一个回调函数接口来实现 */
uintptr_t spare_hook0;
uintptr_t spare_hook1;
uintptr_t spare_hook2;
uintptr_t spare_hook3;
uintptr_t spare_hook4;
uintptr_t spare_hook5;
uintptr_t spare_hook6;
uintptr_t spare_hook7;
};
#define NGX_NUMBER_MAJOR 3
#define NGX_NUMBER_MINOR 1
#define NGX_MODULE_V1 0, 0, 0, 0, \
NGX_DSO_ABI_COMPATIBILITY, NGX_NUMBER_MAJOR, NGX_NUMBER_MINOR
/* 前7个成员初始化 */
#define NGX_MODULE_V1_PADDING 0, 0, 0, 0, 0, 0, 0, 0
/* 后8个成员初始化 */
恩。。就这些了,接下来就是从main开始看的东东了。是nginx初始化参数,解析命令行参数。。等等的东西了!目前看不下去了。。。得找个好的资料继续学习才行 ︿( ̄︶ ̄)︿
参考资料
博客(总):https://blog.csdn.net/wuchunlai_2012/article/category/6098793
看源码的好地方:http://lxr.nginx.org/source 【真的很好用】
小杭 _(¦3」∠)_