Linux中epoll_wait的实现(三)
·
一、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 == -1→MAX_SCHEDULE_TIMEOUT - 溢出检查:
timeout > (MAX_SCHEDULE_TIMEOUT - 1000) / HZ→MAX_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;
重试条件:
!res: 没有错误发生eavail: 有可用事件!(res = ep_events_transfer(...)): 事件传输返回0(没有传输到事件)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)中提取最多maxevents个epitem - 将这些
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. 关键设计要点
-
临时链表设计:
- 使用临时传输链表
txlist避免长时间持有就绪列表锁 - 允许在无锁状态下处理用户空间数据复制
- 使用临时传输链表
-
模式支持:
- 通过重注入逻辑实现ET和LT模式
- ET模式:事件只通知一次
- LT模式:只要条件满足,持续通知
9. 为什么需要重新注入?
考虑水平触发(LT)场景:
- 文件可读,
epitem在就绪列表中 ep_collect_ready_items将其移到传输链表- 用户读取部分数据,文件仍然可读
ep_reinject_items检查后将其重新放回就绪列表- 下次
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结构 txlink是epitem中用于传输链表的字段
- 获取链表第一个节点的
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)) {
四个必须同时满足的条件:
-
EP_RB_LINKED(&epi->rbn):epitem仍在红黑树中(没有被删除)- 如果不在红黑树中,说明正在被移除,不需要处理
-
!(epi->event.events & EPOLLET):- 不是边缘触发(ET)模式
- 这是水平触发(LT)和边缘触发(ET)的关键区别
- ET模式:事件只通知一次,不重新注入
- LT模式:只要条件满足,持续通知,需要重新注入
-
(epi->revents & epi->event.events):- 文件当前仍有感兴趣的事件发生
epi->revents是当前的事件状态epi->event.events是用户感兴趣的事件- 如果事件仍然存在,需要继续通知
-
!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: 传输链表,包含待处理的epitemevents: 用户空间事件数组(输出结果)
- 变量:
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".
*/
- 任务私有列表: 传输链表
txlist是当前任务的私有数据 - 收集测试保证: 在
ep_collect_ready_items中的检查确保唯一性 - 信号量保护: 持有
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结构 txlink是epitem中用于传输链表的字段名
- 将链表节点转换为
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: 初始化收集计数为0lnk = 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结构 rdllink是epitem中用于就绪链表的字段名
- 将链表节点转换为
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数量
更多推荐
所有评论(0)