epoll_wait 及相关函数原理详解

1. 概述

1.1 什么是 epoll?

epoll 是 Linux 内核提供的一种高效的 I/O 事件通知机制,用于监控多个文件描述符(file descriptors)的 I/O 事件。

1.2 epoll 的优势

特性 select/poll epoll
时间复杂度 O(n) O(1)
文件描述符限制 1024 (select) / 无限制 (poll) 无限制
效率 每次调用都要传递所有 fd 只返回就绪的 fd
适用场景 少量 fd 大量 fd

1.3 epoll 系统调用族

c 复制代码
// 创建 epoll 实例
int epoll_create(int size);
int epoll_create1(int flags);

// 控制 epoll 实例
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

// 等待事件
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
int epoll_pwait(int epfd, struct epoll_event *events, int maxevents, int timeout, const sigset_t *sigmask);

2. epoll 架构设计

2.1 整体架构

perl 复制代码
用户空间                          内核空间
┌─────────────┐                  ┌─────────────┐
│             │                  │             │
│ 应用程序     │                  │  epoll 核心  │
│             │                  │             │
│  epoll_fd   │◄────────────────►│ eventpoll   │
│             │                  │             │
│  file_fd1   │                  │  epitem1    │
│  file_fd2   │                  │  epitem2    │
│  file_fd3   │                  │  epitem3    │
│             │                  │             │
└─────────────┘                  └─────────────┘
      │                                 │
      │                                 │
      ▼                                 ▼
┌─────────────┐                  ┌─────────────┐
│  文件系统    │                  │  设备驱动    │
│  (socket,   │                  │  (网络,      │
│   pipe,     │                  │   串口等)    │
│   etc.)     │                  │             │
└─────────────┘                  └─────────────┘

2.2 核心数据结构

2.2.1 eventpoll - epoll 实例

c 复制代码
// fs/eventpoll.c
struct eventpoll {
    // 保护 epoll 实例的自旋锁
    spinlock_t lock;
    
    // 等待队列:等待 epoll_wait 的进程
    wait_queue_head_t wq;
    
    // 等待队列:等待文件描述符就绪的进程
    wait_queue_head_t poll_wait;
    
    // 就绪事件列表(双向链表)
    struct list_head rdllist;
    
    // 红黑树根:存储所有监控的文件描述符
    struct rb_root_cached rbr;
    
    // 就绪事件的数量
    u32 rdllist_count;
    
    // 唤醒回调函数
    struct wakeup_source *ws;
    
    // 用户空间事件数组的指针
    struct epitem *ovflist;
    
    // epoll 文件描述符
    struct file *file;
    
    // 用于避免循环检测
    struct user_struct *user;
    
    // 事件循环嵌套深度
    int nested_has_wakeup;
};

2.2.2 epitem - 监控的文件描述符项

c 复制代码
// fs/eventpoll.c
struct epitem {
    // 红黑树节点
    union {
        struct rb_node rbn;      // 用于红黑树
        struct rcu_head rcu;      // 用于 RCU 释放
    };
    
    // 就绪列表节点
    struct list_head rdllink;     // 链接到 rdllist
    
    // epoll 实例
    struct eventpoll *ep;
    
    // 监控的文件描述符
    struct epoll_filefd ffd;
    
    // 用户空间事件结构
    struct epoll_event event;
    
    // 等待队列项
    struct wait_queue_entry wq;
    
    // 文件描述符的等待队列
    wait_queue_head_t *whead;
};

2.2.3 epoll_event - 用户空间事件结构

c 复制代码
// include/uapi/linux/eventpoll.h
struct epoll_event {
    __u32 events;      // 事件类型:
                       // EPOLLIN  - 可读
                       // EPOLLOUT - 可写
                       // EPOLLERR - 错误
                       // EPOLLHUP - 挂起
                       // EPOLLET  - 边缘触发
                       // EPOLLONESHOT - 一次性
                       // EPOLLRDHUP - 对端关闭
    __u64 data;        // 用户数据(可以是 fd、指针等)
};

