在看 qemu_init_main_loop 函数的时候发现 qemu_signal_init 里竟然会调用 qemu_set_fd_handler 添加 ctx的io_handlers, 设置 fd对应的处理函数,于是看了下 qemu_signal_init ,原来是 信号线程化的,使信号可重入,使信号处理函数可以更复杂。
同时这里还使用了 signal_fd,原来现在内核里已实现了信号fd,以前都是用管道,用户态实现通知,qemu为了兼容,两种都实现了,下面分析代码:
qemu_init_main_loop 函数:
int qemu_init_main_loop(Error **errp)
{
int ret;
GSource *src;
Error *local_error = NULL;
init_clocks(qemu_timer_notify_cb);
ret = qemu_signal_init(); 初始化信号处理函数
if (ret) {
return ret;
}
qemu_aio_context = aio_context_new(&local_error); //创建事件源
if (!qemu_aio_context) {
error_propagate(errp, local_error);
return -EMFILE;
}
qemu_notify_bh = qemu_bh_new(notify_event_cb, NULL); //创建 底半部
gpollfds = g_array_new(FALSE, FALSE, sizeof(GPollFD));
src = aio_get_g_source(qemu_aio_context);
g_source_set_name(src, "aio-context");
g_source_attach(src, NULL); //事件源添加到上下文
g_source_unref(src);
src = iohandler_get_g_source();
g_source_set_name(src, "io-handler");
g_source_attach(src, NULL);
g_source_unref(src);
return 0;
}
qemu_signal_init, 先屏蔽信号,然后调用 qemu_signalfd 获得信号通知的 fd,qemu_signalfd是后面的重点。
static int qemu_signal_init(void)
{
int sigfd;
sigset_t set;
/*
* SIG_IPI must be blocked in the main thread and must not be caught
* by sigwait() in the signal thread. Otherwise, the cpu thread will
* not catch it reliably.
*/
sigemptyset(&set);
sigaddset(&set, SIG_IPI);
sigaddset(&set, SIGIO);
sigaddset(&set, SIGALRM);
sigaddset(&set, SIGBUS);
/* SIGINT cannot be handled via signalfd, so that ^C can be used
* to interrupt QEMU when it is being run under gdb. SIGHUP and
* SIGTERM are also handled asynchronously, even though it is not
* strictly necessary, because they use the same handler as SIGINT.
*/
pthread_sigmask(SIG_BLOCK, &set, NULL);
sigdelset(&set, SIG_IPI);
sigfd = qemu_signalfd(&set);
if (sigfd == -1) {
fprintf(stderr, "failed to create signalfd\n");
return -errno;
}
fcntl_setfl(sigfd, O_NONBLOCK);
qemu_set_fd_handler(sigfd, sigfd_handler, NULL, (void *)(intptr_t)sigfd);
return 0;
}
qemu_signalfd 如果定义了 CONFIG_SIGNALFD,则使用系统调用 syscall SYS_signalfd 获得对应的信号通知 fd,如果没有定义则进入qemu_signalfd_compat 函数,这个函数就是使用创建管道和线程sigwait_compat,返回管道 读端。
static int qemu_signalfd_compat(const sigset_t *mask)
{
struct sigfd_compat_info *info;
QemuThread thread;
int fds[2];
info = malloc(sizeof(*info));
if (info == NULL) {
errno = ENOMEM;
return -1;
}
if (pipe(fds) == -1) {
free(info);
return -1;
}
qemu_set_cloexec(fds[0]);
qemu_set_cloexec(fds[1]);
memcpy(&info->mask, mask, sizeof(*mask));
info->fd = fds[1];
qemu_thread_create(&thread, "signalfd_compat", sigwait_compat, info,
QEMU_THREAD_DETACHED);
return fds[0];
}
int qemu_signalfd(const sigset_t *mask)
{
#if defined(CONFIG_SIGNALFD)
int ret;
ret = syscall(SYS_signalfd, -1, mask, _NSIG / 8);
if (ret != -1) {
qemu_set_cloexec(ret);
return ret;
}
#endif
return qemu_signalfd_compat(mask);
}
sigwait_compat 里使用 sigwait 获取pending的信号,并写入管道写端,从而实现通知管道读端,然后glib 循环获取事件,执行sigfd_handler 函数。
static void *sigwait_compat(void *opaque)
{
struct sigfd_compat_info *info = opaque;
while (1) {
int sig;
int err;
err = sigwait(&info->mask, &sig);
if (err != 0) {
if (errno == EINTR) {
continue;
} else {
return NULL;
}
} else {
struct qemu_signalfd_siginfo buffer;
size_t offset = 0;
memset(&buffer, 0, sizeof(buffer));
buffer.ssi_signo = sig;
while (offset < sizeof(buffer)) {
ssize_t len;
len = write(info->fd, (char *)&buffer + offset,
sizeof(buffer) - offset);
if (len == -1 && errno == EINTR)
continue;
if (len <= 0) {
return NULL;
}
offset += len;
}
}
}
}