Libevent源码分析-timer和signal处理
2015-08-21 22:53
483 查看
timer处理
Signal处理
timerfd和signalfd
timerfd
signalfd
其中evtimer_set是个宏定义
可以看到定时器事件中,fd=-1,event=0。fd为-1的事件不会发生。在将event添加到event_base时:
可以看到有个超时时间tv。在epoll_wait时,会有个超时时间timeout
当timeout到时,事件还没发生,epoll_wait还是会返回。
在Libevent中没这么简单,因为监控了多个事件,有许多超时时间,event_base中专门有个管理timeout的堆
这个堆是小根堆。堆得插入和删除复杂度都是Log(N)。在epoll_wait返回后会处理堆中的定时器事件
这就是Libevent中的Timer处理原理。
在Libevent中用了一个socket pair,先看一下event_base中的一个数据结构
这个数据结构中的
通过这一对socket pair就把signal事件转换为IO事件了。
这节介绍muduo中定时器的实现。先看一个2.6内核新增的有关定时的系统调用,基于这几个系统调用可以实现基于文件描述符的定时器。即可是定时,使文件描述符在某一特定时间可读。
1、timerfd_create用于创建一个定时器文件,函数返回值是一个文件句柄fd。
2、timerfd_settime用于设置新的超时时间,并开始计时。flag为0表示相对时间,为1表示绝对时间。new_value为这次设置的新时间,old_value为上次设置的时间。返回0表示设置成功。
3、timerfd_gettime用于获得定时器距离下次超时还剩下的时间。如果调用时定时器已经到期,并且该定时器处于循环模式(设置超时时间时struct itimerspec::it_interval不为0),那么调用此函数之后定时器重新开始计时。
参数fd:如果是-1,表示新建一个fd;否则应该是一个存在的文件描述符。
参数mask:通过这fd检测的信号集合。
参数flags:改变signal()的行为。
参考
linux新API—signalfd的使用方法
Signal处理
timerfd和signalfd
timerfd
signalfd
timer处理
在Libevent源码分析-event处理流程中,使用了定时器,来看一下源码:evtimer_set(&ev, time_cb, NULL);//设置定时器事件
其中evtimer_set是个宏定义
#define evtimer_set(ev, cb, arg) event_set((ev), -1, 0, (cb), (arg)) //event_set原型 void event_set(struct event *ev, evutil_socket_t fd, short events, void (*callback)(evutil_socket_t, short, void *), void *arg)
可以看到定时器事件中,fd=-1,event=0。fd为-1的事件不会发生。在将event添加到event_base时:
event_add(&ev, &tv);//注册事件
可以看到有个超时时间tv。在epoll_wait时,会有个超时时间timeout
int epoll_wait(int epfd,struct epoll_event* events, int maxevents,int timeout)
当timeout到时,事件还没发生,epoll_wait还是会返回。
在Libevent中没这么简单,因为监控了多个事件,有许多超时时间,event_base中专门有个管理timeout的堆
struct min_heap timeheap;
这个堆是小根堆。堆得插入和删除复杂度都是Log(N)。在epoll_wait返回后会处理堆中的定时器事件
static void timeout_process(struct event_base *base) { /* Caller must hold lock. */ struct timeval now; struct event *ev; if (min_heap_empty(&base->timeheap)) {//堆为空 return; } gettime(base, &now); while ((ev = min_heap_top(&base->timeheap))) { if (evutil_timercmp(&ev->ev_timeout, &now, >))//依次从堆顶去值,找到超时时间随后处理 break; /* delete this event from the I/O queues */ event_del_internal(ev); event_debug(("timeout_process: call %p", ev->ev_callback)); event_active_nolock(ev, EV_TIMEOUT, 1);//将超时时间加入active队列 } }
这就是Libevent中的Timer处理原理。
Signal处理
Libevent中,信号的处理不像timer那么简单了。因为timer可以借助等待超时实现,信号的发送是随机的,如何转换为IO?在Libevent中用了一个socket pair,先看一下event_base中的一个数据结构
struct evsig_info { struct event ev_signal;//检测ev_signal_pair[1]的event evutil_socket_t ev_signal_pair[2];//这里是2个socket fd int ev_signal_added;//信号是否注册 int ev_n_signals_added;//总共添加了几个信号 #ifdef _EVENT_H***E_SIGACTION struct sigaction **sh_old; #else ev_sighandler_t **sh_old; #endif int sh_old_max;/sh_old的大小 };
这个数据结构中的
ev_signal_pair[2]就是把signal转换为IO事件的关键。这其实是一对socket fd,它们的两端都在本地。ev_signal_pair[0]作为写入端,ev_signal_pair[1]作为fd事件。当信号发生时,写入ev_signal_pair[0],即可检测到ev_signal_pair[1]的可读事件。
evsig_init为信号做初始化,并把
ev_signal_pair[1]作为监听的socket fd。这个函数调用了
evutil_socketpair( AF_UNIX, SOCK_STREAM, 0, base->sig.ev_signal_pair)初始化ev_signal_pair
int evutil_ersatz_socketpair(int family, int type, int protocol, evutil_socket_t fd[2]) { evutil_socket_t listener = -1; evutil_socket_t connector = -1; evutil_socket_t acceptor = -1; struct sockaddr_in listen_addr; struct sockaddr_in connect_addr; ev_socklen_t size; int saved_errno = -1; if (protocol || (family != AF_INET)) { EVUTIL_SET_SOCKET_ERROR(ERR(EAFNOSUPPORT)); return -1; } if (!fd) { EVUTIL_SET_SOCKET_ERROR(ERR(EINVAL)); return -1; } listener = socket(AF_INET, type, 0);//设置监听的listen if (listener < 0) return -1; memset(&listen_addr, 0, sizeof(listen_addr)); listen_addr.sin_family = AF_INET; listen_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);//地址为本机 listen_addr.sin_port = 0; /* kernel chooses port. */ if (bind(listener, (struct sockaddr *) &listen_addr, sizeof (listen_addr)) == -1) goto tidy_up_and_fail; if (listen(listener, 1) == -1) goto tidy_up_and_fail; connector = socket(AF_INET, type, 0);//初始化主动连接端 if (connector < 0) goto tidy_up_and_fail; /* We want to find out the port number to connect to. */ size = sizeof(connect_addr); if (getsockname(listener, (struct sockaddr *) &connect_addr, &size) == -1)//主动连接地址为本机 goto tidy_up_and_fail; if (size != sizeof (connect_addr)) goto abort_tidy_up_and_fail; if (connect(connector, (struct sockaddr *) &connect_addr,//发起连接 sizeof(connect_addr)) == -1) goto tidy_up_and_fail; size = sizeof(listen_addr); acceptor = accept(listener, (struct sockaddr *) &listen_addr, &size);//接收连接 if (acceptor < 0) goto tidy_up_and_fail; if (size != sizeof(listen_addr)) goto abort_tidy_up_and_fail; /* Now check we are talking to ourself by matching port and host on the two sockets. */ if (getsockname(connector, (struct sockaddr *) &connect_addr, &size) == -1) goto tidy_up_and_fail; if (size != sizeof (connect_addr) || listen_addr.sin_family != connect_addr.sin_family || listen_addr.sin_addr.s_addr != connect_addr.sin_addr.s_addr || listen_addr.sin_port != connect_addr.sin_port) goto abort_tidy_up_and_fail; evutil_closesocket(listener); fd[0] = connector;//写入端 fd[1] = acceptor;//等待对方写入的fd return 0; }
通过这一对socket pair就把signal事件转换为IO事件了。
timerfd和signalfd
timerfd
在Linux 2.6内核以后新增了timerfd来做计时器,和IO相关联这节介绍muduo中定时器的实现。先看一个2.6内核新增的有关定时的系统调用,基于这几个系统调用可以实现基于文件描述符的定时器。即可是定时,使文件描述符在某一特定时间可读。
#include <sys/timerfd.h> int timerfd_create(int clockid, int flags); int timerfd_settime(int fd, int flags, onst struct itimerspec *new_value, struct itimerspec *old_value); int timerfd_gettime(int fd, struct itimerspec *curr_value);
1、timerfd_create用于创建一个定时器文件,函数返回值是一个文件句柄fd。
2、timerfd_settime用于设置新的超时时间,并开始计时。flag为0表示相对时间,为1表示绝对时间。new_value为这次设置的新时间,old_value为上次设置的时间。返回0表示设置成功。
3、timerfd_gettime用于获得定时器距离下次超时还剩下的时间。如果调用时定时器已经到期,并且该定时器处于循环模式(设置超时时间时struct itimerspec::it_interval不为0),那么调用此函数之后定时器重新开始计时。
signalfd
signal是把信号转换为IO事件的系统自带的方法。#include <sys/signalfd.h> int signalfd(int fd, const sigset_t *mask, int flags);
参数fd:如果是-1,表示新建一个fd;否则应该是一个存在的文件描述符。
参数mask:通过这fd检测的信号集合。
参数flags:改变signal()的行为。
参考
linux新API—signalfd的使用方法
相关文章推荐
- ssh常见错误
- 创建mysql数据库并指定编码
- 傻瓜都能看懂的网络流ek算法(poj1273)
- 【C++】万年历(时间计数器)
- Android的Service组件
- 要有被打断仍能够继续学习的能力
- 删除数据库mysql
- uva 10256 - The Great Divide(凸包)
- Easy Ui Layout控件
- 作业,大美农登录界面和设置密码
- 冒泡排序及两种优化方式
- js编程(选项卡)
- 今天开博客啦
- gnu-bash#1 介绍
- linux 查找文件或者服务
- 放弃是最难的进步,进步是最大的放弃
- js编程(增删学生信息)
- Linux应用编程之静态链接库和动态链接库
- 控件开发
- LA 4329 - Ping pong 树状数组