2.3 数据结构关系图

scss 复制代码
eventpoll (epoll 实例)
├── rbr (红黑树根)
│   ├── epitem1 (fd1)
│   ├── epitem2 (fd2)
│   └── epitem3 (fd3)
│
└── rdllist (就绪列表)
    ├── epitem1 (fd1 就绪)
    └── epitem3 (fd3 就绪)

3. epoll_create / epoll_create1

3.1 系统调用定义

c 复制代码
// fs/eventpoll.c
SYSCALL_DEFINE1(epoll_create, int, size)
{
    if (size <= 0)
        return -EINVAL;
    
    return sys_epoll_create1(0);
}

SYSCALL_DEFINE1(epoll_create1, int, flags)
{
    int error, fd;
    struct eventpoll *ep = NULL;
    struct file *file;
    
    // 1. 检查标志
    if (flags & ~EPOLL_CLOEXEC)
        return -EINVAL;
    
    // 2. 分配 eventpoll 结构
    error = ep_alloc(&ep);
    if (error < 0)
        return error;
    
    // 3. 获取未使用的文件描述符
    fd = get_unused_fd_flags(O_RDWR | (flags & O_CLOEXEC));
    if (fd < 0) {
        error = fd;
        goto out_free_ep;
    }
    
    // 4. 创建文件结构
    file = anon_inode_getfile("[eventpoll]", &eventpoll_fops, ep,
                              O_RDWR | (flags & O_CLOEXEC));
    if (IS_ERR(file)) {
        error = PTR_ERR(file);
        goto out_free_fd;
    }
    
    // 5. 初始化 eventpoll
    ep->file = file;
    fd_install(fd, file);
    
    return fd;
    
out_free_fd:
    put_unused_fd(fd);
out_free_ep:
    ep_free(ep);
    return error;
}

3.2 ep_alloc - 分配 eventpoll

c 复制代码
// fs/eventpoll.c
static int ep_alloc(struct eventpoll **pep)
{
    int error;
    struct user_struct *user;
    struct eventpoll *ep;
    
    // 1. 分配 eventpoll 结构
    ep = kzalloc(sizeof(*ep), GFP_KERNEL);
    if (unlikely(!ep))
        return -ENOMEM;
    
    // 2. 初始化自旋锁
    spin_lock_init(&ep->lock);
    
    // 3. 初始化等待队列
    init_waitqueue_head(&ep->wq);
    init_waitqueue_head(&ep->poll_wait);
    
    // 4. 初始化就绪列表
    INIT_LIST_HEAD(&ep->rdllist);
    ep->rdllist_count = 0;
    
    // 5. 初始化红黑树
    ep->rbr = RB_ROOT_CACHED;
    
    // 6. 初始化其他字段
    ep->ovflist = EP_UNACTIVE_PTR;
    ep->user = get_current_user();
    
    *pep = ep;
    return 0;
}

3.3 创建流程

scss 复制代码
用户空间调用 epoll_create1(0)
    ↓
进入内核 sys_epoll_create1()
    ↓
分配 eventpoll 结构
    ↓
初始化等待队列、红黑树、就绪列表
    ↓
创建匿名 inode 文件
    ↓
分配文件描述符
    ↓
返回文件描述符给用户空间

4. epoll_ctl

4.1 系统调用定义

c 复制代码
// fs/eventpoll.c
SYSCALL_DEFINE4(epoll_ctl, int, epfd, int, op, int, fd,
                struct epoll_event __user *, event)
{
    struct epoll_event epds;
    
    // 1. 复制用户空间事件结构
    if (ep_op_has_event(op) &&
        copy_from_user(&epds, event, sizeof(struct epoll_event)))
        return -EFAULT;
    
    // 2. 执行操作
    return do_epoll_ctl(epfd, op, fd, &epds, false);
}

