Analyzing eventfd System Call Implementation with select

eventfd(2) Combined with seelect(2) Source Code Analysis

The code examples are from Linux kernel 4.17

eventfd(2) - Creates a file descriptor for event notification.

  • Usage
  • Source Code Analysis
  • References
#include <sys/eventfd.h>

int eventfd(unsigned int initval, int flags);

int eventfd2(unsigned int initval, int flags);

Parameters
- \initval  Initial value (associated with the internal count)
- \flags    This parameter was invalid before kernel 2.6.26 and must be specified as 0

Meaningful flags include:
- EFD_CLOEXEC, equivalent to O_CLOEXEC
- EFD_NONBLOCK, equivalent to O_NONBLOCK
- EFD_SEMAPHORE, semaphore option affecting read(2) value


Return
- Returns a new file descriptor on success, -1 on failure with errno set

eventfd acts as a simple abstract file, where each file descriptor corresponds to a __u64 count maintained in kernel space. All file operations related to eventfd are tied to this counter.

Supported file operations:

  • read(2), reads a value that reduces the count. If EFD_SEMAPHORE is set in flags, count -= 1; otherwise, count -= count. Returns 8 on success.
  • write(2), writes a cnt, count += cnt, returns 8 on success.
  • poll(2), poll operation, core of event notification, detailed below.
  • close(2), decrements the reference count of the eventfd structure. If it reaches 0, releases the allocated memory.

Usage

The core of eventfd(2) is its poll operation. The most common usage is combining it with select(2)/poll(2)/epoll(2) for inter-thread communication.

#include <poll.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/eventfd.h>
#include <pthread.h>

int event_fd;

void *writer_thread(void *arg) {
    uint64_t value = 1;
    while (1) {
        printf("Writing value: %zu\n", value);
        write(event_fd, &value, sizeof(value));
        value++;
        sleep(2);
    }
}

int main() {
    struct pollfd poll_fds;
    pthread_t thread_id;

    unsigned int initial_value = 1000;  // Observe the output order when changing to 0
    int flags = 0;
    int timeout_ms = 1000;

//     flags |= EFD_SEMAPHORE; // Uncomment to observe different output
    event_fd = eventfd(initial_value, flags);
    poll_fds.fd = event_fd;
    poll_fds.events |= POLLIN;

    pthread_create(&thread_id, NULL, writer_thread, NULL);

    while (1) {
        int result = poll(&poll_fds, 1, timeout_ms);
        if (result > 0) {
            uint64_t read_value;
            read(event_fd, &read_value, sizeof(read_value));
            printf("Read value: %zu\n", read_value);
        }
    }
}

Read value: 1000
Writing value: 1
Read value: 1
Writing value: 2
Read value: 2
Writing value: 3
Read value: 3
Writing value: 4
Read value: 4

This example demonstrates simple inter-thread communication. The child thread writes incrementing unsigned long integers to eventfd every second. The main thread uses poll(2) to detect readiness and reads the reduced counter value from kernel space.

The parameters for read/write system calls follow:

int read(int, void *, size_t);

Since eventfd maintains an internal counter, the second and third parameters should be uint64_t * and sizeof(uint64_t) respectively.

Implementation

eventfd(2) implementation is located in fs/eventfd.c

The directory structure reveals eventfd is implemented as a file. The code is concise (under 500 lines) and easy to understand. Studying eventfd provides insight into kernel driver logic.

struct eventfd_ctx

struct eventfd_ctx is the structure maintained in kernel space for eventfd, lightweight and simple.

struct eventfd_ctx {
        struct kref kref;  // Reference count, memory freed when reaches 0
        wait_queue_head_t wqh;  // Wait queue head
        /*
         * Every write(2) operation adds the written __u64 value to "count" and wakes up "wqh".
         * A read(2) returns the "count" value to userspace and resets "count" to zero.
         * The kernel-side eventfd_signal() also adds to the "count" counter and issues a wakeup.
         */
        __u64 count;  // Counter closely related to file operations
        unsigned int flags;  // Various flags
};

eventfd(2)

This system call creates a new file descriptor, initializes the kernel-space counter, and initializes the wait queue head. Subsequent read/write operations will add themselves to this wait queue.

static int do_eventfd(unsigned int count, int flags)
{
        struct eventfd_ctx *ctx;
        int fd;

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

        // Flags must be among EFD_CLOEXEC, EFD_NONBLOCK, EFD_SEMAPHORE
        if (flags & ~EFD_FLAGS_SET)
                return -EINVAL;

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

        kref_init(&ctx->kref);  // Initialize reference count to 1
        init_waitqueue_head(&ctx->wqh);  // Initialize wait queue head
        ctx->count = count;  // Initialize counter
        ctx->flags = flags;  // Set flags

        // Create new file descriptor and set eventfd file operations
        fd = anon_inode_getfd("[eventfd]", &eventfd_fops, ctx,
                              O_RDWR | (flags & EFD_SHARED_FCNTL_FLAGS));
        if (fd < 0)
                eventfd_free_ctx(ctx);

        return fd;
}

eventfd_fops is the file operations structure for eventfd, registered in the file's f_op structure.

static const struct file_operations eventfd_fops = {
        .release        = eventfd_release,  // File close operation
        .poll           = eventfd_poll,    // File poll operation
        .read           = eventfd_read,    // Read
        .write          = eventfd_write,   // Write
        .llseek         = noop_llseek,
};

eventfd_read(2), read(2), eventfd_write(2), write(2)

