eventfd
Rev.2を表示中。最新版はこちら。
eventfdシステムコールはeventfd_file_create()でanon_inodefsファイルシステムからinodeを取得し、struct fileを取得します。anon_inodefsファイルシステムのinode取得は、バーチャルで実際に取得されません。anon_inodefsファイルシステムを登録する時に作成される雛形となるanon_inode_mntを参照するにすぎません。またこの時inodeのコールバックは定義されておらず、従ってanon_inodefsでのFILE取得は、利用する側で設定しなければなりません。eventfd_fopsがそれになります。
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); if (IS_ERR(file)) { error = PTR_ERR(file); goto err_put_unused_fd; } fd_install(fd, file); return fd; err_put_unused_fd: put_unused_fd(fd); return error; }anon_inode_getfile()でfile->f_op = eventfd_fops/ file->private_data = ctxとするFILEを取得します。countをctx->count = countします。
ctx->countはeventfd_fileの仮想データ数的な物で、0でないcountでeventfd_file_create()をコールすると、write()済みの状態でeventfdが作成されると言う事です。
static const struct file_operations eventfd_fops = { .release = eventfd_release, .poll = eventfd_poll, .read = eventfd_read, .write = eventfd_write, .llseek = noop_llseek, }; struct eventfd_ctx { struct kref kref; wait_queue_head_t wqh; __u64 count; unsigned int flags; }; 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); 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)); if (IS_ERR(file)) eventfd_free_ctx(ctx); return file; }readシステムコールのコールバックです。読み込みカウントは8バイト以下です。eventfdのreadはバッファを読むのでなく、file->private_dataのeventfd_ctxのcountの数値処理となるからです。
file->f_flags & O_NONBLOCKでノンブロックなら1、ブロックなら0で、ループ処理で読み込みを行います。cntは読み込んだ結果です。
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); if (res < 0) return res; return put_user(cnt, (__u64 __user *) buf) ? -EFAULT : sizeof(cnt); }ctx->countはwriteで設定されます。ctx->count > 0なら読み込んで復帰。!no_waitなら-EAGAINで復帰です。
no_wait!=0の通常の処理で、自プロセスをctx->wqhにリストし、schedule()を介してシグナルが発生したか、ctx->count > 0でないかまで、ループし続けます。シグナルによる復帰はエラーです。
ctx->count > 0(res = 0でwriteによる)でループを抜けると、eventfd_ctx_do_read()でフラグに応じたctx->countを取得し、他にウエイトしているプロセスがあれば、そのプロセスも起床させます。
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); if (waitqueue_active(&ctx->wqh)) wake_up_locked_poll(&ctx->wqh, POLLOUT); } spin_unlock_irq(&ctx->wqh.lock); return res; } EXPORT_SYMBOL_GPL(eventfd_ctx_read);eventfd_ctx_do_read()が仮想的な実読み込みで、EFD_SEMAPHOREなら1つずつ、そうでないなら一気に読み込みます。
static void eventfd_ctx_do_read(struct eventfd_ctx *ctx, __u64 *cnt) { *cnt = (ctx->flags & EFD_SEMAPHORE) ? 1 : ctx->count; ctx->count -= *cnt; }書き込みでは読み込みと逆で、書き込みcountが8バイト以下だとエラーです。ctx->count+ucntがULLONG_MAXを超えると、他のプロセスがreadし、ctx->count+ucntがULLONG_MAXの範囲となるまで、schedule()を介してループします。
write可能なら、ctx->count += ucntとし、ウエイトしている全タスクを起床させます。
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); 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; }