4.2 do_epoll_ctl - 核心实现

c 复制代码
// fs/eventpoll.c
int do_epoll_ctl(int epfd, int op, int fd, struct epoll_event *epds,
                 bool nonblock)
{
    int error;
    int full_check = 0;
    struct fd f, tf;
    struct eventpoll *ep;
    struct epitem *epi;
    struct epoll_event epds;
    struct eventpoll *tep = NULL;
    
    // 1. 获取 epoll 文件描述符
    f = fdget(epfd);
    if (!f.file)
        return -EBADF;
    
    // 2. 检查是否是 epoll 文件
    if (!is_file_epoll(f.file)) {
        error = -EINVAL;
        goto error_tgt_fput;
    }
    
    // 3. 获取目标文件描述符
    tf = fdget(fd);
    if (!tf.file) {
        error = -EBADF;
        goto error_tgt_fput;
    }
    
    // 4. 获取 eventpoll 结构
    ep = f.file->private_data;
    
    // 5. 根据操作类型执行
    switch (op) {
    case EPOLL_CTL_ADD:
        // 添加文件描述符到 epoll
        error = ep_insert(ep, &epds, tf.file, fd, full_check);
        break;
        
    case EPOLL_CTL_DEL:
        // 从 epoll 删除文件描述符
        error = ep_remove(ep, tf.file, fd);
        break;
        
    case EPOLL_CTL_MOD:
        // 修改文件描述符的事件
        error = ep_modify(ep, tf.file, fd, &epds);
        break;
        
    default:
        error = -EINVAL;
        break;
    }
    
error_tgt_fput:
    if (full_check)
        clear_tfile_check_list();
    fdput(tf);
error_tgt_fput:
    fdput(f);
    return error;
}

4.3 ep_insert - 添加文件描述符

c 复制代码
// fs/eventpoll.c
static int ep_insert(struct eventpoll *ep,
                     const struct epoll_event *event,
                     struct file *tfile, int fd, int full_check)
{
    int error, pwake = 0;
    __poll_t revents;
    long user_watches;
    struct epitem *epi;
    struct ep_pqueue epq;
    
    // 1. 检查用户限制
    user_watches = atomic_long_read(&ep->user->epoll_watches);
    if (unlikely(user_watches >= max_user_watches))
        return -ENOSPC;
    
    // 2. 分配 epitem
    if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL)))
        return -ENOMEM;
    
    // 3. 初始化 epitem
    INIT_LIST_HEAD(&epi->rdllink);
    INIT_LIST_HEAD(&epi->fllink);
    INIT_LIST_HEAD(&epi->pwqlist);
    epi->ep = ep;
    ep_set_ffd(&epi->ffd, tfile, fd);
    epi->event = *event;
    epi->nwait = 0;
    epi->next = EP_UNACTIVE_PTR;
    
    // 4. 初始化等待队列项
    ep_setup_wait(&epq, epi);
    
    // 5. 将 epitem 插入红黑树
    spin_lock(&ep->lock);
    ep_rbtree_insert(ep, epi);
    spin_unlock(&ep->lock);
    
    // 6. 注册回调函数到文件描述符
    revents = ep_item_poll(epi, &epq.pt, 1);
    
    // 7. 如果文件已经就绪,添加到就绪列表
    if (revents && !ep_is_linked(&epi->rdllink)) {
        list_add_tail(&epi->rdllist, &ep->rdllist);
        ep_pm_stay_awake_rcu(epi);
        
        // 如果有进程在等待,唤醒它
        if (waitqueue_active(&ep->wq))
            wake_up(&ep->wq);
    }
    
    // 8. 增加用户计数
    atomic_long_inc(&ep->user->epoll_watches);
    
    return 0;
}

4.4 ep_item_poll - 注册回调

