<返回更多

Linux signalfd 原理

2023-02-17  今日头条  Linux码农
加入收藏

信号是什么?

 

信号是事件发送时对进程的一种通知机制。有时也称之为软件中断。信号与硬件中断的相似之处在于打断了程序执行的正常流程,大多情况下,无法预测信号到达的精确时间。

一个具有合适权限的进程不仅能够向另一进程发送信号,也可以向自身发送信号。然而,发往进程的诸多信号,通常都是源于内核。

linux 信号可由如下条件产生:

服务器程序必须处理(或至少忽略)一些常见的信号,以免异常终止。

 

signalfd 是什么?

 

signalfd 是一个将信号抽象的文件描述符,将信号的异步处理转换为文件的I/O 操作。通过文件描述符就绪的方法来通知信号的到来,当有信号发生时可以对其 read,这样可以将信号的监听放到 select、poll、epoll 等监听队列中。

通过文件描述符就绪的方法来通知信号的到来,当有信号发生时可以对其read,这样可以将信号的监听放到 select、poll、epoll 等监听队列中。

signalfd 的系统调用接口

#include <sys/signalfd.h>
int signalfd(int fd, const sigset_t *mask, int flags);

 

创建并返回一个用于所受信号的文件描述符。

mask:信号的集合,这里主要是你想监听的信号的集合。

flags 可以使用以下标志位进行或(or)的结果:

 

获取 signalfd 文件描述符后,我们来查看一下可以对其做哪些操作。

 

static const struct file_operations signalfd_fops = {
#ifdef CONFIG_PROC_FS
.show_fdinfo = signalfd_show_fdinfo,
#endif
.release = signalfd_release,
.poll = signalfd_poll,
.read = signalfd_read,
.llseek = noop_llseek,
};

 

通过上面 signalfd 实现的调用可知, 我们可以对 eventfd 进行 read、poll、close 等操作。

 

下面通过一个例子来了解下 signalfd 的使用方式,具体完整代码可通过 man signalfd 获取

 

int main(int argc, char *argv[])
{
...
//初始化信号集
sigemptyset(&mask);
//添加信号到信号集中
sigaddset(&mask, SIGINT);
sigaddset(&mask, SIGQUIT);
//将mask所指向的信号集中所包含的信号加到当前的信号掩码中,作为新的信号屏蔽字,关闭内核的默认行为。
sigprocmask(SIG_BLOCK, &mask, NULL)
//创建 signalfd 文件描述符
sfd = signalfd(-1, &mask, 0);
for (;;) {
//阻塞等待信号发生并读取。根据读取的结果可以知道发生了什么信号
s = read(sfd, &fdsi, sizeof(fdsi));
if (fdsi.ssi_signo == SIGINT) {
printf("Got SIGINTn");
} else if (fdsi.ssi_signo == SIGQUIT) {
printf("Got SIGQUITn");
exit(EXIT_SUCCESS);
} else {
printf("Read unexpected signaln");
}
}
}

 

当没有信号时,进程阻塞在 read 调用上,当有信号发生时,结果如下:

 

$ ./signalfd_demo
^C # Control-C generates SIGINT
Got SIGINT
^C
Got SIGINT
^ # Control- generates SIGQUIT
Got SIGQUIT
$

 

每次 Control + C,进程都会捕获到一次信号,并打印具体信息。

通过如下查看,得到 signalfd 其实也是一个匿名 fd 类型。

[root@localhost ~]# ll /proc/48356/fd/
lrwx------ 1 root root 64 5月 23 11:54 3 ->anon_inode:[signalfd]

 

signalfd 源码解析

 

接下来我们通过分析源码的方式来探究 signalfd 的底层实现原理。

 

signalfd ( signalfd4 )

