Libev学习笔记2
这一节根据官方文档给出的简单示例,深入代码内部,了解其实现机制。示例代码如下:
int
main (void)
{
struct ev_loop *loop = EV_DEFAULT; ev_io_init (&stdin_watcher, stdin_cb, /*STDIN_FILENO*/ , EV_READ);
ev_io_start (loop, &stdin_watcher); ev_timer_init (&timeout_watcher, timeout_cb, 5.5, .);
ev_timer_start (loop, &timeout_watcher); ev_run (loop, ); return ;
}
宏EV_DEFAULT对缺省的ev_loop进行初始化,然后将指针返回给main函数中的loop,它的定义为:
# define EV_DEFAULT ev_default_loop ()
函数ev_default_loop定义如下:
#if EV_MULTIPLICITY
struct ev_loop * ecb_cold
#else
int
#endif
ev_default_loop (unsigned int flags) EV_THROW
{
if (!ev_default_loop_ptr)
{
/* EV_P = struct ev_loop *loop */
EV_P = ev_default_loop_ptr = &default_loop_struct; /* 初始化ev_loop各个字段, flags = 0 */
loop_init (EV_A_ flags); if (ev_backend (EV_A))
{
#if EV_CHILD_ENABLE
/* 接收SIGCHLD信号 */
ev_signal_init (&childev, childcb, SIGCHLD);
ev_set_priority (&childev, EV_MAXPRI);
ev_signal_start (EV_A_ &childev);
ev_unref (EV_A); /* child watcher should not keep loop alive */
#endif
}
else
ev_default_loop_ptr = ;
} return ev_default_loop_ptr;
}
ev_default_loop_ptr是一个全局指针,指向缺省的ev_loop,在这个函数中可以看出,这个缺省的ev_loop就是default_loop_struct。该函数最终就是把这个ev_default_loop_ptr赋值给main函数中的loop的。初始化完指针后,就要对这个缺省ev_loop进行初始化了,这个任务交给loop_init函数来完成。loop_init函数主要是对default_loop_struct中的各个字段初始化,包括对事件驱动机制(如poll、epoll、select等)的初始化。
初始化完毕后回到main函数,此时的loop已经指向了一个初始化后的ev_loop结构体。
接下来调用ev_io_init宏函数。该函数主要针对如下几个问题进行初始化:要监听几号描述符?监听这个描述符的什么事件?监听事件发生时做什么动作?这几个问题的答案就是由我们调用该函数时所传入的参数来回答的。该宏的定义如下:
#define ev_io_init(ev,cb,fd,events) do { ev_init ((ev), (cb)); ev_io_set ((ev),(fd),(events)); } while (0)
这个宏的初始化工作可以分为两个部分,一部分由ev_init宏来完成,另一部分由ev_io_set宏来完成。为什么要分成两部分,还需要从Libev对结构体的设计说起。
Libev中有不同的事件,每一种事件的定义不同,一个需要监控的事件称为一个watcher。在官方示例中,定义了两个watcher,一个是监控I/O的名为stdin_watcher的watcher,另一个监控定时器的名为timeout_watcher的watcher。这些watcher作为派生类,共同继承自基类EV_WATCHER,基类的定义如下:
#define EV_WATCHER(type) \
int active; /* private */ \ /* 非0表示watcher为激活状态,是periodics或timers数组的下标 */
int pending; /* private */ \ /* 非0表示watcher中有事件被触发,是pendings数组的下标 */
EV_DECL_PRIORITY /* private */ \ /* watcher的优先级 */
EV_COMMON /* rw */ \ /* void *data; */
EV_CB_DECLARE (type) /* private */ /* void (*cb)(EV_P_ struct type *w, int revents); */
实际上这个宏就是定义了一些变量,每个变量的含义我已经在注释中表明,最后一个宏展开后就是定义了一个函数指针,倒数第二个宏展开后就是一个通用指针,供用户自由使用。再看看派生类ev_io的定义:
#define EV_WATCHER_LIST(type) \
EV_WATCHER (type) \
struct ev_watcher_list *next; /* private */ typedef struct ev_io
{
EV_WATCHER_LIST (ev_io) int fd; /* ro */
int events; /* ro */
} ev_io;
ev_io是一个真正的结构体,它包含的成员包括:基类中定义的各个变量,next指针,自己结构体中独有的变量。next指针的目的是将不同的watcher连接起来,形成链表。
所以,对一个派生类的初始化需要分别对继承的基类部分和派生类部分进行初始化。ev_init专门负责初始化基类部分,ev_XXX_set专门负责初始化派生类部分,XXX需要根据不同类型的watcher而定。回到刚才的ev_io_init宏,该宏分别调用了两个宏:ev_init和ev_io_set分别初始化基类成员和派生类成员。如果需要初始化timer类型的watcher,那么就调用ev_init和ev_timer_set分别进行初始化。ev_init是每一种类型的watcher都需要调用的,因为它们都继承自同一个基类,而各自特有的成员变量则通过ev_XXX_set初始化。这样的模拟面向对象的设计和各种宏的定义使得代码阅读起来变得困难,但是代码的复用性会增强,执行效率也会变得更高。
对watcher初始化完毕后回到main函数,接下来调用ev_io_start,主要代码如下:
/* EV_P_ = EV_P = struct ev_loop *loop, 单一参数用EV_P, 第一个参数用EV_P_ */
void noinline
ev_io_start (EV_P_ ev_io *w) EV_THROW
{
int fd = w->fd;/* 设置为启动状态 */
ev_start (EV_A_ (W)w, ); /* 将watcher挂到loop->anfds的head链表中 */
array_needsize (ANFD, anfds, anfdmax, fd + , array_init_zero);
wlist_add (&anfds[fd].head, (WL)w); fd_change (EV_A_ fd, w->events & EV__IOFDSET | EV_ANFD_REIFY);
w->events &= ~EV__IOFDSET; EV_FREQUENT_CHECK;
}
该函数主要完成两件事:
- 开启对stdin_watcher这个watcher的监听,包括设置相关的标志位和加入事件驱动机制中(如epoll调用epoll_ctl)。
- 将watcher挂到初始化好的loop中统一管理。
下面进行详细的分析。
ev_start函数就是设置一些watcher中的标志,表示开始监听该watcher。(W)w可以看成是将派生类指针ev_io*强制转换成基类指针,也就是多态机制。
在Libev中,有一个非常重要的结构体叫ANFD,它是对文件描述符的封装,一个fd对应一个ANFD,内部包含了对该fd所做动作或已触发动作的描述:
/* file descriptor info structure */
/* 一个fd对应一个ANFD */
typedef struct
{
/* 一个fd可以有多个watcher同时监听,多个watcher形成一个链表,head表示链表头 */
WL head; /* 上面链表中事件按位或 */
unsigned char events; /* the events watched for */ /* 为1表示需要监听的事件发生了变化,需要重新epoll_ctl */
unsigned char reify; /* flag set when this ANFD needs reification (EV_ANFD_REIFY, EV__IOFDSET) */ /* 实际触发的事件 */
unsigned char emask; /* the epoll backend stores the actual kernel mask in here */
unsigned char unused;
#if EV_USE_EPOLL
unsigned int egen; /* generation counter to counter epoll bugs */
#endif
} ANFD;
那么fd如何和对应的ANFD相关联呢?答案是将ANFD放入ev_loop的anfds数组中,fd作为下标,这样寻找对应ANFD的时间复杂度是O(1),速度非常的快。anfds数组的大小是可调的,它根据当前需要监听的fd的最大值调整自己的大小,array_needsize宏就是做调整工作的:
#define array_needsize(type,base,cur,cnt,init) \
if (expect_false ((cnt) > (cur))) \
{ \
int ecb_unused ocur_ = (cur); \
(base) = (type *)array_realloc \
(sizeof (type), (base), &(cur), (cnt)); \
/* 只初始化新分配空间 */
init ((base) + (ocur_), (cur) - ocur_); \
}
如果新加入的fd大于anfds数组当前的大小cur,就增加容量。expect_false宏的作用是让编译期对代码在分支预测方面进行优化,提高代码性能,这涉及到流水线方面的一些知识。
回到ev_io_start函数,调整完loop的anfds数组后,调用wlist_add函数将初始化的watcher加入到anfds数组中,watcher监听的fd作为数组的下标。在ANFD的定义中有一个head成员,它就是连接所有相同fd的watcher的链表头。
回到ev_io_start函数,到目前为止,需要监听的fd还没有加入到事件驱动机制中。Libev采用的方法是将新加入的fd添加到一个数组中,之后再扫描这个数组,将数组中的所有fd加入到事件驱动机制中。ev_io_start函数中调用fd_change函数的目的就是将新fd加入到数组中,这个数组的名字叫做fdchanges,属于ev_loop的成员。不光是新的fd需要加入到fdchanges数组,被修改了监听内容的fd都需要加入到fdchanges数组。
ev_io_start函数大体上分析完毕,它的作用是开启对一个watcher的监听,而不是fd,因为一个fd可以对应多个watcher。例如监听的fd=100,需要同时监听输入和输出,那么会有两个watcher对应这个fd,一个是输入,一个是输出。ev_io_stop函数就是停止监听一个watcher,操作和ev_io_start相反:
void noinline
ev_io_stop (EV_P_ ev_io *w) EV_THROW
{
clear_pending (EV_A_ (W)w);
if (expect_false (!ev_is_active (w)))
return; assert (("libev: ev_io_stop called with illegal fd (must stay constant after start!)", w->fd >= && w->fd < anfdmax)); EV_FREQUENT_CHECK; wlist_del (&anfds[w->fd].head, (WL)w);
ev_stop (EV_A_ (W)w); fd_change (EV_A_ w->fd, EV_ANFD_REIFY); EV_FREQUENT_CHECK;
}
Libev学习笔记2的更多相关文章
- libev学习笔记
转 libev的使用--结合Socket编程 作者:cxy450019566 之前自己学过一些libev编程的基础,这次写压测刚好用上了,才算真正动手写了些东西,在这里做一些总结.写这篇文章是为了用浅 ...
- Libev学习笔记4
这一节首先分析Libev的定时器部分,然后分析signal部分. 对定时器的使用主要有两个函数: ev_timer_init (&timeout_watcher, timeout_cb, .) ...
- Libev学习笔记3
设置完需要监听的事件之后,就开始event loop了.在Libev中,该工作由ev_run函数完成.它的大致流程如下: int ev_run (EV_P_ int flags) { do { /* ...
- Libev学习笔记1
和Libevent相似,Libev是一个高性事件驱动框架,据说性能比Libevent要高,bug比Libevent要少.Libev只是一个事件驱动框架,不是网络库,因为它的内部并没有任何socket编 ...
- libev 学习使用
libev 简单的I/O库. a high performance full featured event loop written in c libev 的大小也比 libevent 小得多并且自 ...
- js学习笔记:webpack基础入门(一)
之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...
- PHP-自定义模板-学习笔记
1. 开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2. 整体架构图 ...
- PHP-会员登录与注册例子解析-学习笔记
1.开始 最近开始学习李炎恢老师的<PHP第二季度视频>中的“章节5:使用OOP注册会员”,做一个学习笔记,通过绘制基本页面流程和UML类图,来对加深理解. 2.基本页面流程 3.通过UM ...
- 2014年暑假c#学习笔记目录
2014年暑假c#学习笔记 一.C#编程基础 1. c#编程基础之枚举 2. c#编程基础之函数可变参数 3. c#编程基础之字符串基础 4. c#编程基础之字符串函数 5.c#编程基础之ref.ou ...
随机推荐
- Oracle经典书籍推荐
转自:http://www.cnblogs.com/fjfzhkb/archive/2007/12/05/983381.html 很多网友询问如何选择入门书籍,学Oracle有什么好书,这里给出一些常 ...
- windows下nginx+tomcat分布式集群部署
首先官网下载 http://nginx.org/en/download.html,我的本地环境为 实现的架构: 从图上可以看出,nginx作为负载均衡请求分发器,当请求A应用时候,分发到A集群,同理 ...
- 经常会用到的js函数
//获取样式function getStyle(obj,attr){ if(obj.currentStyle){ return obj.currentStyle[attr]; }else{ re ...
- codeforces 55D. Beautiful numbers 数位dp
题目链接 一个数, 他的所有位上的数都可以被这个数整除, 求出范围内满足条件的数的个数. dp[i][j][k], i表示第i位, j表示前几位的lcm是几, k表示这个数mod2520, 2520是 ...
- 网络抓包--Wireshark
Wireshark 是一款非常棒的Unix和Windows上的开源网络协议分析器.它可以实时检测网络通讯数据,也可以检测其抓取的网络通讯数据快照文件.可以通过图形界面浏览这些数据,可以查看网络通讯数据 ...
- Codeforces 713C Sonya and Problem Wihtout a Legend(单调DP)
[题目链接] http://codeforces.com/problemset/problem/713/C [题目大意] 给出一个数列,请你经过调整使得其成为严格单调递增的数列,调整就是给某些位置加上 ...
- HDU 2167 Pebbles
题目大意:有个N*N( 3<=N<=15 )方阵, 可从中若干个数, 使其总和最大.取数要求, 当某一个数被选, 其周围8个数都不能选. 题解:记s数组为合法状态,即没有相邻的数字同时被选 ...
- Tempter of the Bone(dfs奇偶剪枝)
Tempter of the Bone Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Othe ...
- iOS中Block介绍(二)内存管理与其他特性
我们在前一章介绍了block的用法,而正确使用block必须要求正确理解block的内存管理问题.这一章,我们只陈述结果而不追寻原因,我们将在下一章深入其原因. 一.block放在哪里 我们针对不同情 ...
- Jquery 遍历数组之grep()方法介绍
grep()方法用于数组元素过滤筛选. grep(array,callback,boolean);方法参数介绍. array ---待处理数组 callback ---这个回调函数用来处理数组中 ...