一、sys_epoll_wait

asmlinkage long sys_epoll_wait(int epfd, struct epoll_event __user *events,
                               int maxevents, int timeout)
{
        int error;
        struct file *file;
        struct eventpoll *ep;

        DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_wait(%d, %p, %d, %d)\n",
                     current, epfd, events, maxevents, timeout));

        /* The maximum number of event must be greater than zero */
        if (maxevents <= 0)
                return -EINVAL;

        /* Verify that the area passed by the user is writeable */
        if ((error = verify_area(VERIFY_WRITE, events, maxevents * sizeof(struct epoll_event))))
                goto eexit_1;

        /* Get the "struct file *" for the eventpoll file */
        error = -EBADF;
        file = fget(epfd);
        if (!file)
                goto eexit_1;

        /*
         * We have to check that the file structure underneath the fd
         * the user passed to us _is_ an eventpoll file.
         */
        error = -EINVAL;
        if (!IS_FILE_EPOLL(file))
                goto eexit_2;

        /*
         * At this point it is safe to assume that the "private_data" contains
         * our own data structure.
         */
        ep = file->private_data;

        /* Time to fish for events ... */
        error = ep_poll(ep, events, maxevents, timeout);

eexit_2:
        fput(file);
eexit_1:
        DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_wait(%d, %p, %d, %d) = %d\n",
                     current, epfd, events, maxevents, timeout, error));

        return error;
}

1. 函数声明

asmlinkage long sys_epoll_wait(int epfd, struct epoll_event __user *events,
                               int maxevents, int timeout)
{
    int error;
    struct file *file;
    struct eventpoll *ep;
  • asmlinkage: 系统调用标准调用约定,参数通过栈传递
  • 参数:
    • epfd: epoll实例的文件描述符
    • events: 用户空间的事件数组指针(用于返回就绪事件)
    • maxevents: 事件数组的最大容量
    • timeout: 超时时间(毫秒),-1表示无限等待,0表示立即返回
  • 变量:
    • error: 错误码
    • file: epoll文件结构指针
    • ep: eventpoll结构指针

2. 参数有效性检查

/* The maximum number of event must be greater than zero */
if (maxevents <= 0)
    return -EINVAL;
  • 检查: maxevents 必须为正数
  • 错误码: -EINVAL(无效参数)
  • 直接返回,不需要后续的资源清理

3. 用户空间缓冲区可写性验证

/* Verify that the area passed by the user is writeable */
if ((error = verify_area(VERIFY_WRITE, events, maxevents * sizeof(struct epoll_event))))
    goto eexit_1;
  • 检查用户空间的事件数组是否可写

  • VERIFY_WRITE: 检查写权限

  • events: 用户空间指针

  • maxevents * sizeof(struct epoll_event): 要检查的内存大小

  • 错误处理: 如果检查失败,设置错误码并跳转到 eexit_1

4. 获取epoll文件结构

/* Get the "struct file *" for the eventpoll file */
error = -EBADF;
file = fget(epfd);
if (!file)
    goto eexit_1;
  • error = -EBADF: 预设错误码"错误的文件描述符"
  • fget(epfd):
    • 根据文件描述符获取文件结构
    • 增加文件的引用计数
  • 检查: 如果获取失败(文件描述符无效),跳转到错误处理

5. 文件类型验证

/*
 * We have to check that the file structure underneath the fd
 * the user passed to us _is_ an eventpoll file.
 */
error = -EINVAL;
if (!IS_FILE_EPOLL(file))
    goto eexit_2;
  • error = -EINVAL: 预设错误码"无效参数"
  • IS_FILE_EPOLL(file): 宏,检查文件操作是否为epoll文件操作集
  • 检查: 如果不是epoll文件,跳转到 eexit_2(需要释放文件引用)

6. 获取eventpoll结构

/*
 * At this point it is safe to assume that the "private_data" contains
 * our own data structure.
 */
ep = file->private_data;
  • file->private_data: 从文件私有数据中获取eventpoll结构
  • 这是在 ep_file_init 中设置的关联

7. 执行核心的等待逻辑

/* Time to fish for events ... */
error = ep_poll(ep, events, maxevents, timeout);
  • ep_poll(ep, events, maxevents, timeout):
    • 核心的等待函数
    • 检查就绪事件,如果没有则等待
    • 返回获取到的事件数量或错误码

8. 错误处理路径

eexit_2: 文件类型检查失败

eexit_2:
    fput(file);
  • fput(file): 减少文件引用计数
  • 如果引用计数降为0,会释放文件结构

eexit_1: 参数检查或文件获取失败

eexit_1:
    DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_wait(%d, %p, %d, %d) = %d\n",
                 current, epfd, events, maxevents, timeout, error));
    return error;
  • 输出调试信息,显示最终结果
  • 返回错误码