c 复制代码
// fs/eventpoll.c
static __poll_t ep_item_poll(const struct epitem *epi, poll_table *pt,
                             int depth)
{
    struct file *file = epi->ffd.file;
    __poll_t res;
    
    // 1. 设置等待队列项
    pt->_key = epi->event.events;
    
    // 2. 调用文件的 poll 方法
    res = vfs_poll(file, pt);
    
    // 3. 如果使用边缘触发,只返回新的事件
    if (epi->event.events & EPOLLET) {
        res &= epi->event.events | EPOLLONESHOT | EPOLLRDHUP;
    }
    
    return res;
}

4.5 回调机制

当文件描述符就绪时,会调用回调函数:

c 复制代码
// fs/eventpoll.c
static int ep_poll_callback(wait_queue_entry_t *wait, unsigned mode, int sync, void *key)
{
    int pwake = 0;
    unsigned long flags;
    struct epitem *epi = ep_item_from_wait(wait);
    struct eventpoll *ep = epi->ep;
    __poll_t pollflags = key_to_poll(key);
    int ewake = 0;
    
    // 1. 获取锁
    spin_lock_irqsave(&ep->lock, flags);
    
    // 2. 检查事件是否匹配
    if (!(epi->event.events & ~EPOLLEXCLUSIVE_BITS))
        goto out_unlock;
    
    // 3. 检查是否已经在就绪列表中
    if (ep_is_linked(&epi->rdllink))
        goto out_unlock;
    
    // 4. 添加到就绪列表
    list_add_tail(&epi->rdllink, &ep->rdllist);
    ep_pm_stay_awake_rcu(epi);
    
    // 5. 如果有进程在等待,唤醒它
    if (waitqueue_active(&ep->wq)) {
        if ((epi->event.events & EPOLLEXCLUSIVE) &&
            !(pollflags & POLLFREE))
            __add_wait_queue_exclusive(&ep->wq, &epi->wq);
        else
            wake_up(&ep->wq);
    }
    
    if (waitqueue_active(&ep->poll_wait))
        pwake++;
    
out_unlock:
    spin_unlock_irqrestore(&ep->lock, flags);
    
    if (pwake)
        ep_poll_safewake(&ep->poll_wait);
    
    return 1;
}

5. epoll_wait / epoll_pwait

5.1 系统调用定义

c 复制代码
// fs/eventpoll.c
SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events,
                int, maxevents, int, timeout)
{
    return do_epoll_wait(epfd, events, maxevents, timeout);
}

SYSCALL_DEFINE6(epoll_pwait, int, epfd, struct epoll_event __user *, events,
                int, maxevents, int, timeout, const sigset_t __user *, sigmask,
                size_t, sigsetsize)
{
    sigset_t ksigmask, sigsaved;
    
    // 1. 设置信号掩码
    if (sigmask) {
        if (copy_from_user(&ksigmask, sigmask, sizeof(ksigmask)))
            return -EFAULT;
        sigdelsetmask(&ksigmask, sigmask(SIGKILL) | sigmask(SIGSTOP));
        sigprocmask(SIG_SETMASK, &ksigmask, &sigsaved);
    }
    
    // 2. 调用 epoll_wait
    int error = do_epoll_wait(epfd, events, maxevents, timeout);
    
    // 3. 恢复信号掩码
    if (sigmask)
        sigprocmask(SIG_SETMASK, &sigsaved, NULL);
    
    return error;
}

5.2 do_epoll_wait - 核心实现

c 复制代码
// fs/eventpoll.c
static int do_epoll_wait(int epfd, struct epoll_event __user *events,
                        int maxevents, int timeout)
{
    int error;
    struct fd f;
    struct eventpoll *ep;
    
    // 1. 参数检查
    if (maxevents <= 0 || maxevents > EP_MAX_EVENTS)
        return -EINVAL;
    
    // 2. 获取 epoll 文件描述符
    f = fdget(epfd);
    if (!f.file)
        return -EBADF;
    
    // 3. 检查是否是 epoll 文件
    if (!is_file_epoll(f.file)) {
        error = -EINVAL;
        goto error_fput;
    }
    
    // 4. 获取 eventpoll 结构
    ep = f.file->private_data;
    
    // 5. 等待事件
    error = ep_poll(ep, events, maxevents, timeout);
    
error_fput:
    fdput(f);
    return error;
}