static ssize_t eventfd_read(struct file *file, char __user *buf, size_t count,
                            loff_t *ppos)
{
        struct eventfd_ctx *ctx = file->private_data;  // Retrieve eventfd structure from file private data
        ssize_t res;
        __u64 ucnt = 0;
        DECLARE_WAITQUEUE(wait, current);  // Declare a wait queue entry

        if (count < sizeof(ucnt))  // Must be able to hold a u64
                return -EINVAL;

        spin_lock_irq(&ctx->wqh.lock);
        res = -EAGAIN;  // Initial EAGAIN for non-blocking mode when unreadable
        if (ctx->count > 0)  // Counter > 0 means readable, return 8
                res = sizeof(ucnt);
        else if (!(file->f_flags & O_NONBLOCK)) {  // count = 0 and not non-blocking
                __add_wait_queue(&ctx->wqh, &wait);  // Add wait entry to queue
                for (;;) {
                        set_current_state(TASK_INTERRUPTIBLE);  // Set task state to interruptible
                        if (ctx->count > 0) {  // Counter > 0, exit loop
                                res = sizeof(ucnt);
                                break;
                        }
                        if (signal_pending(current)) {  // Signal pending, exit loop
                                res = -ERESTARTSYS;
                                break;
                        }
                        spin_unlock_irq(&ctx->wqh.lock);
                        schedule();  // Schedule
                        spin_lock_irq(&ctx->wqh.lock);
                }
                __remove_wait_queue(&ctx->wqh, &wait);  // Remove wait entry
                __set_current_state(TASK_RUNNING);  // Set task to running
        }
        if (likely(res > 0)) {
                eventfd_ctx_do_read(ctx, &ucnt);  // Read based on flags
                if (waitqueue_active(&ctx->wqh))
                        wake_up_locked_poll(&ctx->wqh, EPOLLOUT);  // Wake up threads
        }
        spin_unlock_irq(&ctx->wqh.lock);

        if (res > 0 && put_user(ucnt, (__u64 __user *)buf))  // Copy reduced count to userspace
                return -EFAULT;

        return res;
}


static void eventfd_ctx_do_read(struct eventfd_ctx *ctx, __u64 *cnt)
{
        *cnt = (ctx->flags & EFD_SEMAPHORE) ? 1 : ctx->count;  // EFD_SEMAPHORE reads 1
        ctx->count -= *cnt;
}

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)))  // Copy 8 bytes from userspace
                return -EFAULT;
        if (ucnt == ULLONG_MAX)  // Max count value
                return -EINVAL;
        spin_lock_irq(&ctx->wqh.lock);
        res = -EAGAIN;  // Initial EAGAIN for non-blocking mode when unwritable
        if (ULLONG_MAX - ctx->count > ucnt)  // Can write
                res = sizeof(ucnt);
        else if (!(file->f_flags & O_NONBLOCK)) {  // Cannot write and not non-blocking
                __add_wait_queue(&ctx->wqh, &wait);  // Add to wait queue
                for (res = 0;;) {  // Clear EAGAIN
                        set_current_state(TASK_INTERRUPTIBLE);  // Set task to interruptible
                        if (ULLONG_MAX - ctx->count > ucnt) {  // Can write, set return value
                                res = sizeof(ucnt);
                                break;
                        }
                        if (signal_pending(current)) {  // Signal pending
                                res = -ERESTARTSYS;
                                break;
                        }
                        spin_unlock_irq(&ctx->wqh.lock);
                        schedule();  // Schedule
                        spin_lock_irq(&ctx->wqh.lock);
                }
                __remove_wait_queue(&ctx->wqh, &wait);  // Remove from wait queue
                __set_current_state(TASK_RUNNING);  // Set task to running
        }
        if (likely(res > 0)) {
                ctx->count += ucnt;  // Increment counter
                if (waitqueue_active(&ctx->wqh))
                        wake_up_locked_poll(&ctx->wqh, EPOLLIN);  // Wake up threads
        }
        spin_unlock_irq(&ctx->wqh.lock);

        return res;
}

Besides parameter validation, the handling of blocking mode differs slightly before the loop: write(2) sets res from -EAGAIN to 0, while read(2) doesn't modify it. However, both achieve the same effect as res will be set before returning from blocking mode.

During each blocking operation, read(2)/write(2) add themselves to the internal wait queue via __add_wait_queue(). After the counter becomes available, a wakeup occurs by traversing the wait queue and waking threads.

poll

static __poll_t eventfd_poll(struct file *file, poll_table *wait)
{
        struct eventfd_ctx *ctx = file->private_data;
        __poll_t events = 0;
        u64 count;

        poll_wait(file, &ctx->wqh, wait);  // Key function when combined with select

        // Critical section access comments

        count = READ_ONCE(ctx->count);

        if (count > 0)  // Readable when count > 0
                events |= EPOLLIN;
        if (count == ULLONG_MAX)  // Counter at max, error
                events |= EPOLLERR;
        if (ULLONG_MAX - 1 > count)  // Writable
                events |= EPOLLOUT;

        return events;
}

The poll implementation is straightforward, returning events based on the counter value.

In select(2)/poll(2), the file's f_op->poll() corresponds to eventfd_poll(). The select(2) implementation (do_select() → poll_initwait()) sets pt->_qproc to __pollwait(). During the select loop, each file descriptor's poll method is called, which for eventfd is eventfd_poll().

eventfd_poll() calls poll_wait() → __pollwait(), which sets the queue entry's callback to pollwake() and adds it to the file's wait queue, returning the readiness mask.

When read(2)/write(2) occurs, before returning, wake_up_locked_poll() traverses the file's wait queue, executes the queue entry's callback (pollwake() for select(2)), and wakes the thread.

Tags: eventfd SELECT Linux kernel system call File Descriptor

Posted on Sat, 01 Aug 2026 16:33:13 +0000 by Fredric