等待队列(Wait Queue)应用之---eventfd

eventfd

ventfd是Linux一种轻量化的线程间通信方式。eventfd(2)会产生一个”eventfd object”,该对象包含一个uint64_t的计数器。使用eventfd(2)成功后会返回一个fd。在使用阻塞I/O时,eventfd的逻辑如下:

  • 对eventfd进行read
    • 如果计数器的值为0,则该次read调用阻塞,直到计数器的值不为零。
    • 如果计数器的值不为0,则将计数器的值置0,并返回原来原来计数器的值。
  • 对eventfd进行write
    • 将buffer中的数值加到计数器上,如果相加后计数器的值超过的UINT64_MAX,则write阻塞直到有其他进程调用read

eventfd的“阻塞”功能,是通过wait_queue来实现的,下面我们来分析eventfd的一部分的代码。

eventfd的数据结构与初始化

struct eventfd_ctx {
  struct kref kref;
  wait_queue_head_t wqh;
  __u64 count;
  unsigned int flags;
  int id;
};

wqh是一个等待队列头wait_queue_head_t,而计数器就是count

eventfd读取数据

eventfd(2)系统调用通过do_eventfd来完成evnetfd的创建和初始化:

  • #1 对等待队列头进行了初始化。
  • #2 取得一个未使用的文件描述符
  • #3 创建一个匿名inode
  • #4 将该fd和匿名inode安装到当前线程上
static int do_eventfd(unsigned int count, int flags)
{
  struct eventfd_ctx *ctx;
  struct file *file;
  int fd;

  /* Check the EFD_* constants for consistency.  */
  BUILD_BUG_ON(EFD_CLOEXEC != O_CLOEXEC);
  BUILD_BUG_ON(EFD_NONBLOCK != O_NONBLOCK);

  if (flags & ~EFD_FLAGS_SET)
    return -EINVAL;

  ctx = kmalloc(sizeof(*ctx), GFP_KERNEL);
  if (!ctx)
    return -ENOMEM;

  kref_init(&ctx->kref);
  init_waitqueue_head(&ctx->wqh); // #1 
  ctx->count = count;
  ctx->flags = flags;
  ctx->id = ida_simple_get(&eventfd_ida, 0, 0, GFP_KERNEL);

  flags &= EFD_SHARED_FCNTL_FLAGS;
  flags |= O_RDWR;
  fd = get_unused_fd_flags(flags); // #2
  if (fd < 0)
    goto err;

  file = anon_inode_getfile("[eventfd]", &eventfd_fops, ctx, flags); // #3
  if (IS_ERR(file)) {
    put_unused_fd(fd);
    fd = PTR_ERR(file);
    goto err;
  }

  file->f_mode |= FMODE_NOWAIT;
  fd_install(fd, file); // #4
  return fd;
err:
  eventfd_free_ctx(ctx);
  return fd;
}

SYSCALL_DEFINE2(eventfd2, unsigned int, count, int, flags)
{
  return do_eventfd(count, flags);
}

SYSCALL_DEFINE1(eventfd, unsigned int, count)
{
  return do_eventfd(count, 0);
}

eventfd并没有使用之前介绍的封装好的wait_eventwake_up系列macro,而是使用DECLARE_WAITQUEUE__add_wait_queue等自行维护wait_queue

  • #1 在栈上静态初始化一个wait_queue_entry。其回调函数为default_wake_function

  • #2 不允许读取的内容少于8个字节

  • #3 获得等待队列的自旋锁,该自旋锁不但保护等待队列,也保护counter。可能这就是重新实现一次wait_queue逻辑的原因。因为如果我们使用封装好的wait_evnet系列宏,就可能需要另一个同步原语来保证计数器的读写。
  • #4 如果计数器的值大于零,不需等待,直接跳到#14,遍历等待队列,并唤醒等待队列上所有的task。
  • #5 如果计数器的值为零,且不是非阻塞I/O,就进行将该task加入到等待队列的一系列工作,首先将之前声明在栈上的wait_queue_entry放入等待队列
  • #6 开始进入循环,每次程序被唤醒时从schedule()中返回,并从#11处开始执行
  • #7 将该task状态设置为TASK_INTERRUPTIBLE,准备将该进程置于interruptible sleep
  • #8 检查counter,如果不为零,就结束sleep
  • #9 因为处于interruptible sleep,检查信号,如果有待处理的信号,返回-ERESTARTSYS
  • #10 马上要放弃CPU进入sleep,因为绝对不可带着自旋锁进入休眠,因此将释放自旋锁。
  • #11 当从该task被唤醒,并从schedule()返回后,重新取得自旋锁,进行#7,#8,#9,#10重新进行条件检查。

由此可见,其逻辑基本和wait_event_interruptible相同。

static ssize_t eventfd_read(struct file *file, char __user *buf, size_t count,
          loff_t *ppos)
{
  struct eventfd_ctx *ctx = file->private_data;
  ssize_t res;
  __u64 ucnt = 0;
  DECLARE_WAITQUEUE(wait, current); // #1 

  if (count < sizeof(ucnt)) // #2
    return -EINVAL;

  spin_lock_irq(&ctx->wqh.lock); // #3
  res = -EAGAIN;
  if (ctx->count > 0) // #4
    res = sizeof(ucnt);
  else if (!(file->f_flags & O_NONBLOCK)) {
    __add_wait_queue(&ctx->wqh, &wait); // #5
    for (;;) {  // #6
      set_current_state(TASK_INTERRUPTIBLE);  // #7
      if (ctx->count > 0) { // #8
        res = sizeof(ucnt);
        break;
      }
      if (signal_pending(current)) { // #9
        res = -ERESTARTSYS;
        break;
      }
      spin_unlock_irq(&ctx->wqh.lock); // #10 
      schedule();
      spin_lock_irq(&ctx->wqh.lock); // #11
    }
    __remove_wait_queue(&ctx->wqh, &wait); // #12
    __set_current_state(TASK_RUNNING); // #13
  }
  if (likely(res > 0)) {
    eventfd_ctx_do_read(ctx, &ucnt);
    if (waitqueue_active(&ctx->wqh)) // #14
      wake_up_locked_poll(&ctx->wqh, EPOLLOUT); // #15
  }
  spin_unlock_irq(&ctx->wqh.lock);

  if (res > 0 && put_user(ucnt, (__u64 __user *)buf))
    return -EFAULT;

  return res;
}
// /include/linux/wait.h

#define wake_up_locked_poll(x, m)            \
    __wake_up_locked_key((x), TASK_NORMAL, poll_to_key(m))


// /kernel/sched/wait.c

void __wake_up_locked_key(struct wait_queue_head *wq_head, unsigned int mode, void *key)
{
  __wake_up_common(wq_head, mode, 1, 0, key, NULL);
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值