5.3 ep_poll - 等待事件的核心函数

c 复制代码
// fs/eventpoll.c
static int ep_poll(struct eventpoll *ep, struct epoll_event __user *events,
                   int maxevents, long timeout)
{
    int res = 0, eavail, timed_out = 0;
    unsigned long flags;
    u64 slack = 0;
    wait_queue_entry_t wait;
    ktime_t expires, *to = NULL;
    
    // 1. 计算超时时间
    if (timeout > 0) {
        struct timespec64 end_time = ep_set_mstimeout(timeout);
        slack = select_estimate_accuracy(&end_time);
        to = &expires;
        *to = timespec64_to_ktime(end_time);
    } else if (timeout == 0) {
        // 非阻塞模式
        timed_out = 1;
    }
    
    // 2. 进入循环
fetch_events:
    spin_lock_irqsave(&ep->lock, flags);
    
    // 3. 检查是否有就绪事件
    eavail = ep_events_available(ep);
    if (eavail)
        goto send_events;
    
    // 4. 如果没有就绪事件,准备等待
    if (!eavail) {
        // 初始化等待队列项
        init_waitqueue_entry(&wait, current);
        __add_wait_queue_exclusive(&ep->wq, &wait);
        
        // 循环等待
        for (;;) {
            // 5. 设置进程状态为可中断睡眠
            set_current_state(TASK_INTERRUPTIBLE);
            
            // 6. 再次检查是否有就绪事件
            eavail = ep_events_available(ep);
            if (eavail)
                break;
            
            // 7. 检查是否有信号待处理
            if (signal_pending(current)) {
                res = -EINTR;
                break;
            }
            
            // 8. 检查超时
            if (timeout != -1) {
                unsigned long expires = ep_set_mstimeout(timeout);
                if (time_after_eq(jiffies, expires)) {
                    timed_out = 1;
                    break;
                }
            }
            
            // 9. 释放锁并进入睡眠
            spin_unlock_irqrestore(&ep->lock, flags);
            
            // ========== 关键点:进程在这里进入睡眠 ==========
            // 如果 timeout 很大(如 30000ms),进程会在这里等待很长时间
            // 在这期间,即使收到冻结信号,进程也不会返回
            // 只有超时或事件到来时,进程才会被唤醒
            
            if (!schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS)) {
                timed_out = 1;
            }
            
            // 10. 被唤醒后重新获取锁
            spin_lock_irqsave(&ep->lock, flags);
        }
        
        // 11. 从等待队列移除
        __remove_wait_queue(&ep->wq, &wait);
        __set_current_state(TASK_RUNNING);
    }
    
send_events:
    // 12. 检查是否需要发送事件
    if (!res && eavail &&
        !(res = ep_send_events(ep, events, maxevents)) && !timed_out)
        goto fetch_events;
    
    spin_unlock_irqrestore(&ep->lock, flags);
    
    return res;
}

5.4 ep_events_available - 检查就绪事件

c 复制代码
// fs/eventpoll.c
static inline int ep_events_available(struct eventpoll *ep)
{
    return !list_empty(&ep->rdllist) || ep->ovflist != EP_UNACTIVE_PTR;
}

5.5 ep_send_events - 发送事件到用户空间

c 复制代码
// fs/eventpoll.c
static int ep_send_events(struct eventpoll *ep,
                          struct epoll_event __user *events, int maxevents)
{
    struct epitem *epi, *tmp;
    LIST_HEAD(txlist);
    int res = 0;
    
