libevent源码学习(15):信号event的处理
目录
信号event处理流程
与信号event相关的结构体
初始化工作
创建一个信号event
添加一个信号event
信号回调函数
信号event的激活
Libevent中的event,主要分为三大类:io读写event、超时事件以及信号event。前面的文章对前两类的event都进行了分析,下面就来说一下Libevent是如何处理信号event的。
信号event处理流程
不管使用的是什么后端IO复用模型,这些复用模型本身都是只支持读写IO事件的,Libevent所实现的“信号event处理”,实际上也是把信号event最终转换到了IO读写event。它的工作原理为:用户层面指定一个需要监听的信号sig以及回调函数custom_cb,对于Libevent,它会为sig定义一个信号处理函数evsig_handler,然后再定义一个socket pair,这是一对全双工套接字,向其中一个写入数据那么就可以从另一个中读出数据,Libevent会将其中之一固定为读端,而另一个则为写端,并为读端套接字注册一个IOevent名为ev_signal,其回调函数为evsig_cb。
当需要监听的信号sig到达,就会自动去调用刚才设置的信号处理函数evsig_handler,在evsig_handler中会向写端套接字写入一个字节(这个字节实际上就是sig的值),此时读端套接字就会收到这个字节,触发可读事件,激活ev_signal然后调用evsig_cb,在evsig_cb函数中,会将所有监听sig信号的event全部激活,这些event中就包含根据用户要求所定义的event,处理这个event的时候就会回调custom_cb,这样,就完成了监听sig信号到回调custom_cb的信号event处理过程。如下图所示:
与信号event相关的结构体
struct event_base {
......
const struct eventop *evsigsel;//信号处理后端相关函数
/** Data to implement the common signal handelr code. */
struct evsig_info sig;//信号相关信息
......
/** Mapping from file descriptors to enabled (added) events */
struct event_io_map io;
/** Mapping from signal numbers to enabled (added) events. */
struct event_signal_map sigmap;
......
};
前面说过,event_base在初始化的时候就会绑定一个IO复用模型后端到evsel成员中,但是对于信号event,整个处理的过程中是不会也不应该直接使用这些IO复用后端,而是使用信号event专用的后端,并且将其绑定到evsigsel中,在该后端结构体中只含有添加和删除函数,如下所示:
static const struct eventop evsigops = {
"signal",
NULL,
evsig_add,
evsig_del,
NULL,
NULL,
0, 0, 0
};
然后再说event_base中的sig成员,这是一个evsig_info类型的结构体变量,该类型定义如下:
typedef void (*ev_sighandler_t)(int);
struct evsig_info {
struct event ev_signal; //需要添加到event_io_map中监听读端套接字
/* Socketpair used to send notifications from the signal handler */
evutil_socket_t ev_signal_pair[2];//一对全双工套接字
/* True iff we've added the ev_signal event yet. */
int ev_signal_added; //标识ev_signal是否被添加到io map中
/* Count of the number of signals we're currently watching. */
int ev_n_signals_added; //监听的信号数量
#ifdef _EVENT_HAVE_SIGACTION
struct sigaction **sh_old; //sigaction *数组,每一个元素都指向一个sigaction,其中含有信号的处理函数
#else
ev_sighandler_t **sh_old; //如果没有定义_EVENT_HAVE_SIGACTION,那么就是一个函数指针数组,存储每个信号的回调函数指针
#endif
/* Size of sh_old. */
int sh_old_max; //sh_old数组的大小
};
这里的ev_signal_pair[2]就是前面所说的socket pair全双工套接字。而evsig_info中的ev_signal,就是用于监听socket pair中读端套接字的event。sh_old是一个二级指针,也可以看做数组,它每个元素都对应了一个信号的处理函数。
初始化工作
对于普通的IO复用模型,是在event_base的初始化过程中将相应的后端结构体绑定到evsel中的,同样的,信号相关的后端结构体也是在event_base的初始化过程中绑定到evsigsel中的,并且还会做很多事情,如下所示:
struct event_base *
event_base_new_with_config(const struct event_config *cfg)
{
......
for (i = 0; eventops[i] && !base->evbase; i++) {
......
base->evsel = eventops[i]; //将该backup作为base使用的backup
base->evbase = base->evsel->init(base); //用选定的backup的初始化函数来初始化base中的evbase
}
......
}
//以epoll后端为例
static void *
epoll_init(struct event_base *base)
{
......
evsig_init(base);
}
int
evsig_init(struct event_base *base)
{
if (evutil_socketpair(
AF_UNIX, SOCK_STREAM, 0, base->sig.ev_signal_pair) == -1) {//创建了两个互相连接的套接字放到ev_signal_pair中
......
}
evutil_make_socket_closeonexec(base->sig.ev_signal_pair[0]);//调用exec时关闭套接字
evutil_make_socket_closeonexec(base->sig.ev_signal_pair[1]);
base->sig.sh_old = NULL;
base->sig.sh_old_max = 0;
evutil_make_socket_nonblocking(base->sig.ev_signal_pair[0]);//将创建的两个套接字都设置为非阻塞
evutil_make_socket_nonblocking(base->sig.ev_signal_pair[1]);
......
}
在evsig_init函数中,会先调用evutil_socketpair来创建一对全双工套接字,该函数定义如下:
int
evutil_socketpair(int family, int type, int protocol, evutil_socket_t fd[2])//创建一对相互连接的套接字,保存在fd[2]中
{
#ifndef WIN32
return socketpair(family, type, protocol, fd);
#else
return evutil_ersatz_socketpair(family, type, protocol, fd);
#endif
}
int
evutil_ersatz_socketpair(int family, int type, int protocol,
evutil_socket_t fd[2])
{
/* This code is originally from Tor. Used with permission. */
/* This socketpair does not work when localhost is down. So
* it's really not the same thing at all. But it's close enough
* for now, and really, when localhost is down sometimes, we
* have other problems too.
*/
#ifdef WIN32
#define ERR(e) WSA##e
#else
#define ERR(e) e
#endif
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
#ifdef AF_UNIX
&& family != AF_UNIX
#endif
)) {
EVUTIL_SET_SOCKET_ERROR(ERR(EAFNOSUPPORT));
return -1;
}
if (!fd) {
EVUTIL_SET_SOCKET_ERROR(ERR(EINVAL));
return -1;
}
listener = socket(AF_INET, type, 0);
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监听本地环回地址
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); //创建connector套接字
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)//获取listener绑定的ip地址,保存到connect_addr中
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)//将connector与listener连接
goto tidy_up_and_fail;
size = sizeof(listen_addr);
acceptor = accept(listener, (struct sockaddr *) &listen_addr, &size);//通过acceptor可以向connector发送数据
if (acceptor < 0)
goto tidy_up_and_fail;
if (size != sizeof(listen_addr))
goto abort_tidy_up_and_fail;
evutil_closesocket(listener);
/* 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;
fd[0] = connector;//通过connector向listener发信息
fd[1] = acceptor; //通过acceptor向connector发信息
return 0;
......
}
创建一对全双工套接字的流程是:先创建一个listener,监听本地环回端口,相当于服务端。创建一个connector作为客户端向listener发起连接,listener通过accept函数与connecotr建立连接,新连接套接字为acceptor,此时acceptor和connector就可以互发消息,成为一对全双工套接字。
再回到evsig_init函数中,接着就会把创建的这对套接字设置为非阻塞,并且在执行exec时关闭。接下来会调用event_assign函数,设置ev_signal的监听对象就是这对套接字之一,并且监听读事件,回调函数为evsig_cb,这点就对应了前面处理流程中紫色框部分。然后就是将信号处理后端函数结构体绑定到event_base中的evsigsel上。
int
evsig_init(struct event_base *base)
{
......
event_assign(&base->sig.ev_signal, base, base->sig.ev_signal_pair[1],
EV_READ | EV_PERSIST, evsig_cb, base);//pair[1]为读端,为其注册ev_signal,类型为永久性读事件
base->sig.ev_signal.ev_flags |= EVLIST_INTERNAL;//标识为内部事件
event_priority_set(&base->sig.ev_signal, 0);//signal event优先级为0
base->evsigsel = &evsigops;//绑定信号处理后端函数调用结构体
return 0;
}
到这里,初始化就算是完成了,关于ev_signal的回调函数后面再说,接下来就进入信号event的处理过程。
创建一个信号event
创建一个io event的函数是event_new,如果要创建信号event,一种方法是直接设置event_new的参数为EV_SIGNAL,不过这种方法对于用户来说是非常不友好的。为此,Libevent在event.h中进行了如下的宏定义:
#define evsignal_add(ev, tv) event_add((ev), (tv))
#define evsignal_assign(ev, b, x, cb, arg) \
event_assign((ev), (b), (x), EV_SIGNAL|EV_PERSIST, cb, (arg))
#define evsignal_new(b, x, cb, arg) \
event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg))
#define evsignal_del(ev) event_del(ev)
#define evsignal_pending(ev, tv) event_pending((ev), EV_SIGNAL, (tv))
#define evsignal_initialized(ev) event_initialized(ev)
可以看到,这里实际上对io event的相关函数的参数进行了“特殊化处理”,最终得到了信号event的相关函数。此时,就可以通过evsignal_new函数来创建一个信号event了,实际上就是创建了一个EV_SIGNAL|EV_PERSIST的永久信号事件。而该函数内部实际上又会调用event_assign函数。需要注意的是,创建普通event时第二个参数传入的是需要监听的的文件描述符,而这里创建信号event时传入的第二个参数则应当是需要监听的信号值了,比如说需要监听的信号是SIGUSR1,那么调用evsignal_new时,传入的第二个参数就应该直接使用SIGUSR1。evsignal_new的第三个参数cb自然就应当是用户需要监听的信号发生后,期待调用的函数。
接下来再来分析如何添加一个信号event。
添加一个信号event
如上所述,添加一个信号event使用evsignal_add函数,实际上就是event_add函数。前面创建的信号event,其events成员已经被设置为了EV_SIGNAL|EV_PERSIST。因此,event_add函数中会调用evmap_signal_add函数将该event添加到event_signal_map中,evmap_signal_add如下所示:
int
evmap_signal_add(struct event_base *base, int sig, struct event *ev)
{
const struct eventop *evsel = base->evsigsel;//使用的是信号回调函数结构体
struct event_signal_map *map = &base->sigmap;
struct evmap_signal *ctx = NULL;
if (sig >= map->nentries) {
if (evmap_make_space(
map, sig, sizeof(struct evmap_signal *)) == -1)
return (-1);
}
GET_SIGNAL_SLOT_AND_CTOR(ctx, map, sig, evmap_signal, evmap_signal_init,
base->evsigsel->fdinfo_len); //ctx指向sigmap的entries[sig]对应的evmap_signal,evmap_signal中含有一个event双向链表
if (TAILQ_EMPTY(&ctx->events)) {
if (evsel->add(base, ev->ev_fd, 0, EV_SIGNAL, NULL)//调用的实际上是evsigsel中的add函数
== -1)
return (-1);
}
TAILQ_INSERT_TAIL(&ctx->events, ev, ev_signal_next);
return (1);
}
可以看到,在通过TAILQ_INSERT_TAIL将前面创建的event添加到event_signal_map之前,会先调用信号回调后端函数中的add函数,也就是前面的evsig_add函数,该函数定义如下:
static int
evsig_add(struct event_base *base, evutil_socket_t evsignal, short old, short events, void *p)
{
struct evsig_info *sig = &base->sig;
(void)p;
EVUTIL_ASSERT(evsignal >= 0 && evsignal < NSIG);
/* catch signals if they happen quickly */
EVSIGBASE_LOCK();
if (evsig_base != base && evsig_base_n_signals_added) {
event_warnx("Added a signal to event base %p with signals "
"already added to event_base %p. Only one can have "
"signals at a time with the %s backend. The base with "
"the most recently added signal or the most recent "
"event_base_loop() call gets preference; do "
"not rely on this behavior in future Libevent versions.",
base, evsig_base, base->evsel->name);
}
evsig_base = base;
evsig_base_n_signals_added = ++sig->ev_n_signals_added;
evsig_base_fd = base->sig.ev_signal_pair[0];//pair[0]是写端
EVSIGBASE_UNLOCK();
event_debug(("%s: %d: changing signal handler", __func__, (int)evsignal));
if (_evsig_set_handler(base, (int)evsignal, evsig_handler) == -1) {//设置信号处理函数
goto err;
}
if (!sig->ev_signal_added) {//如果还没有添加ev_signal,就调用event_add添加,如果添加过了就不用再添加了
if (event_add(&sig->ev_signal, NULL))//ev_signal的事件类型为READ|PERSIST
goto err;
sig->ev_signal_added = 1;
}
return (0);
err:
EVSIGBASE_LOCK();
--evsig_base_n_signals_added;
--sig->ev_n_signals_added;
EVSIGBASE_UNLOCK();
return (-1);
}
evsig_add函数做了两件重要的事情:设置信号回调函数,将监听读端套接字事件的ev_signal进行添加。
信号回调函数的设置是通过_evsig_set_handler函数实现的,实际上它内部还是用的signal或者sigaction函数,并且将设置的信号回调函数都放到了sig的sh_old数组中,数组的索引就是信号值。该函数定义如下:
int
_evsig_set_handler(struct event_base *base,
int evsignal, void (__cdecl *handler)(int))
{
#ifdef _EVENT_HAVE_SIGACTION
struct sigaction sa; //存储新的信号处理信息
#else
ev_sighandler_t sh; //信号处理函数指针
#endif
struct evsig_info *sig = &base->sig;
void *p;
/*
* resize saved signal handler array up to the highest signal number.
* a dynamic array is used to keep footprint on the low side.
*/
if (evsignal >= sig->sh_old_max) {//数组大小不够,就进行扩容
int new_max = evsignal + 1;
event_debug(("%s: evsignal (%d) >= sh_old_max (%d), resizing",
__func__, evsignal, sig->sh_old_max));
p = mm_realloc(sig->sh_old, new_max * sizeof(*sig->sh_old));
if (p == NULL) {
event_warn("realloc");
return (-1);
}
memset((char *)p + sig->sh_old_max * sizeof(*sig->sh_old),
0, (new_max - sig->sh_old_max) * sizeof(*sig->sh_old));
sig->sh_old_max = new_max;
sig->sh_old = p;
}
/* allocate space for previous handler out of dynamic array */
sig->sh_old[evsignal] = mm_malloc(sizeof *sig->sh_old[evsignal]);
if (sig->sh_old[evsignal] == NULL) {
event_warn("malloc");
return (-1);
}
//添加信号处理函数
/* save previous handler and setup new handler */
#ifdef _EVENT_HAVE_SIGACTION
memset(&sa, 0, sizeof(sa));
sa.sa_handler = handler;//信号处理函数
sa.sa_flags |= SA_RESTART;//被信号中断的系统调用会自动重启
sigfillset(&sa.sa_mask);//阻塞所有信号
if (sigaction(evsignal, &sa, sig->sh_old[evsignal]) == -1) {//sig->sh_old[evsignal]中保存evsignal原本的信号处理函数
event_warn("sigaction");
mm_free(sig->sh_old[evsignal]);
sig->sh_old[evsignal] = NULL;
return (-1);
}
#else
if ((sh = signal(evsignal, handler)) == SIG_ERR) {
event_warn("signal");
mm_free(sig->sh_old[evsignal]);
sig->sh_old[evsignal] = NULL;
return (-1);
}
*sig->sh_old[evsignal] = sh; //存储信号处理函数
#endif
return (0);
}
通过_evsig_set_handler函数,就把信号sig的回调函数设置为evsig_handler,也就是调用_evsig_set_handler的第三个参数。当信号sig达到时,就会自动调用evsig_handler函数。
evsig_handler函数后面再说,先回到evsig_add函数中。设置好信号回调函数之后,就会将ev_signal通过event_add函数进行添加,前面说过,ev_signal的监听事件类型为EV_READ|EV_PERSIST,它用来监听socket pair中的读端套接字是否有可读事件发生。
也就是说,通过evsig_add函数,不仅设置了信号回调函数,还设置监听读端套接字是否有可读事件发生。
接下来再来看看信号回调函数到底做了什么事。
信号回调函数
evsig_handler函数定义如下所示:
static void __cdecl
evsig_handler(int sig)//信号处理函数,当信号发生时调用该函数
{
int save_errno = errno;
#ifdef WIN32
int socket_errno = EVUTIL_SOCKET_ERROR();
#endif
ev_uint8_t msg;
if (evsig_base == NULL) {
event_warnx(
"%s: received signal %d, but have no base configured",
__func__, sig);
return;
}
#ifndef _EVENT_HAVE_SIGACTION
signal(sig, evsig_handler);
#endif
/* Wake up our notification mechanism */
msg = sig;
send(evsig_base_fd, (char*)&msg, 1, 0);//向写端pair[0]写入信号值,那么读端pair[1]就会收到该信号值
errno = save_errno;
#ifdef WIN32
EVUTIL_SET_SOCKET_ERROR(socket_errno);
#endif
}
这个函数的功能很简单,就是向socket pair中的写端写入一个字节的数据,而这一字节就是信号的值。当这个字节通过socket pair的写端套接字写入之后,socket pair的读端就会读到该字节,读端套接字可读事件发生,监听该事件的ev_signal就会被激活。激活后调用ev_signal的回调函数evsig_cb,该函数定义如下:
static void
evsig_cb(evutil_socket_t fd, short what, void *arg)
{
static char signals[1024];
ev_ssize_t n;
int i;
int ncaught[NSIG];
struct event_base *base;
base = arg;
memset(&ncaught, 0, sizeof(ncaught));
while (1) {
n = recv(fd, signals, sizeof(signals), 0);//从读端pair[1]读取数据,fd是非阻塞的,如果
if (n == -1) {//说明没有数据可读,或者出错了
int err = evutil_socket_geterror(fd);
if (! EVUTIL_ERR_RW_RETRIABLE(err))
event_sock_err(1, fd, "%s: recv", __func__);
break;
} else if (n == 0) {//说明对端关闭
/* XXX warn? */
break;
}
for (i = 0; i < n; ++i) {//读到了多少个字节,说明收到了多少个信号
ev_uint8_t sig = signals[i];
if (sig < NSIG)
ncaught[sig]++;//记录该信号被捕获的次数,捕获多少次就应该激活多少次
}
}
EVBASE_ACQUIRE_LOCK(base, th_base_lock);
for (i = 0; i < NSIG; ++i) {//遍历所有被捕获的信号,将其对应的event激活
if (ncaught[i])
evmap_signal_active(base, i, ncaught[i]);
}
EVBASE_RELEASE_LOCK(base, th_base_lock);
}
evsig_cb会从读端套接字中读取数据到signals数组中,读出的每一个字节都代表一个发生的信号值,用ncaught数组来存储每个信号发生的次数,并且对于每个发生的信号,都调用evmap_signal_active进行处理,可以参考event_signal_map的激活。
信号event的激活
evmap_signal_active主要是找到event_signal_map中,监听信号值为sig的那一个evmap_signal,在这个evmap_signal中,包含了所有监听信号值为sig的event组成的双向链表,然后直接遍历这个双向链表,把每个元素都按照EV_SIGNAL的激活方式调用event_active_nolock函数。
void
evmap_signal_active(struct event_base *base, evutil_socket_t sig, int ncalls)
{
struct event_signal_map *map = &base->sigmap;
struct evmap_signal *ctx;
struct event *ev;
EVUTIL_ASSERT(sig < map->nentries);
GET_SIGNAL_SLOT(ctx, map, sig, evmap_signal); //ctx保存信号值为sig的evmap_signal,也就是一个event的双向链表
TAILQ_FOREACH(ev, &ctx->events, ev_signal_next)
event_active_nolock(ev, EV_SIGNAL, ncalls);//遍历信号值为Sig的event双向链表,将每个event以EV_SIGNAL方式激活
}
而对于event_active_nolock函数来说,做的事情很少,就是把传入的event添加到激活队列中,如下所示:
void
event_active_nolock(struct event *ev, int res, short ncalls)
{
......
event_queue_insert(base, ev, EVLIST_ACTIVE); //将event插入到激活队列中
......
}
到这里,用来监听用户指定的信号值的那个信号event就被添加到了激活队列中,接下来,就等待主循环处理激活队列时去处理那个信号event。
int
event_base_loop(struct event_base *base, int flags)
{
......
if (N_ACTIVE_CALLBACKS(base)) { //如果激活队列中有事件
int n = event_process_active(base); //执行激活队列中的event相应的回调函数,返回的n是成功执行的非内部事件数目
......
}
static int
event_process_active(struct event_base *base)//遍历base的激活队列中所有event,调用其回调函数
{
......
for (i = 0; i < base->nactivequeues; ++i) { //遍历激活队列中的事件
if (TAILQ_FIRST(&base->activequeues[i]) != NULL) { //同一个优先级下可以有多个事件
base->event_running_priority = i; //设置当前的优先级
activeq = &base->activequeues[i]; //获取优先级i下的所有event组成的链表
c = event_process_active_single_queue(base, activeq); //遍历activeq链表,调用其中每个event的回调函数
......
}
}
}
static int
event_process_active_single_queue(struct event_base *base,
struct event_list *activeq)
{
......
switch (ev->ev_closure) { //在调用回调函数是否进行其他行为
case EV_CLOSURE_SIGNAL: //信号事件回调处理方式
event_signal_closure(base, ev);
break;
case EV_CLOSURE_PERSIST: //对于永久事件,在调用回调函数之前会重新调用event_add来添加该事件到对应队列中
event_persist_closure(base, ev);
break;
default:
case EV_CLOSURE_NONE: //对于一般事件,直接调用回调函数
EVBASE_RELEASE_LOCK(base, th_base_lock);//释放锁
(*ev->ev_callback)(
ev->ev_fd, ev->ev_res, ev->ev_arg); //调用回调函数
break;
}
......
}
激活处理的过程就是event_base_loop——event_process_active——event_process_active_single_queue,在event_process_active_single_queue函数中,会判断激活处理事件的回调关闭方式ev_closure,而对于信号event来说,在evsignal_new时由于传入的参数为EV_SIGNAL|EV_PERSIST,因此evsignal_new内部调用的event_assign会直接设置信号event的ev_closure为EV_CLOSURE_SIGNAL,也就是说,处理激活的信号event最终是通过event_signal_closure函数实现的,该函数定义如下:
static inline void
event_signal_closure(struct event_base *base, struct event *ev)
{
......
while (ncalls) {//信号发生了多少次就调用多少次回调函数
ncalls--;
ev->ev_ncalls = ncalls;
if (ncalls == 0)
ev->ev_pncalls = NULL;
(*ev->ev_callback)(ev->ev_fd, ev->ev_res, ev->ev_arg);
......
}
}
这里的ncalls,实际上就是evsig_cb函数中记录的信号值sig的发生次数,信号event激活时只处理一次,但是在这一次中会根据信号发生的次数来决定调用多少次回调函数,而这里的回调函数就是用户最开始设定的“当信号发生时应当调用的函数了”。
————————————————
版权声明:本文为CSDN博主「HerofH_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_28114615/article/details/97657766
libevent源码学习(15):信号event的处理的更多相关文章
- libevent源码学习
怎么快速学习开源库比如libevent? libevent分析 - sparkliang的专栏 - 博客频道 - CSDN.NET Libevent源码分析 - luotuo44的专栏 - 博客频道 ...
- libevent源码学习(9):事件event
目录在event之前需要知道的event_baseevent结构体创建/注册一个event向event_base中添加一个event设置event的优先级激活一个event删除一个event获取指定e ...
- libevent源码学习之event
timer event libevent添加一个间隔1s持续触发的定时器如下: struct event_base *base = event_base_new(); struct event *ti ...
- libevent源码学习(8):event_signal_map解析
目录event_signal_map结构体向event_signal_map中添加event激活event_signal_map中的event删除event_signal_map中的event以下源码 ...
- libevent源码学习(6):事件处理基础——event_base的创建
目录前言创建默认的event_baseevent_base的配置event_config结构体创建自定义event_base--event_base_new_with_config禁用(避免使用)某一 ...
- libevent源码学习(7):event_io_map
event_io_map 哈希表操作函数 hashcode与equals函数 哈希表初始化 哈希表元素查找 哈希表扩容 哈希表元素插入 哈希表元素替换 哈希表元素删除 自定义条件删除元素 哈希表第一个 ...
- libevent源码学习(11):超时管理之min_heap
目录min_heap的定义向min_heap中添加eventmin_heap中event的激活以下源码均基于libevent-2.0.21-stable. 在前文中,分析了小顶堆min_h ...
- libevent源码学习(10):min_heap数据结构解析
min_heap类型定义min_heap函数构造/析构函数及初始化判断event是否在堆顶判断两个event之间超时结构体的大小关系判断堆是否为空及堆大小返回堆顶event分配堆空间堆元素的上浮堆元素 ...
- libevent源码学习(2):内存管理
目录 内存管理函数 函数声明 event-config.h 函数定义 event_mm_malloc_ event_mm_calloc_ event_mm_strdup_ event_mm_reall ...
随机推荐
- 洛谷 P3580 - [POI2014]ZAL-Freight(单调队列优化 dp)
洛谷题面传送门 考虑一个平凡的 DP:我们设 \(dp_i\) 表示前 \(i\) 辆车一来一回所需的最小时间. 注意到我们每次肯定会让某一段连续的火车一趟过去又一趟回来,故转移可以枚举上一段结束位置 ...
- Codeforces 1373F - Network Coverage(模拟网络流)
Codeforces 题面传送门 & 洛谷题面传送门 提供一个模拟网络流的题解. 首先我们觉得这题一脸可以流的样子,稍微想想可以想到如下建图模型: 建立源点 \(S,T\) 和上下两排点,不妨 ...
- 洛谷 P7360 -「JZOI-1」红包(Min-Max 容斥+推式子)
洛谷题面传送门 hot tea. 首先注意到这个 \(\text{lcm}\) 特别棘手,并且这里的 \(k\) 大得离谱,我们也没办法直接枚举每个质因子的贡献来计算答案.不过考虑到如果我们把这里的 ...
- EXCEL ctrl+e 百变用法不只是你用的那么简单
Excel2013版本中,新增加了一个快捷键:Ctrl+E,可以依据字符之间的关系,实现快速填充功能.一些需要使用公式或者其他功能进行解决的问题,现在只要一个快捷键就可以实现了. 用法1:快速拆解出需 ...
- 通过yum安装 memcache
. 通过yum安装 复制代码代码如下: yum -y install memcached#安装完成后执行:memcached -h#出现memcached帮助信息说明安装成功 2. 加入启动服务 复制 ...
- C#点击按钮添加标签
<asp:Button ID="button1" runat="server" Text="创建" onclick="But ...
- Spark基础:(一)初识Spark
1.Spark中的Python和Scala的Shell (1): Python的Spark Shell 也就是我们常说的PySpark Shell进入我们的Spark目录中然后输入 bin/pyspa ...
- 在 Apple Silicon Mac 上 DFU 模式恢复 macOS 固件
DFU 模式全新安装 macOS Big Sur 或 macOS Monterey 请访问原文链接:https://sysin.org/blog/apple-silicon-mac-dfu/,查看最新 ...
- FileReader (三) - 网页拖拽并预显示图片简单实现
以下是一个很贱很简单的一个 在网页上图拽图片并预显示的demo. 我是从https://developer.mozilla.org/en-US/docs/Web/API/FileReader#Stat ...
- 【vector+pair】洛谷 P4715 【深基16.例1】淘汰赛
题目:P4715 [深基16.例1]淘汰赛 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 这道题因为数据范围不大,所以做法可以非常简单,使用一个vector加上pair就可以了: ...