二、事件等待ep_poll

static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
                   int maxevents, long timeout)
{
        int res, eavail;
        unsigned long flags;
        long jtimeout;
        wait_queue_t wait;

        /*
         * Calculate the timeout by checking for the "infinite" value ( -1 )
         * and the overflow condition. The passed timeout is in milliseconds,
         * that why (t * HZ) / 1000.
         */
        jtimeout = timeout == -1 || timeout > (MAX_SCHEDULE_TIMEOUT - 1000) / HZ ?
                MAX_SCHEDULE_TIMEOUT: (timeout * HZ + 999) / 1000;

retry:
        write_lock_irqsave(&ep->lock, flags);

        res = 0;
        if (list_empty(&ep->rdllist)) {
                /*
                 * We don't have any available event to return to the caller.
                 * We need to sleep here, and we will be wake up by
                 * ep_poll_callback() when events will become available.
                 */
                init_waitqueue_entry(&wait, current);
                add_wait_queue(&ep->wq, &wait);

                for (;;) {
                        /*
                         * We don't want to sleep if the ep_poll_callback() sends us
                         * a wakeup in between. That's why we set the task state
                         * to TASK_INTERRUPTIBLE before doing the checks.
                         */
                        set_current_state(TASK_INTERRUPTIBLE);
                        if (!list_empty(&ep->rdllist) || !jtimeout)
                                break;
                        if (signal_pending(current)) {
                                res = -EINTR;
                                break;
                        }

                        write_unlock_irqrestore(&ep->lock, flags);
                        jtimeout = schedule_timeout(jtimeout);
                        write_lock_irqsave(&ep->lock, flags);
                }
                remove_wait_queue(&ep->wq, &wait);

                set_current_state(TASK_RUNNING);
        }

        /* Is it worth to try to dig for events ? */
        eavail = !list_empty(&ep->rdllist);

        write_unlock_irqrestore(&ep->lock, flags);

        /*
         * Try to transfer events to user space. In case we get 0 events and
         * there's still timeout left over, we go trying again in search of
         * more luck.
         */
        if (!res && eavail &&
            !(res = ep_events_transfer(ep, events, maxevents)) && jtimeout)
                goto retry;

        return res;
}

1. 函数声明和变量定义