    // 1. 获取就绪列表
    mutex_lock(&ep->mtx);
    ep_start_scan(ep, &txlist);
    mutex_unlock(&ep->mtx);
    
    // 2. 遍历就绪列表
    list_for_each_entry_safe(epi, tmp, &txlist, rdllink) {
        struct epoll_event event;
        
        // 3. 获取事件
        event = epi->event;
        
        // 4. 如果是边缘触发,从就绪列表移除
        if (epi->event.events & EPOLLET) {
            list_del_init(&epi->rdllink);
        }
        
        // 5. 复制到用户空间
        if (__put_user(event.events, &events[res].events) ||
            __put_user(event.data, &events[res].data)) {
            list_add(&epi->rdllink, &ep->rdllist);
            ep_pm_stay_awake(epi);
            if (!res)
                res = -EFAULT;
            break;
        }
        res++;
        
        if (res >= maxevents)
            break;
    }
    
    // 6. 将剩余项放回就绪列表
    ep_done_scan(ep, &txlist);
    
    return res;
}

5.6 关键点:schedule_hrtimeout_range

c 复制代码
// kernel/time/hrtimer.c
long __sched schedule_hrtimeout_range(ktime_t *expires, u64 delta,
                                      const enum hrtimer_mode mode)
{
    struct hrtimer_sleeper t;
    
    // 1. 设置高精度定时器
    hrtimer_init_sleeper_on_stack(&t, CLOCK_MONOTONIC, mode);
    hrtimer_set_expires_range_ns(&t.timer, *expires, delta);
    
    // 2. 进入睡眠
    do {
        set_current_state(TASK_INTERRUPTIBLE);
        hrtimer_sleeper_start_expires(&t, mode);
        
        if (likely(t.task))
            schedule();  // ← 进程在这里被调度出去
        
        hrtimer_cancel(&t.timer);
        mode = HRTIMER_MODE_ABS;
        
    } while (t.task && !signal_pending(current));
    
    __set_current_state(TASK_RUNNING);
    
    destroy_hrtimer_on_stack(&t.timer);
    
    return 0;
}

关键问题

  • 如果 timeout 很大(如 30000ms),schedule_hrtimeout_range 会设置一个很长的定时器
  • 进程会进入 TASK_INTERRUPTIBLE 状态,等待定时器到期或事件到来
  • 即使收到冻结信号,进程也不会立即返回
  • 只有定时器到期或事件到来时,进程才会被唤醒

6. 内核实现详解

6.1 等待队列机制

epoll 使用等待队列来管理等待事件的进程:

c 复制代码
// include/linux/wait.h
struct wait_queue_head {
    spinlock_t lock;
    struct list_head head;
};

struct wait_queue_entry {
    unsigned int flags;
    void *private;
    wait_queue_func_t func;
    struct list_head entry;
};

工作流程

  1. 进程调用 epoll_wait
  2. 如果没有就绪事件,进程被添加到 ep->wq 等待队列
  3. 当文件描述符就绪时,回调函数 ep_poll_callback 被调用
  4. 回调函数唤醒等待队列中的进程
  5. 进程被唤醒,检查就绪事件并返回

6.2 红黑树管理

epoll 使用红黑树来高效管理监控的文件描述符:

c 复制代码
// fs/eventpoll.c
static void ep_rbtree_insert(struct eventpoll *ep, struct epitem *epi)
{
    int kcmp;
    struct rb_node **rbp, *parent;
    struct epitem *epic;
    bool leftmost = true;
    
    rbp = &ep->rbr.rb_root.rb_node;
    parent = NULL;
    
    while (*rbp) {
        parent = *rbp;
        epic = rb_entry(parent, struct epitem, rbn);
        
        kcmp = ep_cmp_ffd(&epi->ffd, &epic->ffd);
        if (kcmp > 0) {
            rbp = &parent->rb_right;
            leftmost = false;
        } else
            rbp = &parent->rb_left;
    }
    
    rb_link_node(&epi->rbn, parent, rbp);
    rb_insert_color_cached(&epi->rbn, &ep->rbr, leftmost);
}