SYSCALL_DEFINE3(signalfd, int, ufd, sigset_t __user *, user_mask, size_t, sizemask)
{
...
return do_signalfd4(ufd, &mask, 0);
}
SYSCALL_DEFINE4(signalfd4, int, ufd, sigset_t __user *, user_mask, size_t, sizemask, int, flags)
{
...
return do_signalfd4(ufd, &mask, flags);
}
static int do_signalfd4(int ufd, sigset_t *mask, int flags)
{
struct signalfd_ctx *ctx;
sigdelsetmask(mask, sigmask(SIGKILL) | sigmask(SIGSTOP));
signotset(mask);
//内核新创建signalfd
if (ufd == -1) {
//创建一个signalfd_ctx内核结构
ctx = kmalloc(sizeof(*ctx), GFP_KERNEL);
//设置信号集合
ctx->sigmask = *mask;
//获取一个匿名句柄,file->f_op 设置为 signalfd_fops
ufd = anon_inode_getfd("[signalfd]", &signalfd_fops, ctx, O_RDWR | (flags & (O_CLOEXEC | O_NONBLOCK)));
} else { //已经创建signalfd
//合法性检查
struct fd f = fdget(ufd);
//设置为新的值
ctx->sigmask = *mask;
//唤醒阻塞在当前进程的信号等待队列
wake_up(¤t->sighand->signalfd_wqh);
fdput(f);
}
return ufd;
}

 

signalfd 的操作就是创建或者修改内核结构 signalfd_ctx,signalfd 本身也是一个匿名句柄。

对于 signalfd_ctx 内核结构,就只有一个字段,该字段记录用户设置的信号集合。

struct signalfd_ctx {
sigset_t sigmask;
};

signalfd_read

static ssize_t signalfd_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
int nonblock = file->f_flags & O_NONBLOCK;
count /= sizeof(struct signalfd_siginfo);
//存放读取到的数据结构
siginfo = (struct signalfd_siginfo __user *) buf;
do {
//从信号队列中获取一个信号,然后填充到info中
ret = signalfd_dequeue(ctx, &info, nonblock);
//把获取到的信号填充到返回给用户的数据结构中
ret = signalfd_copyinfo(siginfo, &info);
siginfo++;
total += ret;
nonblock = 1;
} while (--count);
return total ? total: ret;
}
static ssize_t signalfd_dequeue(struct signalfd_ctx *ctx, kernel_siginfo_t *info,
int nonblock)
{
ssize_t ret;
DECLARE_WAITQUEUE(wait, current);
spin_lock_irq(¤t->sighand->siglock);
//从挂起信号队列中获取信号
ret = dequeue_signal(current, &ctx->sigmask, info);
switch (ret) {
case 0: //若没有信号,判断是否需要阻塞
if (!nonblock)
break; //阻塞,跳出,往下走进行休眠
ret = -EAGAIN; //非阻塞,往下走到default,函数返回
default:
spin_unlock_irq(¤t->sighand->siglock);
return ret;
}
//把当前进程加入信号等待队里中
add_wait_queue(¤t->sighand->signalfd_wqh, &wait);
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
//从挂起信号队列中获取信号
ret = dequeue_signal(current, &ctx->sigmask, info);
//存在信号,跳出循环,退出
if (ret != 0)
break;
//检查当前进程是否有信号处理,返回不为0表示有信号需要处理
if (signal_pending(current)) {
ret = -ERESTARTSYS;
break;
}
spin_unlock_irq(¤t->sighand->siglock);
schedule(); //进程调度,进入休眠
spin_lock_irq(¤t->sighand->siglock);
}
spin_unlock_irq(¤t->sighand->siglock);
//把当前进程从等待队列中删除
remove_wait_queue(¤t->sighand->signalfd_wqh, &wait);
__set_current_state(TASK_RUNNING);
return ret;
}

 

signalfd 的读操作很简单,主要操作如下:

signalfd_poll

static __poll_t signalfd_poll(struct file *file, poll_table *wait)
{
struct signalfd_ctx *ctx = file->private_data;
__poll_t events = 0;
//把一个wait等待队列挂到当前进程的信号等待队列signalfd_wqh,其回调函数为ep_poll_callback
poll_wait(file, ¤t->sighand->signalfd_wqh, wait);
...
return events;
}


 

该函数的操作就是把 wait对象直接挂到当前进程的信号等待队列signalfd_wqh 中,对比 timerfd 来讲,区别在于 timerfd 的 wait 对象是挂到 timerfd_ctx->wqh 链表中。(详情参看定时器timerfd原理)

 

signalfd 与 epoll 的结合

 

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>