Linux signalfd 原理( 二 )


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 的读操作很简单,主要操作如下:
  • 查看信号队列中是否有信号,若有信号取出信号,并返回给用户 。
  • 若句柄是阻塞类型的,在没有信号的情况下,则进程进入休眠,直到有信号到来 。
  • 若句柄是非阻塞类型的,则直接返回 EAGAIN 。
  •  
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);


推荐阅读