优势

  • 插入、删除、查找的时间复杂度都是 O(log n)
  • 适合管理大量文件描述符

6.3 就绪列表管理

就绪列表是一个双向链表,存储所有就绪的文件描述符:

c 复制代码
// fs/eventpoll.c
// 添加到就绪列表
list_add_tail(&epi->rdllink, &ep->rdllist);

// 从就绪列表移除
list_del_init(&epi->rdllink);

优势

  • O(1) 时间复杂度的添加和移除
  • 只返回就绪的文件描述符,不需要遍历所有 fd

7. 与进程冻结的关系

7.1 问题场景

c 复制代码
// 问题代码
int nfds = epoll_wait(epfd, events, maxevents, 30000);  // 30秒超时

时间线

ini 复制代码
T=0s:   进程调用 epoll_wait(..., 30000)
        ↓
T=0s:   进入内核 do_epoll_wait()
        ↓
T=0s:   调用 ep_poll()
        ↓
T=0s:   检查就绪事件:没有
        ↓
T=0s:   调用 schedule_hrtimeout_range(..., 30000ms)
        ↓
T=0s:   设置高精度定时器,30秒后到期
        ↓
T=0s:   进程进入 TASK_INTERRUPTIBLE 状态
        ↓
T=0s:   调用 schedule(),进程被调度出去
        ↓
T=5s:   系统尝试挂起,发送冻结信号
        ↓
T=5s:   冻结信号唤醒进程
        ↓
T=5s:   进程检查:有事件吗?没有
        ↓
T=5s:   进程检查:超时了吗?没有(还有25秒)
        ↓
T=5s:   进程检查:有信号吗?有,但定时器还没到期
        ↓
T=5s:   进程继续等待定时器到期 ← 问题!
        ↓
T=30s:  定时器到期,进程被唤醒
        ↓
T=30s:  系统调用返回,检查冻结条件
        ↓
T=30s:  发现需要冻结,进入 __refrigerator()
        ↓
T=30s:  进程成功冻结(但已经晚了25秒!)

7.2 为什么信号不能立即中断?

虽然 epoll_wait 是可中断的系统调用(使用 TASK_INTERRUPTIBLE),但问题在于:

  1. 信号会唤醒进程:冻结信号会唤醒进程
  2. 但进程会继续等待:如果定时器还没到期,进程会重新进入睡眠
  3. 只有定时器到期或事件到来才会返回:进程只有在这些情况下才会从系统调用返回

7.3 修复方案

c 复制代码
// 修复后的代码
int running = 1;
while (running) {
    // 使用短超时(1秒)
    int nfds = epoll_wait(epfd, events, maxevents, 1000);
    
    if (nfds > 0) {
        // 处理事件
        handle_events(events, nfds);
    } else if (nfds == 0) {
        // 超时,检查是否需要退出
        if (should_exit()) {
            running = 0;
        }
    } else {
        // 错误处理
        if (errno == EINTR) {
            // 被信号中断,继续循环
            continue;
        }
        // 其他错误处理
    }
}

修复后的时间线

ini 复制代码
T=0s:   进程调用 epoll_wait(..., 1000)
        ↓
T=1s:   epoll_wait 超时返回(正常情况)
        ↓
T=1s:   系统调用返回,检查冻结条件
        ↓
T=1s:   发现不需要冻结,继续循环
        ↓
T=5s:   系统尝试挂起,发送冻结信号
        ↓
T=5s:   进程正在准备调用 epoll_wait
        ↓
T=5s:   进程调用 epoll_wait(..., 1000)
        ↓
T=5s:   进入内核,检查 TIF_SIGPENDING 标志
        ↓
T=5s:   发现需要冻结,epoll_wait 可能提前返回
        ↓
T=5s:   系统调用返回,检查冻结条件
        ↓
