最开始听说eventfd的时候是在virtio vhost驱动那块听说的。以为很高深的一个技术,没有细看。
最近在了解select poll epoll的时候有看到这个东西,于是决定好好看看eventfd这个模块的代码。
代码路径:fs/eventfd.c
系统调用:eventfd1 eventfd2
使用逻辑:首先程序调用eventfd*系统调用,创建eventfd文件描述符。然后通过对文件描述符的read和write来做进程间通讯。由于eventfd模块的文件操作指针中有poll函数因此可以使用select poll epoll来等待。逻辑简单。具体看代码分析。
关键数据结构
struct eventfd_ctx {
struct kref kref; //对象引用计数
wait_queue_head_t wqh; //等待队列首地址
/*
* Every time that a write(2) is performed on an eventfd, the
* value of the __u64 being written is added to "count" and a
* wakeup is performed on "wqh". A read(2) will return the "count"
* value to userspace, and will reset "count" to zero. The kernel
* side eventfd_signal() also, adds to the "count" counter and
* issue a wakeup.
*/
__u64 count; //read write 操作的计数
unsigned int flags; //eventfd 文件特性描述
};
创建eventfd文件描述符
SYSCALL_DEFINE2(eventfd2, unsigned int, count, int, flags)
{
int fd, error;
struct file *file;
error = get_unused_fd_flags(flags & EFD_SHARED_FCNTL_FLAGS); //获取空闲描述符
if (error < 0)
return error;
fd = error;
file = eventfd_file_create(count, flags); //创建eventfd文件对象
if (IS_ERR(file)) {
error = PTR_ERR(file);
goto err_put_unused_fd;
}
fd_install(fd, file); //文件描述符和eventfd文件对象关联
return fd;
err_put_unused_fd:
put_unused_fd(fd);
return error;
}
struct file *eventfd_file_create(unsigned int count, int flags)
{
struct file *file;
struct eventfd_ctx *ctx;
/* 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 ERR_PTR(-EINVAL);
ctx = kmalloc(sizeof(*ctx), GFP_KERNEL); //分配eventfd文件私有数据结构内存。
if (!ctx)
return ERR_PTR(-ENOMEM);
kref_init(&ctx->kref);
init_waitqueue_head(&ctx->wqh);
ctx->count = count;
ctx->flags = flags;
file = anon_inode_getfile("[eventfd]", &eventfd_fops, ctx,
O_RDWR | (flags & EFD_SHARED_FCNTL_FLAGS));//创建匿名inode 文件,即创建一个文件,但是这个文件的挂载点是匿名的。表明这个文件只是内存中存在,硬盘中不存在。一个虚拟文件系统
if (IS_ERR(file))
eventfd_free_ctx(ctx);
return file;
}
eventfd_fops数据如下
static const struct file_operations eventfd_fops = {
#ifdef CONFIG_PROC_FS
.show_fdinfo = eventfd_show_fdinfo,
#endif
.release = eventfd_release,
.poll = eventfd_poll, //文件poll操作,给select epoll使用
.read = eventfd_read, //文件读操作
.write = eventfd_write, //文件写操作
.llseek = noop_llseek,
};
至此,虚拟evnetfd 文件已经创建完,对应的文件描述符也返回给用户程序。
现在分析 对改文件的读写操作。首先是写操作
static ssize_t eventfd_write(struct file *file, const char __user *buf, size_t count,
loff_t *ppos)
{
struct eventfd_ctx *ctx = file->private_data;
ssize_t res;
__u64 ucnt;
DECLARE_WAITQUEUE(wait, current); //初始化等待变量
if (count < sizeof(ucnt))
return -EINVAL;
if (copy_from_user(&ucnt, buf, sizeof(ucnt))) //拷贝要写入的值大小
return -EFAULT;
if (ucnt == ULLONG_MAX)
return -EINVAL;
spin_lock_irq(&ctx->wqh.lock); //自旋锁,禁止中断
res = -EAGAIN;
if (ULLONG_MAX - ctx->count > ucnt)
res = sizeof(ucnt);
else if (!(file->f_flags & O_NONBLOCK)) { //判断是否阻塞,如果不阻塞跳过等待可写状态代码。
__add_wait_queue(&ctx->wqh, &wait); //将wait添加到等待队列中
for (res = 0;;) {
set_current_state(TASK_INTERRUPTIBLE);
if (ULLONG_MAX - ctx->count > ucnt) {
res = sizeof(ucnt);
break;
}
if (signal_pending(current)) {
res = -ERESTARTSYS;
break;
}
spin_unlock_irq(&ctx->wqh.lock);
schedule(); //切换到其他进程执行,当前进程挂起
spin_lock_irq(&ctx->wqh.lock);
}
__remove_wait_queue(&ctx->wqh, &wait); //进程不需要等待,醒来。
__set_current_state(TASK_RUNNING); //设置当前进程为运行状态
}
if (likely(res > 0)) {
ctx->count += ucnt; //写入数据
if (waitqueue_active(&ctx->wqh)) //判断等待队列是否为空
wake_up_locked_poll(&ctx->wqh, POLLIN); //唤醒等待读的进程。
}
spin_unlock_irq(&ctx->wqh.lock); //解除自旋锁
return res; //返回写入的字节数
}
wake_up_locked_poll 函数函数体
static int __wake_up_common(struct wait_queue_head *wq_head, unsigned int mode,
int nr_exclusive, int wake_flags, void *key,
wait_queue_entry_t *bookmark)
{
wait_queue_entry_t *curr, *next;
int cnt = 0;
if (bookmark && (bookmark->flags & WQ_FLAG_BOOKMARK)) {
curr = list_next_entry(bookmark, entry);
list_del(&bookmark->entry);
bookmark->flags = 0;
} else
curr = list_first_entry(&wq_head->head, wait_queue_entry_t, entry);
if (&curr->entry == &wq_head->head)
return nr_exclusive;
list_for_each_entry_safe_from(curr, next, &wq_head->head, entry) {
unsigned flags = curr->flags;
int ret;
if (flags & WQ_FLAG_BOOKMARK)
continue;
ret = curr->func(curr, mode, wake_flags, key); //执行相应的唤醒函数,进程阻塞和使用epoll执行的这个函数不同。决定是唤醒进程还是,只是将描述符加入就绪队列。
if (ret < 0)
break;
if (ret && (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive) //决定唤醒的进程数量,防止兽群效应。
break;
if (bookmark && (++cnt > WAITQUEUE_WALK_BREAK_CNT) &&
(&next->entry != &wq_head->head)) {
bookmark->flags = WQ_FLAG_BOOKMARK;
list_add_tail(&bookmark->entry, &next->entry);
break;
}
}
return nr_exclusive;
}
eventfd read函数
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 cnt;
if (count < sizeof(cnt))
return -EINVAL;
res = eventfd_ctx_read(ctx, file->f_flags & O_NONBLOCK, &cnt);//关键函数,读取count至
if (res < 0)
return res;
return put_user(cnt, (__u64 __user *) buf) ? -EFAULT : sizeof(cnt);
}
ssize_t eventfd_ctx_read(struct eventfd_ctx *ctx, int no_wait, __u64 *cnt)
{
ssize_t res;
DECLARE_WAITQUEUE(wait, current);
spin_lock_irq(&ctx->wqh.lock);
*cnt = 0;
res = -EAGAIN;
if (ctx->count > 0)
res = 0;
else if (!no_wait) { //阻塞模式,则执行阻塞代码
__add_wait_queue(&ctx->wqh, &wait); //添加到等待队列
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
if (ctx->count > 0) {
res = 0;
break;
}
if (signal_pending(current)) {
res = -ERESTARTSYS;
break;
}
spin_unlock_irq(&ctx->wqh.lock);
schedule();
spin_lock_irq(&ctx->wqh.lock);
}
__remove_wait_queue(&ctx->wqh, &wait);
__set_current_state(TASK_RUNNING);
}
if (likely(res == 0)) {
eventfd_ctx_do_read(ctx, cnt); //读取ctx->count数据,怎么读取有flags控制。
if (waitqueue_active(&ctx->wqh))
wake_up_locked_poll(&ctx->wqh, POLLOUT); //跟write一样,唤醒等待的进程
}
spin_unlock_irq(&ctx->wqh.lock);
return res;
}
static void eventfd_ctx_do_read(struct eventfd_ctx *ctx, __u64 *cnt)
{
*cnt = (ctx->flags & EFD_SEMAPHORE) ? 1 : ctx->count; //根据设置的标志位,决定每次读一个还是全部读取
ctx->count -= *cnt;
}
eventfd中的poll函数。
当前我发现poll函数在两个地方有执行。
fs/select.c文件中的do_select函数中执行,这个函数遍历文件描述符列表对应的文件的poll函数检查是否有事件发生。如果有事件发生则返回。
fs/eventpoll.c文件中ep_ctl在执行ep_insert时候执行,用来注册等待队列,当有事件发生,唤起等待队列时,可以执行fs/eventpoll.c中的一个函数,将文件描述符放到就绪列表中。
static unsigned int eventfd_poll(struct file *file, poll_table *wait)
{
struct eventfd_ctx *ctx = file->private_data;
unsigned int events = 0;
u64 count;
poll_wait(file, &ctx->wqh, wait); //在epoll中有用。在ep_insert时将poll_table挂在等待队列中
/*下面的代码是select时有用。select 遍历每个文件描述符,调用对应poll查询是否有事件发生*/
count = READ_ONCE(ctx->count);
if (count > 0)
events |= POLLIN;
if (count == ULLONG_MAX)
events |= POLLERR;
if (ULLONG_MAX - 1 > count)
events |= POLLOUT;
return events;
}
eventfd 相当于进程间的一个通讯机制,能交互的数据时一个整数。更像是一个事件通知机制。所以virtio的vhost会用这个来做数据包通知。