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 ...
随机推荐
- Linux下Zabbix5.0 LTS + Grafana8.2.2图形可视化
Grafana是一款开源的可视化软件,可以搭配数据源实现一个数据的展示和分析:Grafana功能强大,有着丰富的插件,但Grafana默认没有zabbix作为数据源,需要手动给zabbix安装一个插件 ...
- PHP 日期详细介绍
简介 你可以使用这些函数获取运行 PHP 的服务器的日期和时间, 也可以使用这些函数把日期和时间 格式化成不同格式的字符串. 日期和时间信息在 PHP 内部是以 64 位数字存储的, 它可以覆盖当前时 ...
- Codeforces 521E - Cycling City(点双连通分量+分类讨论)
Codeforces 题面传送门 & 洛谷题面传送门 大家都是暴力找生成树然后跳路径,代码不到 50 行(暴论)的一说--好,那本蒟蒻决定提供一种代码 150 行,但复杂度也是线性的分类讨论做 ...
- Atcoder Regular Contest 096 D - Sweet Alchemy(贪心+多重背包)
洛谷题面传送门 & Atcoder 题面传送门 由于再过 1h 就是 NOI 笔试了所以题解写得会略有点简略. 考虑差分,记 \(b_i=c_i-c_{fa_i}\),那么根据题意有 \(b_ ...
- O(1)判断两点之间是否有边
O(1)判断两点之间是否有边 问题描述 给定一张 \(n\) 个点,\(m\) 条边的有向图. 多次询问,要求每次 \(\mathcal{O}(1)\) 判断两点之间是否有边(你可以忽略输入.输出等问 ...
- ceph简单了解
ceph简介 ceph是一个统一的分布式存储系统,设计初衷是提供较好的性能.可靠性和可扩展性. 目前已经得到众多云计算厂商的支持并被广泛应用.RedHat及OpenStack都可以与Ceph整合以支持 ...
- you crash I crash
今天一大早起来,zabbix报错了 我去查看了mysql的状态 MySQL is not running, but lock file (/var/lock/subsys/mysql) exists ...
- 巩固java第四天
巩固内容: HTML 元素 HTML 文档由 HTML 元素定义. HTML 元素 开始标签 * 元素内容 结束标签 * <p> 这是一个段落 </p> <a href= ...
- acupuncture
acute+puncture. [woninstitute.edu稻糠亩] To understand the basics of acupuncture, it is best to familia ...
- 关于redis HSCAN count参数不生效的问题
这的确是个坑,HSCAN是为了处理大量数据而设计的,可能也是因为这个原因,在数据量较少的情况下count参数并不会生效,具体阈值是多少并没有实际测验过不过可以断定的是一百条数据一下估计是不会生效的.