T=5s:   发现需要冻结,进入 __refrigerator()
        ↓
T=5s:   进程成功冻结!← 只用了5秒,而不是30秒!

8. 性能对比

8.1 select vs poll vs epoll

操作 select poll epoll
添加 fd O(n) O(n) O(log n)
等待事件 O(n) O(n) O(1)
返回就绪 fd O(n) O(n) O(1)
fd 限制 1024 无限制 无限制

8.2 实际性能测试

yaml 复制代码
测试场景:监控 1000 个文件描述符,其中 100 个就绪

select:   ~10ms
poll:     ~10ms
epoll:    ~0.1ms  ← 快 100 倍!

9. 最佳实践

9.1 使用短超时

c 复制代码
// ✅ 正确:使用短超时
int nfds = epoll_wait(epfd, events, maxevents, 1000);  // 1秒

// ❌ 错误:使用长超时
int nfds = epoll_wait(epfd, events, maxevents, 30000);  // 30秒

9.2 处理 EINTR

c 复制代码
int nfds;
do {
    nfds = epoll_wait(epfd, events, maxevents, timeout);
} while (nfds == -1 && errno == EINTR);

if (nfds > 0) {
    // 处理事件
}

9.3 使用边缘触发(ET)模式

c 复制代码
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;  // 边缘触发
ev.data.fd = fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);

优势

  • 减少事件通知次数
  • 提高性能

9.4 使用 epoll_pwait 处理信号

c 复制代码
sigset_t sigmask;
sigemptyset(&sigmask);
sigaddset(&sigmask, SIGTERM);

int nfds = epoll_pwait(epfd, events, maxevents, timeout, &sigmask);

10. 总结

10.1 关键点

  1. epoll 是高效的 I/O 事件通知机制
  2. 使用红黑树管理文件描述符,O(log n) 复杂度
  3. 使用就绪列表返回事件,O(1) 复杂度
  4. 使用等待队列管理等待的进程
  5. 长时间超时会导致进程冻结问题

10.2 与进程冻结的关系

  • 问题:长时间超时(如 30 秒)会导致进程无法及时响应冻结请求
  • 原因 :进程在 schedule_hrtimeout_range 中等待,即使收到信号也不会立即返回
  • 解决:使用短超时(如 1 秒),让系统调用频繁返回,及时检查冻结条件

10.3 最佳实践

  1. 使用短超时(1 秒或更短)
  2. 正确处理 EINTR
  3. 使用边缘触发模式提高性能
  4. 使用 epoll_pwait 处理信号

文档结束

相关推荐
Shawn_CH7 小时前
Linux 进程冻结机制原理详解
嵌入式
黑客思维者2 天前
XGW-9000系列高端新能源电站边缘网关硬件架构设计
网络·架构·硬件架构·嵌入式·新能源·计算机硬件·电站
神圣的大喵2 天前
平台无关的嵌入式通用按键管理器
c语言·单片机·嵌入式硬件·嵌入式·按键库
网易独家音乐人Mike Zhou2 天前
【嵌入式模块芯片开发】LP87524电源PMIC芯片配置流程,给雷达供电的延时上电时序及API函数
c语言·stm32·单片机·51单片机·嵌入式·电源·毫米波雷达
Nerd Nirvana2 天前
WSL——Windows Subsystem for Linux流程一览
linux·运维·服务器·windows·嵌入式·wsl·wsl2
rechol2 天前
mcu启动流程
stm32·单片机·mcu·嵌入式
MounRiver_Studio3 天前
RISC-V IDE MRS2使用笔记(七):书签与笔记功能
ide·嵌入式·risc-v
切糕师学AI3 天前
ARM 架构中的 PRIMASK、FAULTMAST、BASEPRI 寄存器
arm开发·架构·嵌入式·寄存器
迷人的星空4 天前
嵌入式软件调试指南:一看就懂,上手就用
嵌入式