libevent之signal事件

最近接触libevent,拜读“sparkliang”十年前的博文,受益匪浅。但学习中对比libevent-2.1.8 src发现,随着版本演变,文中有些内容不再适用。博客本是很主观的东西,作者书写时的心境已经随风而逝。后来者可进入其当时的心境,但是不可一味盲从甚至奉若神明。

libevent可将IO、定时、信号等事件纳入到主event处理流程event_base_dispatch。现以epoll IO复用为例,梳理下linux下对signal的集成过程。

最开始可追溯到event_init的执行。此函数的功用是new一个struct event_base* base,并初始化某些成员。在这过程中base->evsel = epollops,

const struct eventop epollops = {
	"epoll",
	epoll_init,
	epoll_nochangelist_add,
	epoll_nochangelist_del,
	epoll_dispatch,
	epoll_dealloc,
	1, /* need reinit */
	EV_FEATURE_ET|EV_FEATURE_O1|EV_FEATURE_EARLY_CLOSE,
	0
};

epoll_init也在event_init中被调用,为接下来的架构运行构建基础设施。epoll_init篇幅较长,跟signal有关是它进行了evsig_init_,

int
evsig_init_(struct event_base *base)
{
	/*
	 * Our signal handler is going to write to one end of the socket
	 * pair to wake up our event loop.  The event loop then scans for
	 * signals that got delivered.
	 */
	if (evutil_make_internal_pipe_(base->sig.ev_signal_pair) == -1) {
#ifdef _WIN32
		/* Make this nonfatal on win32, where sometimes people
		   have localhost firewalled. */
		event_sock_warn(-1, "%s: socketpair", __func__);
#else
		event_sock_err(1, -1, "%s: socketpair", __func__);
#endif
		return -1;
	}

	if (base->sig.sh_old) {
		mm_free(base->sig.sh_old);
	}
	base->sig.sh_old = NULL;
	base->sig.sh_old_max = 0;

	event_assign(&base->sig.ev_signal, base, base->sig.ev_signal_pair[0],
		EV_READ | EV_PERSIST, evsig_cb, base);

	base->sig.ev_signal.ev_flags |= EVLIST_INTERNAL;
	event_priority_set(&base->sig.ev_signal, 0);

	base->evsigsel = &evsigops;

	return 0;
}

可以留意到有三个地方:

1、evutil_make_internal_pipe_创建了文件描述符对(管道)(socket pair或者pipe2),并赋值给base->sig.ev_signal_pair,用于信号处理函数(下文会提及)和event_base_dispatch事件loop交互。

2、event_assign,构造一个信号struct event,赋给base->sig.ev_signal(回调函数是evsig_cb,注意此时并没有event_add)。

3、初始化base->evsigsel为evsigops。这个用在event_add 函数处理signal时回调。

static const struct eventop evsigops = {
	"signal",
	NULL,
	evsig_add,
	evsig_del,
	NULL,
	NULL,
	0, 0, 0
};

evsig_add主要完成两大功能,

1、注册了通用signal的信号处理函数evsig_handler。evsig_handler简单将signal num写入管道写fd

2、event_add_nolock_增加对signal event 管道读fd的监控,获取到达的signal num。

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;
#ifdef _WIN32
	send(evsig_base_fd, (char*)&msg, 1, 0);
#else
	{
		int r = write(evsig_base_fd, (char*)&msg, 1);
		(void)r; /* Suppress 'unused return value' and 'unused var' */
	}
#endif
	errno = save_errno;
#ifdef _WIN32
	EVUTIL_SET_SOCKET_ERROR(socket_errno);
#endif
}

如此,基础设施构造完成。下面以文字形式简述signal到来之后发生的种种。

1、signal产生后,evsig_handler被调用,写signal num(char类型消息)到管道写fd。

2、程序主事件loop中对IO处理调用epoll_dispatch,检测到管道读fd可读。经过一系列处理(例如放入active queue),调用event callback - evsig_cb。

3、evsig_cb通过读取管道读fd,获知signal num。再根据signal num检索到用户注册的signal callback,并进行调用。

最后附上evsig_cb的源码

/* Callback for when the signal handler write a byte to our signaling socket */
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) {
#ifdef _WIN32
		n = recv(fd, signals, sizeof(signals), 0);
#else
		n = read(fd, signals, sizeof(signals));
#endif
		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) {
		if (ncaught[i])
			evmap_signal_active_(base, i, ncaught[i]);
	}
	EVBASE_RELEASE_LOCK(base, th_base_lock);
}

 

你可能感兴趣的:(Linux,libevent,signal,linux)