static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
                   int maxevents, long timeout)
{
    int res, eavail;
    unsigned long flags;
    long jtimeout;
    wait_queue_t wait;
  • 参数:
    • ep: eventpoll结构指针
    • events: 用户空间事件数组
    • maxevents: 最大事件数
    • timeout: 超时时间(毫秒)
  • 变量:
    • res: 结果值(事件数量或错误码)
    • eavail: 事件可用标志
    • flags: IRQ状态保存
    • jtimeout: 内核格式的超时时间(jiffies)
    • wait: 等待队列条目

2. 超时时间转换

/*
 * Calculate the timeout by checking for the "infinite" value ( -1 )
 * and the overflow condition. The passed timeout is in milliseconds,
 * that why (t * HZ) / 1000.
 */
jtimeout = timeout == -1 || timeout > (MAX_SCHEDULE_TIMEOUT - 1000) / HZ ?
        MAX_SCHEDULE_TIMEOUT: (timeout * HZ + 999) / 1000;
  • 无限等待: timeout == -1MAX_SCHEDULE_TIMEOUT
  • 溢出检查: timeout > (MAX_SCHEDULE_TIMEOUT - 1000) / HZMAX_SCHEDULE_TIMEOUT
  • 正常转换: (timeout * HZ + 999) / 1000
    • + 999 用于四舍五入
    • 确保毫秒到jiffies的转换更精确

3. 重试标签和加锁

retry:
write_lock_irqsave(&ep->lock, flags);
  • retry: 重试标签,用于超时未用完时重新尝试
  • write_lock_irqsave(&ep->lock, flags): 获取eventpoll写锁,禁用中断

4. 检查就绪列表

res = 0;
if (list_empty(&ep->rdllist)) {
  • res = 0: 初始化结果为0(没有事件)
  • list_empty(&ep->rdllist): 检查就绪事件链表是否为空
  • 如果为空,需要进入等待逻辑

5. 等待队列设置

/*
 * We don't have any available event to return to the caller.
 * We need to sleep here, and we will be wake up by
 * ep_poll_callback() when events will become available.
 */
init_waitqueue_entry(&wait, current);
add_wait_queue(&ep->wq, &wait);
  • init_waitqueue_entry(&wait, current): 初始化等待队列条目,关联当前任务
  • add_wait_queue(&ep->wq, &wait): 将当前任务添加到epoll的等待队列

6. 等待循环

for (;;) {
    /*
     * We don't want to sleep if the ep_poll_callback() sends us
     * a wakeup in between. That's why we set the task state
     * to TASK_INTERRUPTIBLE before doing the checks.
     */
    set_current_state(TASK_INTERRUPTIBLE);
  • set_current_state(TASK_INTERRUPTIBLE):
    • 将当前任务设置为可中断睡眠状态
    • 这样在检查条件之前,如果收到唤醒信号,可以立即响应

7. 循环退出条件检查

if (!list_empty(&ep->rdllist) || !jtimeout)
    break;
  • 条件1: !list_empty(&ep->rdllist) - 就绪列表不为空(有事件了)
  • 条件2: !jtimeout - 超时时间为0(超时了)
  • 满足任一条件就退出循环

8. 信号检查

if (signal_pending(current)) {
    res = -EINTR;
    break;
}
  • signal_pending(current): 检查当前任务是否有待处理信号
  • 如果有信号,设置结果 res = -EINTR(被中断)并退出循环

9. 调度和超时等待

write_unlock_irqrestore(&ep->lock, flags);
jtimeout = schedule_timeout(jtimeout);
write_lock_irqsave(&ep->lock, flags);
  • 释放锁: 允许其他操作修改eventpoll结构
  • schedule_timeout(jtimeout):
    • 让出CPU,睡眠指定的jiffies时间
    • 返回剩余的jiffies时间
  • 重新加锁: 继续保护数据结构

10. 清理等待队列

remove_wait_queue(&ep->wq, &wait);
set_current_state(TASK_RUNNING);
  • remove_wait_queue(&ep->wq, &wait): 从等待队列中移除当前任务
  • set_current_state(TASK_RUNNING): 恢复任务为运行状态

11. 事件可用性检查

/* Is it worth to try to dig for events ? */
eavail = !list_empty(&ep->rdllist);
write_unlock_irqrestore(&ep->lock, flags);
  • eavail = !list_empty(&ep->rdllist): 检查是否有可用事件
  • 释放锁: 事件传输不需要持有锁

12. 事件传输和重试逻辑

/*
 * Try to transfer events to user space. In case we get 0 events and
 * there's still timeout left over, we go trying again in search of
 * more luck.
 */
if (!res && eavail &&
    !(res = ep_events_transfer(ep, events, maxevents)) && jtimeout)
    goto retry;

重试条件:

  1. !res: 没有错误发生
  2. eavail: 有可用事件
  3. !(res = ep_events_transfer(...)): 事件传输返回0(没有传输到事件)
  4. jtimeout: 还有剩余超时时间
  • 如果所有条件满足,跳转到 retry 重新尝试
  • 这是一种优化:避免在事件竞争情况下过早返回

返回结果

return res;
  • 返回事件数量或错误码

三、epoll事件传输ep_events_transfer

static int ep_events_transfer(struct eventpoll *ep,
                              struct epoll_event __user *events, int maxevents)
{
        int eventcnt = 0;
        struct list_head txlist;

        INIT_LIST_HEAD(&txlist);

        /*
         * We need to lock this because we could be hit by
         * eventpoll_release_file() and epoll_ctl(EPOLL_CTL_DEL).
         */
        down_read(&ep->sem);

        /* Collect/extract ready items */
        if (ep_collect_ready_items(ep, &txlist, maxevents) > 0) {
                /* Build result set in userspace */
                eventcnt = ep_send_events(ep, &txlist, events);

                /* Reinject ready items into the ready list */
                ep_reinject_items(ep, &txlist);
        }

        up_read(&ep->sem);

        return eventcnt;
}

1. 函数声明和变量定义

static int ep_events_transfer(struct eventpoll *ep,
                              struct epoll_event __user *events, int maxevents)
{
    int eventcnt = 0;
    struct list_head txlist;
  • 参数:
    • ep: eventpoll结构指针
    • events: 用户空间事件数组(用于输出结果)
    • maxevents: 最大返回事件数
  • 变量:
    • eventcnt: 实际传输的事件计数(初始化为0)
    • txlist: 传输链表,用于临时存放就绪的epitem

2. 初始化传输链表

INIT_LIST_HEAD(&txlist);
  • INIT_LIST_HEAD(&txlist): 初始化一个空的链表头
  • 这个链表将用于临时存放从就绪列表中提取的epitem

3. 获取读信号量

/*
 * We need to lock this because we could be hit by
 * eventpoll_release_file() and epoll_ctl(EPOLL_CTL_DEL).
 */
down_read(&ep->sem);
  • 保护并发访问:防止在事件传输过程中,其他操作修改epoll结构
  • 防止的竞争条件
    • eventpoll_release_file(): 文件释放时清理相关的epoll
    • epoll_ctl(EPOLL_CTL_DEL): 删除监控项的操作

4. 收集就绪项

/* Collect/extract ready items */
if (ep_collect_ready_items(ep, &txlist, maxevents) > 0) {
  • ep_collect_ready_items(ep, &txlist, maxevents):
    • epoll的就绪列表(rdllist)中提取最多maxeventsepitem
    • 将这些epitem移动到临时传输链表txlist
    • 返回实际收集到的epitem数量
  • 条件检查: 如果收集到至少1个就绪项,执行后续操作

5. 事件传输

5.1. 构建用户空间结果集

/* Build result set in userspace */
eventcnt = ep_send_events(ep, &txlist, events);
  • ep_send_events(ep, &txlist, events):
    • 遍历传输链表中的每个epitem
    • 检查文件的当前事件状态
    • 将事件信息复制到用户空间的事件数组中
    • 返回成功传输的事件数量

5.2. 重新注入就绪项

/* Reinject ready items into the ready list */
ep_reinject_items(ep, &txlist);
  • ep_reinject_items(ep, &txlist):
    • 遍历传输链表中剩余的epitem
    • 对于边缘触发(ET)模式:不重新放回就绪列表
    • 对于水平触发(LT)模式:如果仍有事件,重新放回就绪列表
    • 这是实现ET和LT模式差异的关键

6. 释放信号量

up_read(&ep->sem);
  • up_read(&ep->sem): 释放读信号量
  • 允许其他需要写锁的操作进行

7. 返回事件计数

return eventcnt;
  • 返回实际传输到用户空间的事件数量

8. 关键设计要点

  1. 临时链表设计:

    • 使用临时传输链表txlist避免长时间持有就绪列表锁
    • 允许在无锁状态下处理用户空间数据复制
  2. 模式支持:

    • 通过重注入逻辑实现ET和LT模式
    • ET模式:事件只通知一次
    • LT模式:只要条件满足,持续通知

9. 为什么需要重新注入?

考虑水平触发(LT)场景:

  1. 文件可读,epitem在就绪列表中
  2. ep_collect_ready_items 将其移到传输链表
  3. 用户读取部分数据,文件仍然可读
  4. ep_reinject_items 检查后将其重新放回就绪列表
  5. 下次epoll_wait会再次通知

对于边缘触发(ET):

  • 步骤4中不会重新注入,即使用户没有完全处理

四、重新注入就绪列表ep_reinject_items

static void ep_reinject_items(struct eventpoll *ep, struct list_head *txlist)
{
        int ricnt = 0, pwake = 0;
        unsigned long flags;
        struct epitem *epi;

        write_lock_irqsave(&ep->lock, flags);

        while (!list_empty(txlist)) {
                epi = list_entry(txlist->next, struct epitem, txlink);

                /* Unlink the current item from the transfer list */
                EP_LIST_DEL(&epi->txlink);

                /*
                 * If the item is no more linked to the interest set, we don't
                 * have to push it inside the ready list because the following
                 * ep_release_epitem() is going to drop it. Also, if the current
                 * item is set to have an Edge Triggered behaviour, we don't have
                 * to push it back either.
                 */
                if (EP_RB_LINKED(&epi->rbn) && !(epi->event.events & EPOLLET) &&
                    (epi->revents & epi->event.events) && !EP_IS_LINKED(&epi->rdllink)) {
                        list_add_tail(&epi->rdllink, &ep->rdllist);
                        ricnt++;
                }
        }

        if (ricnt) {
                /*
                 * Wake up ( if active ) both the eventpoll wait list and the ->poll()
                 * wait list.
                 */
                if (waitqueue_active(&ep->wq))
                        wake_up(&ep->wq);
                if (waitqueue_active(&ep->poll_wait))
                        pwake++;
        }

        write_unlock_irqrestore(&ep->lock, flags);

        /* We have to call this outside the lock */
        if (pwake)
                ep_poll_safewake(&psw, &ep->poll_wait);
}

1. 函数声明和变量定义

static void ep_reinject_items(struct eventpoll *ep, struct list_head *txlist)
{
    int ricnt = 0, pwake = 0;
    unsigned long flags;
    struct epitem *epi;
  • 参数:
    • ep: eventpoll结构指针
    • txlist: 传输链表,包含待重新注入的epitem
  • 变量:
    • ricnt: 重新注入计数(记录有多少epitem被重新放回就绪列表)
    • pwake: 唤醒标志(是否需要唤醒轮询等待队列)
    • flags: IRQ状态保存
    • epi: 当前处理的epitem指针

2. 加锁保护

write_lock_irqsave(&ep->lock, flags);
  • write_lock_irqsave(&ep->lock, flags):
    • 获取eventpoll的写锁
    • 保存中断状态并禁用中断
    • 保护对就绪列表的并发修改

3. 遍历传输链表

while (!list_empty(txlist)) {
    epi = list_entry(txlist->next, struct epitem, txlink);
  • !list_empty(txlist): 循环直到传输链表为空
  • epi = list_entry(txlist->next, struct epitem, txlink):
    • 获取链表第一个节点的epitem结构
    • txlinkepitem中用于传输链表的字段

4. 从传输链表移除

/* Unlink the current item from the transfer list */
EP_LIST_DEL(&epi->txlink);
  • EP_LIST_DEL(&epi->txlink): 将当前epitem从传输链表中移除

5. 重新注入条件检查

/*
 * If the item is no more linked to the interest set, we don't
 * have to push it inside the ready list because the following
 * ep_release_epitem() is going to drop it. Also, if the current
 * item is set to have an Edge Triggered behaviour, we don't have
 * to push it back either.
 */
if (EP_RB_LINKED(&epi->rbn) && !(epi->event.events & EPOLLET) &&
    (epi->revents & epi->event.events) && !EP_IS_LINKED(&epi->rdllink)) {

四个必须同时满足的条件:

  1. EP_RB_LINKED(&epi->rbn):

    • epitem仍在红黑树中(没有被删除)
    • 如果不在红黑树中,说明正在被移除,不需要处理
  2. !(epi->event.events & EPOLLET):

    • 不是边缘触发(ET)模式
    • 这是水平触发(LT)和边缘触发(ET)的关键区别
    • ET模式:事件只通知一次,不重新注入
    • LT模式:只要条件满足,持续通知,需要重新注入
  3. (epi->revents & epi->event.events):

    • 文件当前仍有感兴趣的事件发生
    • epi->revents 是当前的事件状态
    • epi->event.events 是用户感兴趣的事件
    • 如果事件仍然存在,需要继续通知
  4. !EP_IS_LINKED(&epi->rdllink):

    • epitem当前不在就绪列表中
    • 避免重复添加到就绪列表

6. 重新注入到就绪列表

list_add_tail(&epi->rdllink, &ep->rdllist);
ricnt++;
  • list_add_tail(&epi->rdllink, &ep->rdllist):
    • epitem添加到就绪列表的尾部
    • 这样下次epoll_wait时会被再次处理
  • ricnt++: 增加重新注入计数

7. 唤醒等待的进程

if (ricnt) {
    /*
     * Wake up ( if active ) both the eventpoll wait list and the ->poll()
     * wait list.
     */
    if (waitqueue_active(&ep->wq))
        wake_up(&ep->wq);
    if (waitqueue_active(&ep->poll_wait))
        pwake++;
}
  • 条件: 只有重新注入了epitem时才需要唤醒
  • ep->wq: epoll等待队列(epoll_wait阻塞的进程)
    • 如果有进程在等待,立即唤醒
  • ep->poll_wait: 轮询等待队列(poll/select检查的进程)
    • 设置pwake标志,用于后续安全唤醒

8. 解锁和安全唤醒

write_unlock_irqrestore(&ep->lock, flags);

/* We have to call this outside the lock */
if (pwake)
    ep_poll_safewake(&psw, &ep->poll_wait);
  • 释放锁: 恢复中断状态
  • 安全唤醒: 在锁外安全地唤醒轮询等待队列

9. 完整的执行流程示例

9.1. 水平触发(LT)模式场景

文件可读 → epitem在就绪列表中
↓
ep_collect_ready_items: 移动到传输链表
↓
ep_send_events: 用户读取部分数据,文件仍然可读
↓
ep_reinject_items: 
   - 检查:仍在红黑树中 ✓
   - 检查:LT模式 ✓  
   - 检查:仍然可读 ✓
   - 检查:不在就绪列表 ✓
   → 重新放回就绪列表
↓
下次epoll_wait会再次通知

9.2. 边缘触发(ET)模式场景

文件可读 → epitem在就绪列表中
↓
ep_collect_ready_items: 移动到传输链表
↓
ep_send_events: 用户可能读取或不读取数据
↓
ep_reinject_items:
   - 检查:ET模式 ✗(不满足条件2)
   → 不重新放回就绪列表
↓
即使文件仍然可读,下次epoll_wait不会通知

五、将内核事件传输到用户空间ep_send_events

static int ep_send_events(struct eventpoll *ep, struct list_head *txlist,
                          struct epoll_event __user *events)
{
        int eventcnt = 0;
        unsigned int revents;
        struct list_head *lnk;
        struct epitem *epi;

        /*
         * We can loop without lock because this is a task private list.
         * The test done during the collection loop will guarantee us that
         * another task will not try to collect this file. Also, items
         * cannot vanish during the loop because we are holding "sem".
         */
        list_for_each(lnk, txlist) {
                epi = list_entry(lnk, struct epitem, txlink);

                /*
                 * Get the ready file event set. We can safely use the file
                 * because we are holding the "sem" in read and this will
                 * guarantee that both the file and the item will not vanish.
                 */
                revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL);

                /*
                 * Set the return event set for the current file descriptor.
                 * Note that only the task task was successfully able to link
                 * the item to its "txlist" will write this field.
                 */
                epi->revents = revents & epi->event.events;

                if (epi->revents) {
                        if (__put_user(epi->revents,
                                       &events[eventcnt].events) ||
                            __put_user(epi->event.data,
                                       &events[eventcnt].data))
                                return -EFAULT;
                        if (epi->event.events & EPOLLONESHOT)
                                epi->event.events &= EP_PRIVATE_BITS;
                        eventcnt++;
                }
        }
        return eventcnt;
}

1. 函数声明和变量定义

static int ep_send_events(struct eventpoll *ep, struct list_head *txlist,
                          struct epoll_event __user *events)
{
    int eventcnt = 0;
    unsigned int revents;
    struct list_head *lnk;
    struct epitem *epi;
  • 参数:
    • ep: eventpoll结构指针
    • txlist: 传输链表,包含待处理的epitem
    • events: 用户空间事件数组(输出结果)
  • 变量:
    • eventcnt: 成功发送的事件计数
    • revents: 文件的当前事件状态
    • lnk: 链表遍历指针
    • epi: 当前处理的epitem指针

2. 安全遍历说明

/*
 * We can loop without lock because this is a task private list.
 * The test done during the collection loop will guarantee us that
 * another task will not try to collect this file. Also, items
 * cannot vanish during the loop because we are holding "sem".
 */
  1. 任务私有列表: 传输链表txlist是当前任务的私有数据
  2. 收集测试保证: 在ep_collect_ready_items中的检查确保唯一性
  3. 信号量保护: 持有ep->sem读锁防止epitem被删除

3. 遍历传输链表

list_for_each(lnk, txlist) {
    epi = list_entry(lnk, struct epitem, txlink);
  • list_for_each(lnk, txlist): 遍历传输链表中的每个节点
  • epi = list_entry(lnk, struct epitem, txlink):
    • 将链表节点转换为epitem结构
    • txlinkepitem中用于传输链表的字段名

4. 获取当前文件事件状态

/*
 * Get the ready file event set. We can safely use the file
 * because we are holding the "sem" in read and this will
 * guarantee that both the file and the item will not vanish.
 */
revents = epi->ffd.file->f_op->poll(epi->ffd.file, NULL);
  • epi->ffd.file->f_op->poll(epi->ffd.file, NULL):
    • 调用目标文件的poll方法
    • 第二个参数为NULL,表示不注册新的等待队列(只是检查当前状态)
    • 返回文件的当前事件状态位图

5. 计算返回的事件集

/*
 * Set the return event set for the current file descriptor.
 * Note that only the task task was successfully able to link
 * the item to its "txlist" will write this field.
 */
epi->revents = revents & epi->event.events;
  • revents & epi->event.events:
    • 取文件当前事件状态与用户感兴趣的事件的交集
    • 只返回用户真正关心的事件
    • 例如:文件有POLLIN|POLLOUT,用户只关心POLLIN → 返回POLLIN

6. 事件处理和用户空间复制

if (epi->revents) {
    if (__put_user(epi->revents,
                   &events[eventcnt].events) ||
        __put_user(epi->event.data,
                   &events[eventcnt].data))
            return -EFAULT;
  • if (epi->revents): 只有当有用户关心的事件时才处理

  • __put_user(epi->revents, &events[eventcnt].events):

    • 将事件掩码复制到用户空间数组的events字段
    • 使用__put_user进行安全的用户空间写入
  • __put_user(epi->event.data, &events[eventcnt].data):

    • 将用户数据复制到用户空间数组的data字段
    • 这是用户在epoll_ctl中设置的关联数据
  • 如果任一复制操作失败,返回-EFAULT(错误地址)

7. EPOLLONESHOT 处理

if (epi->event.events & EPOLLONESHOT)
    epi->event.events &= EP_PRIVATE_BITS;

EPOLLONESHOT 机制:

  • 作用: 确保事件只被通知一次,然后自动禁用监控
  • 处理逻辑:
    • 如果设置了EPOLLONESHOT标志
    • 将事件掩码与EP_PRIVATE_BITS进行与操作
    • EP_PRIVATE_BITS通常只包含内核内部使用的位
    • 效果:清除所有用户事件位,禁用后续事件通知

8. 事件计数更新

eventcnt++;
  • 成功处理一个事件后,增加事件计数
  • 用于数组索引和最终返回值

9. 返回成功计数

return eventcnt;
  • 返回成功发送到用户空间的事件数量

六、从就绪列表中提取事件项ep_collect_ready_items

static int ep_collect_ready_items(struct eventpoll *ep, struct list_head *txlist, int maxevents)
{
        int nepi;
        unsigned long flags;
        struct list_head *lsthead = &ep->rdllist, *lnk;
        struct epitem *epi;

        write_lock_irqsave(&ep->lock, flags);

        for (nepi = 0, lnk = lsthead->next; lnk != lsthead && nepi < maxevents;) {
                epi = list_entry(lnk, struct epitem, rdllink);

                lnk = lnk->next;

                /* If this file is already in the ready list we exit soon */
                if (!EP_IS_LINKED(&epi->txlink)) {
                        /*
                         * This is initialized in this way so that the default
                         * behaviour of the reinjecting code will be to push back
                         * the item inside the ready list.
                         */
                        epi->revents = epi->event.events;

                        /* Link the ready item into the transfer list */
                        list_add(&epi->txlink, txlist);
                        nepi++;

                        /*
                         * Unlink the item from the ready list.
                         */
                        EP_LIST_DEL(&epi->rdllink);
                }
        }

        write_unlock_irqrestore(&ep->lock, flags);

        return nepi;
}

1. 函数声明和变量定义

static int ep_collect_ready_items(struct eventpoll *ep, struct list_head *txlist, int maxevents)
{
    int nepi;
    unsigned long flags;
    struct list_head *lsthead = &ep->rdllist, *lnk;
    struct epitem *epi;
  • 参数:
    • ep: eventpoll结构指针
    • txlist: 传输链表(输出参数,用于存放收集的epitem
    • maxevents: 最大收集事件数
  • 变量:
    • nepi: 已收集的epitem数量
    • flags: IRQ状态保存
    • lsthead: 指向就绪列表头(&ep->rdllist
    • lnk: 链表遍历指针
    • epi: 当前处理的epitem指针

2. 加锁保护

write_lock_irqsave(&ep->lock, flags);
  • write_lock_irqsave(&ep->lock, flags):
    • 获取eventpoll的写锁
    • 保存中断状态并禁用中断
    • 保护对就绪列表的并发修改

3. 循环遍历就绪列表

for (nepi = 0, lnk = lsthead->next; lnk != lsthead && nepi < maxevents;) {

循环初始化:

  • nepi = 0: 初始化收集计数为0
  • lnk = lsthead->next: 从就绪列表的第一个节点开始

循环条件:

  • lnk != lsthead: 没有遍历完整个链表(循环链表,回到头节点表示结束)
  • nepi < maxevents: 没有达到最大收集数量

4. 获取当前epitem

epi = list_entry(lnk, struct epitem, rdllink);
lnk = lnk->next;
  • epi = list_entry(lnk, struct epitem, rdllink):
    • 将链表节点转换为epitem结构
    • rdllinkepitem中用于就绪链表的字段名
  • lnk = lnk->next:
    • 提前保存下一个节点指针
    • 重要:因为在后续操作中当前节点会被移除,需要提前保存next指针

5. 重复检查保护

/* If this file is already in the ready list we exit soon */
if (!EP_IS_LINKED(&epi->txlink)) {
  • !EP_IS_LINKED(&epi->txlink): 检查epitem是否不在传输链表中
  • 作用:防止同一个epitem被多次收集到传输链表
  • 场景:在并发环境下,确保原子性操作

6. 初始化返回事件集

/*
 * This is initialized in this way so that the default
 * behaviour of the reinjecting code will be to push back
 * the item inside the ready list.
 */
epi->revents = epi->event.events;
  • 预设值:将 revents 初始化为用户感兴趣的所有事件
  • 默认行为:在 ep_reinject_items 中,如果条件满足,默认会重新注入
  • 后续覆盖:在 ep_send_events 中会用实际的事件状态覆盖这个预设值

7. 添加到传输链表

/* Link the ready item into the transfer list */
list_add(&epi->txlink, txlist);
nepi++;
  • list_add(&epi->txlink, txlist):
    • epitem添加到传输链表的头部
    • 使用 txlink 字段链接
  • nepi++: 增加收集计数

8. 从就绪列表中移除

/*
 * Unlink the item from the ready list.
 */
EP_LIST_DEL(&epi->rdllink);
  • EP_LIST_DEL(&epi->rdllink): 将epitem从就绪列表中移除
  • 目的:防止同一个事件被重复处理

9. 解锁和返回

write_unlock_irqrestore(&ep->lock, flags);
return nepi;
  • 释放锁:恢复中断状态
  • 返回:实际收集的epitem数量
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