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;
}

備考

eventfd_ctx_read()でノンブロックの読み込みに意味があるかと思ってしまいますが、イベント回数を限定する処理の実装で、eventfd作成時に起床限定回数とし、EFD_SEMAPHOREの読み込みとする事で実現できます。ノンブロックは、限定回数を超えたならreadはエラーで復帰しますます。

追記

anon_inodefsのinode取得による実装ですが、anon_inodefsはこの実装に限らず、利用する機能/実装その物に全く関与してません。


最終更新 2015/05/10 18:46:48 - north
(2014/04/29 03:15:41 作成)


検索

アクセス数
3690510
最近のコメント
コアダンプファイル - sakaia
list_head構造体 - yocto_no_yomikata
勧告ロックと強制ロック - wataash
LKMからのファイル出力 - 重松 宏昌
kprobe - ななし
ksetの実装 - スーパーコピー
カーネルスレッドとは - ノース
カーネルスレッドとは - nbyst
asmlinkageってなに? - ノース
asmlinkageってなに? - よろしく
Adsense
広告情報が設定されていません。