优秀的编程知识分享平台

网站首页 > 技术文章 正文

linux 内核poll/select/epoll实现剖析(经典)-上

nanyue 2024-11-03 14:06:28 技术文章 9 ℃

推荐视频:

linux多线程之epoll原理剖析与reactor原理及应用

linux下的epoll实战揭秘——支撑亿级IO的底层基石

c/c++ linux服务器开发学习地址:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂

poll/select/epoll的实现都是基于文件提供的poll方法(f_op->poll),该方法利用poll_table提供的_qproc方法向文件内部事件掩码_key对应的的一个或多个等待队列(wait_queue_head_t)上添加包含唤醒函数(wait_queue_t.func)的节点(wait_queue_t),并检查文件当前就绪的状态返回给poll的调用者(依赖于文件的实现)。

当文件的状态发生改变时(例如网络数据包到达),文件就会遍历事件对应的等待队列并调用回调函数(wait_queue_t.func)唤醒等待线程。

通常的file.f_ops.poll实现及相关结构体如下

struct file {  
    const struct file_operations    *f_op;  
    spinlock_t          f_lock;  
    // 文件内部实现细节  
    void               *private_data;  
#ifdef CONFIG_EPOLL  
    /* Used by fs/eventpoll.c to link all the hooks to this file */  
    struct list_head    f_ep_links;  
    struct list_head    f_tfile_llink;  
#endif /* #ifdef CONFIG_EPOLL */  
    // 其他细节....  
};  
  
// 文件操作  
struct file_operations {  
    // 文件提供给poll/select/epoll  
    // 获取文件当前状态, 以及就绪通知接口函数  
    unsigned int (*poll) (struct file *, struct poll_table_struct *);  
    // 其他方法read/write 等... ...  
};  
  
// 通常的file.f_ops.poll 方法的实现  
unsigned int file_f_op_poll (struct file *filp, struct poll_table_struct *wait)  
{  
    unsigned int mask = 0;  
    wait_queue_head_t * wait_queue;  
  
    //1. 根据事件掩码wait->key_和文件实现filep->private_data 取得事件掩码对应的一个或多个wait queue head  
    some_code();  
  
    // 2. 调用poll_wait 向获得的wait queue head 添加节点  
    poll_wait(filp, wait_queue, wait);  
  
    // 3. 取得当前就绪状态保存到mask  
    some_code();  
  
    return mask;  
}  
  
// select/poll/epoll 向文件注册就绪后回调节点的接口结构  
typedef struct poll_table_struct {  
    // 向wait_queue_head 添加回调节点(wait_queue_t)的接口函数  
    poll_queue_proc _qproc;  
    // 关注的事件掩码, 文件的实现利用此掩码将等待队列传递给_qproc  
    unsigned long   _key;  
} poll_table;  
typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);  
  
  
// 通用的poll_wait 函数, 文件的f_ops->poll 通常会调用此函数  
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)  
{  
    if (p && p->_qproc && wait_address) {  
        // 调用_qproc 在wait_address 上添加节点和回调函数  
        // 调用 poll_table_struct 上的函数指针向wait_address添加节点, 并设置节点的func  
        // (如果是select或poll 则是 __pollwait, 如果是 epoll 则是 ep_ptable_queue_proc),  
        p->_qproc(filp, wait_address, p);  
    }  
}  
  
  
// wait_queue 头节点  
typedef struct __wait_queue_head wait_queue_head_t;  
struct __wait_queue_head {  
    spinlock_t lock;  
    struct list_head task_list;  
};  
  
// wait_queue 节点  
typedef struct __wait_queue wait_queue_t;  
struct __wait_queue {  
    unsigned int flags;  
#define WQ_FLAG_EXCLUSIVE   0x01  
    void *private;  
    wait_queue_func_t func;  
    struct list_head task_list;  
};  
typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int flags, void *key);  
  
  
// 当文件的状态发生改变时, 文件会调用此函数,此函数通过调用wait_queue_t.func通知poll的调用者  
// 其中key是文件当前的事件掩码  
void __wake_up(wait_queue_head_t *q, unsigned int mode,  
               int nr_exclusive, void *key)  
{  
    unsigned long flags;  
  
    spin_lock_irqsave(&q->lock, flags);  
    __wake_up_common(q, mode, nr_exclusive, 0, key);  
    spin_unlock_irqrestore(&q->lock, flags);  
}  
static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,  
                             int nr_exclusive, int wake_flags, void *key)  
{  
    wait_queue_t *curr, *next;  
    // 遍历并调用func 唤醒, 通常func会唤醒调用poll的线程  
    list_for_each_entry_safe(curr, next, &q->task_list, task_list) {  
        unsigned flags = curr->flags;  
  
        if (curr->func(curr, mode, wake_flags, key) &&  
                (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive) {  
            break;  
        }  
    }  
} 

poll 和 select

poll和select的实现基本上是一致的,只是传递参数有所不同,他们的基本流程如下:

1. 复制用户数据到内核空间

2. 估计超时时间

3. 遍历每个文件并调用f_op->poll 取得文件当前就绪状态, 如果前面遍历的文件都没有就绪,向文件插入wait_queue节点

4. 遍历完成后检查状态:

  • a). 如果已经有就绪的文件转到5;
  • b). 如果有信号产生,重启poll或select(转到 1或3);
  • c). 否则挂起进程等待超时或唤醒,超时或被唤醒后再次遍历所有文件取得每个文件的就绪状态

5. 将所有文件的就绪状态复制到用户空间

6. 清理申请的资源

关键结构体

下面是poll/select共用的结构体及其相关功能:

poll_wqueues 是 select/poll 对poll_table接口的具体化实现,其中的table, inline_index和inline_entries都是为了管理内存。

poll_table_entry 与一个文件相关联,用于管理插入到文件的wait_queue节点。

// select/poll 对poll_table的具体化实现  
struct poll_wqueues {  
    poll_table pt;  
    struct poll_table_page *table;     // 如果inline_entries 空间不足, 从poll_table_page 中分配  
    struct task_struct *polling_task;  // 调用poll 或select 的进程  
    int triggered;                     // 已触发标记  
    int error;  
    int inline_index;                  // 下一个要分配的inline_entrie 索引  
    struct poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES];//  
};  
// 帮助管理select/poll  申请的内存  
struct poll_table_page {  
    struct poll_table_page  * next;       // 下一个 page  
    struct poll_table_entry * entry;      // 指向第一个entries  
    struct poll_table_entry entries[0];  
};  
// 与一个正在poll /select 的文件相关联,  
struct poll_table_entry {  
    struct file *filp;               // 在poll/select中的文件  
    unsigned long key;  
    wait_queue_t wait;               // 插入到wait_queue_head_t 的节点  
    wait_queue_head_t *wait_address; // 文件上的wait_queue_head_t 地址  
};  

【文章福利】需要C/C++ Linux服务器架构师学习资料加群812855908(资料包括C/C++,Linux,golang技术,内核,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等)

公共函数

下面是poll/select公用的一些函数,这些函数实现了poll和select的核心功能。

poll_initwait 用于初始化poll_wqueues,

__pollwait 实现了向文件中添加回调节点的逻辑,

pollwake 当文件状态发生改变时,由文件调用,用来唤醒线程,

poll_get_entry,free_poll_entry,poll_freewait用来申请释放poll_table_entry 占用的内存,并负责释放文件上的wait_queue节点。

// poll_wqueues 的初始化:  
// 初始化 poll_wqueues , __pollwait会在文件就绪时被调用  
void poll_initwait(struct poll_wqueues *pwq)  
{  
    // 初始化poll_table, 相当于调用基类的构造函数  
    init_poll_funcptr(&pwq->pt, __pollwait);  
    /* 
     * static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc) 
     * { 
     *     pt->_qproc = qproc; 
     *     pt->_key   = ~0UL; 
     * } 
     */  
    pwq->polling_task = current;  
    pwq->triggered = 0;  
    pwq->error = 0;  
    pwq->table = NULL;  
    pwq->inline_index = 0;  
}  
  
  
// wait_queue设置函数  
// poll/select 向文件wait_queue中添加节点的方法  
static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,  
                       poll_table *p)  
{  
    struct poll_wqueues *pwq = container_of(p, struct poll_wqueues, pt);  
    struct poll_table_entry *entry = poll_get_entry(pwq);  
    if (!entry) {  
        return;  
    }  
    get_file(filp); //put_file() in free_poll_entry()  
    entry->filp = filp;  
    entry->wait_address = wait_address; // 等待队列头  
    entry->key = p->key;  
    // 设置回调为 pollwake  
    init_waitqueue_func_entry(&entry->wait, pollwake);  
    entry->wait.private = pwq;  
    // 添加到等待队列  
    add_wait_queue(wait_address, &entry->wait);  
}  
  
// 在等待队列(wait_queue_t)上回调函数(func)  
// 文件就绪后被调用,唤醒调用进程,其中key是文件提供的当前状态掩码  
static int pollwake(wait_queue_t *wait, unsigned mode, int sync, void *key)  
{  
    struct poll_table_entry *entry;  
    // 取得文件对应的poll_table_entry  
    entry = container_of(wait, struct poll_table_entry, wait);  
    // 过滤不关注的事件  
    if (key && !((unsigned long)key & entry->key)) {  
        return 0;  
    }  
    // 唤醒  
    return __pollwake(wait, mode, sync, key);  
}  
static int __pollwake(wait_queue_t *wait, unsigned mode, int sync, void *key)  
{  
    struct poll_wqueues *pwq = wait->private;  
    // 将调用进程 pwq->polling_task 关联到 dummy_wait  
    DECLARE_WAITQUEUE(dummy_wait, pwq->polling_task);  
    smp_wmb();  
    pwq->triggered = 1;// 标记为已触发  
    // 唤醒调用进程  
    return default_wake_function(&dummy_wait, mode, sync, key);  
}  
  
// 默认的唤醒函数,poll/select 设置的回调函数会调用此函数唤醒  
// 直接唤醒等待队列上的线程,即将线程移到运行队列(rq)  
int default_wake_function(wait_queue_t *curr, unsigned mode, int wake_flags,  
                          void *key)  
{  
    // 这个函数比较复杂, 这里就不具体分析了  
    return try_to_wake_up(curr->private, mode, wake_flags);  
}  

poll,select对poll_table_entry的申请和释放采用的是类似内存池的管理方式,先使用预分配的空间,预分配的空间不足时,分配一个内存页,使用内存页上的空间。

// 分配或使用已先前申请的 poll_table_entry,  
static struct poll_table_entry *poll_get_entry(struct poll_wqueues *p) {  
    struct poll_table_page *table = p->table;  
  
    if (p->inline_index < N_INLINE_POLL_ENTRIES) {  
        return p->inline_entries + p->inline_index++;  
    }  
  
    if (!table || POLL_TABLE_FULL(table)) {  
        struct poll_table_page *new_table;  
        new_table = (struct poll_table_page *) __get_free_page(GFP_KERNEL);  
        if (!new_table) {  
            p->error = -ENOMEM;  
            return NULL;  
        }  
        new_table->entry = new_table->entries;  
        new_table->next = table;  
        p->table = new_table;  
        table = new_table;  
    }  
    return table->entry++;  
}  
  
// 清理poll_wqueues 占用的资源  
void poll_freewait(struct poll_wqueues *pwq)  
{  
    struct poll_table_page * p = pwq->table;  
    // 遍历所有已分配的inline poll_table_entry  
    int i;  
    for (i = 0; i < pwq->inline_index; i++) {  
        free_poll_entry(pwq->inline_entries + i);  
    }  
    // 遍历在poll_table_page上分配的inline poll_table_entry  
    // 并释放poll_table_page  
    while (p) {  
        struct poll_table_entry * entry;  
        struct poll_table_page *old;  
        entry = p->entry;  
        do {  
            entry--;  
            free_poll_entry(entry);  
        } while (entry > p->entries);  
        old = p;  
        p = p->next;  
        free_page((unsigned long) old);  
    }  
}  
static void free_poll_entry(struct poll_table_entry *entry)  
{  
    // 从等待队列中删除, 释放文件引用计数  
    remove_wait_queue(entry->wait_address, &entry->wait);  
    fput(entry->filp);  
}  

poll/select核心结构关系

下图是 poll/select 实现公共部分的关系图,包含了与文件直接的关系,以及函数之间的依赖。

poll的实现

// poll 使用的结构体  
struct pollfd {  
    int fd;        // 描述符  
    short events;  // 关注的事件掩码  
    short revents; // 返回的事件掩码  
};  
// long sys_poll(struct pollfd *ufds, unsigned int nfds, long timeout_msecs)  
SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds,  
                long, timeout_msecs)  
{  
    struct timespec end_time, *to = NULL;  
    int ret;  
    if (timeout_msecs >= 0) {  
        to = &end_time;  
        // 将相对超时时间msec 转化为绝对时间  
        poll_select_set_timeout(to, timeout_msecs / MSEC_PER_SEC,  
                                NSEC_PER_MSEC * (timeout_msecs % MSEC_PER_SEC));  
    }  
    // do sys poll  
    ret = do_sys_poll(ufds, nfds, to);  
    // do_sys_poll 被信号中断, 重新调用, 对使用者来说 poll 是不会被信号中断的.  
    if (ret == -EINTR) {  
        struct restart_block *restart_block;  
        restart_block = ¤t_thread_info()->restart_block;  
        restart_block->fn = do_restart_poll; // 设置重启的函数  
        restart_block->poll.ufds = ufds;  
        restart_block->poll.nfds = nfds;  
        if (timeout_msecs >= 0) {  
            restart_block->poll.tv_sec = end_time.tv_sec;  
            restart_block->poll.tv_nsec = end_time.tv_nsec;  
            restart_block->poll.has_timeout = 1;  
        } else {  
            restart_block->poll.has_timeout = 0;  
        }  
        // ERESTART_RESTARTBLOCK 不会返回给用户进程,  
        // 而是会被系统捕获, 然后调用 do_restart_poll,  
        ret = -ERESTART_RESTARTBLOCK;  
    }  
    return ret;  
}  
int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,  
                struct timespec *end_time)  
{  
    struct poll_wqueues table;  
    int err = -EFAULT, fdcount, len, size;  
    /* 首先使用栈上的空间,节约内存,加速访问 */  
    long stack_pps[POLL_STACK_ALLOC/sizeof(long)];  
    struct poll_list *const head = (struct poll_list *)stack_pps;  
    struct poll_list *walk = head;  
    unsigned long todo = nfds;  
    if (nfds > rlimit(RLIMIT_NOFILE)) {  
        // 文件描述符数量超过当前进程限制  
        return -EINVAL;  
    }  
    // 复制用户空间数据到内核  
    len = min_t(unsigned int, nfds, N_STACK_PPS);  
    for (;;) {  
        walk->next = NULL;  
        walk->len = len;  
        if (!len) {  
            break;  
        }  
        // 复制到当前的 entries  
        if (copy_from_user(walk->entries, ufds + nfds-todo,  
                           sizeof(struct pollfd) * walk->len)) {  
            goto out_fds;  
        }  
        todo -= walk->len;  
        if (!todo) {  
            break;  
        }  
        // 栈上空间不足,在堆上申请剩余部分  
        len = min(todo, POLLFD_PER_PAGE);  
        size = sizeof(struct poll_list) + sizeof(struct pollfd) * len;  
        walk = walk->next = kmalloc(size, GFP_KERNEL);  
        if (!walk) {  
            err = -ENOMEM;  
            goto out_fds;  
        }  
    }  
    // 初始化 poll_wqueues 结构, 设置函数指针_qproc  为__pollwait  
    poll_initwait(&table);  
    // poll  
    fdcount = do_poll(nfds, head, &table, end_time);  
    // 从文件wait queue 中移除对应的节点, 释放entry.  
    poll_freewait(&table);  
    // 复制结果到用户空间  
    for (walk = head; walk; walk = walk->next) {  
        struct pollfd *fds = walk->entries;  
        int j;  
        for (j = 0; j < len; j++, ufds++)  
            if (__put_user(fds[j].revents, &ufds->revents)) {  
                goto out_fds;  
            }  
    }  
    err = fdcount;  
out_fds:  
    // 释放申请的内存  
    walk = head->next;  
    while (walk) {  
        struct poll_list *pos = walk;  
        walk = walk->next;  
        kfree(pos);  
    }  
    return err;  
}  
// 真正的处理函数  
static int do_poll(unsigned int nfds,  struct poll_list *list,  
                   struct poll_wqueues *wait, struct timespec *end_time)  
{  
    poll_table* pt = &wait->pt;  
    ktime_t expire, *to = NULL;  
    int timed_out = 0, count = 0;  
    unsigned long slack = 0;  
    if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {  
        // 已经超时,直接遍历所有文件描述符, 然后返回  
        pt = NULL;  
        timed_out = 1;  
    }  
    if (end_time && !timed_out) {  
        // 估计进程等待时间,纳秒  
        slack = select_estimate_accuracy(end_time);  
    }  
    // 遍历文件,为每个文件的等待队列添加唤醒函数(pollwake)  
    for (;;) {  
        struct poll_list *walk;  
        for (walk = list; walk != NULL; walk = walk->next) {  
            struct pollfd * pfd, * pfd_end;  
            pfd = walk->entries;  
            pfd_end = pfd + walk->len;  
            for (; pfd != pfd_end; pfd++) {  
                // do_pollfd 会向文件对应的wait queue 中添加节点  
                // 和回调函数(如果 pt 不为空)  
                // 并检查当前文件状态并设置返回的掩码  
                if (do_pollfd(pfd, pt)) {  
                    // 该文件已经准备好了.  
                    // 不需要向后面文件的wait queue 中添加唤醒函数了.  
                    count++;  
                    pt = NULL;  
                }  
            }  
        }  
        // 下次循环的时候不需要向文件的wait queue 中添加节点,  
        // 因为前面的循环已经把该添加的都添加了  
        pt = NULL;  
  
        // 第一次遍历没有发现ready的文件  
        if (!count) {  
            count = wait->error;  
            // 有信号产生  
            if (signal_pending(current)) {  
                count = -EINTR;  
            }  
        }  
  
        // 有ready的文件或已经超时  
        if (count || timed_out) {  
            break;  
        }  
        // 转换为内核时间  
        if (end_time && !to) {  
            expire = timespec_to_ktime(*end_time);  
            to = &expire;  
        }  
        // 等待事件就绪, 如果有事件发生或超时,就再循  
        // 环一遍,取得事件状态掩码并计数,  
        // 注意此次循环中, 文件 wait queue 中的节点依然存在  
        if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack)) {  
            timed_out = 1;  
        }  
    }  
    return count;  
}  
  
  
static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait)  
{  
    unsigned int mask;  
    int fd;  
    mask = 0;  
    fd = pollfd->fd;  
    if (fd >= 0) {  
        int fput_needed;  
        struct file * file;  
        // 取得fd对应的文件结构体  
        file = fget_light(fd, &fput_needed);  
        mask = POLLNVAL;  
        if (file != NULL) {  
            // 如果没有 f_op 或 f_op->poll 则认为文件始终处于就绪状态.  
            mask = DEFAULT_POLLMASK;  
            if (file->f_op && file->f_op->poll) {  
                if (pwait) {  
                    // 设置关注的事件掩码  
                    pwait->key = pollfd->events | POLLERR | POLLHUP;  
                }  
                // 注册回调函数,并返回当前就绪状态,就绪后会调用pollwake  
                mask = file->f_op->poll(file, pwait);  
            }  
            mask &= pollfd->events | POLLERR | POLLHUP; // 移除不需要的状态掩码  
            fput_light(file, fput_needed);// 释放文件  
        }  
    }  
    pollfd->revents = mask; // 更新事件状态  
    return mask;  
}  
  
  
static long do_restart_poll(struct restart_block *restart_block)  
{  
    struct pollfd __user *ufds = restart_block->poll.ufds;  
    int nfds = restart_block->poll.nfds;  
    struct timespec *to = NULL, end_time;  
    int ret;  
    if (restart_block->poll.has_timeout) {  
        // 获取先前的超时时间  
        end_time.tv_sec = restart_block->poll.tv_sec;  
        end_time.tv_nsec = restart_block->poll.tv_nsec;  
        to = &end_time;  
    }  
    ret = do_sys_poll(ufds, nfds, to); // 重新调用 do_sys_poll  
    if (ret == -EINTR) {  
        // 又被信号中断了, 再次重启  
        restart_block->fn = do_restart_poll;  
        ret = -ERESTART_RESTARTBLOCK;  
    }  
    return ret;  
}  

select 实现

typedef struct {  
    unsigned long *in, *out, *ex;  
    unsigned long *res_in, *res_out, *res_ex;  
} fd_set_bits;  
//  long sys_select(int n, fd_set *inp, fd_set *outp, fd_set *exp, struct timeval *tvp)  
SYSCALL_DEFINE5(select, int, n, fd_set __user *, inp, fd_set __user *, outp,  
                fd_set __user *, exp, struct timeval __user *, tvp)  
{  
    struct timespec end_time, *to = NULL;  
    struct timeval tv;  
    int ret;  
    if (tvp) {  
        if (copy_from_user(&tv, tvp, sizeof(tv))) {  
            return -EFAULT;  
        }  
        // 计算超时时间  
        to = &end_time;  
        if (poll_select_set_timeout(to,  
                                    tv.tv_sec + (tv.tv_usec / USEC_PER_SEC),  
                                    (tv.tv_usec % USEC_PER_SEC) * NSEC_PER_USEC)) {  
            return -EINVAL;  
        }  
    }  
    ret = core_sys_select(n, inp, outp, exp, to);  
    // 复制剩余时间到用户空间  
    ret = poll_select_copy_remaining(&end_time, tvp, 1, ret);  
    return ret;  
}  
  
int core_sys_select(int n, fd_set __user *inp, fd_set __user *outp,  
                    fd_set __user *exp, struct timespec *end_time)  
{  
    fd_set_bits fds;  
    void *bits;  
    int ret, max_fds;  
    unsigned int size;  
    struct fdtable *fdt;  
    //小对象使用栈上的空间,节约内存, 加快访问速度  
    long stack_fds[SELECT_STACK_ALLOC/sizeof(long)];  
  
    ret = -EINVAL;  
    if (n < 0) {  
        goto out_nofds;  
    }  
  
    rcu_read_lock();  
    // 取得进程对应的 fdtable  
    fdt = files_fdtable(current->files);  
    max_fds = fdt->max_fds;  
    rcu_read_unlock();  
    if (n > max_fds) {  
        n = max_fds;  
    }  
  
    size = FDS_BYTES(n);  
    bits = stack_fds;  
    if (size > sizeof(stack_fds) / 6) {  
        // 栈上的空间不够, 申请内存, 全部使用堆上的空间  
        ret = -ENOMEM;  
        bits = kmalloc(6 * size, GFP_KERNEL);  
        if (!bits) {  
            goto out_nofds;  
        }  
    }  
    fds.in     = bits;  
    fds.out    = bits +   size;  
    fds.ex     = bits + 2*size;  
    fds.res_in  = bits + 3*size;  
    fds.res_out = bits + 4*size;  
    fds.res_ex  = bits + 5*size;  
  
    // 复制用户空间到内核  
    if ((ret = get_fd_set(n, inp, fds.in)) ||  
            (ret = get_fd_set(n, outp, fds.out)) ||  
            (ret = get_fd_set(n, exp, fds.ex))) {  
        goto out;  
    }  
    // 初始化fd set  
    zero_fd_set(n, fds.res_in);  
    zero_fd_set(n, fds.res_out);  
    zero_fd_set(n, fds.res_ex);  
  
    ret = do_select(n, &fds, end_time);  
  
    if (ret < 0) {  
        goto out;  
    }  
    if (!ret) {  
        // 该返回值会被系统捕获, 并以同样的参数重新调用sys_select()  
        ret = -ERESTARTNOHAND;  
        if (signal_pending(current)) {  
            goto out;  
        }  
        ret = 0;  
    }  
  
    // 复制到用户空间  
    if (set_fd_set(n, inp, fds.res_in) ||  
            set_fd_set(n, outp, fds.res_out) ||  
            set_fd_set(n, exp, fds.res_ex)) {  
        ret = -EFAULT;  
    }  
  
out:  
    if (bits != stack_fds) {  
        kfree(bits);  
    }  
out_nofds:  
    return ret;  
}  
  
int do_select(int n, fd_set_bits *fds, struct timespec *end_time)  
{  
    ktime_t expire, *to = NULL;  
    struct poll_wqueues table;  
    poll_table *wait;  
    int retval, i, timed_out = 0;  
    unsigned long slack = 0;  
  
    rcu_read_lock();  
    // 检查fds中fd的有效性, 并获取当前最大的fd  
    retval = max_select_fd(n, fds);  
    rcu_read_unlock();  
  
    if (retval < 0) {  
        return retval;  
    }  
    n = retval;  
  
    // 初始化 poll_wqueues 结构, 设置函数指针_qproc    为__pollwait  
    poll_initwait(&table);  
    wait = &table.pt;  
    if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {  
        wait = NULL;  
        timed_out = 1;  
    }  
  
    if (end_time && !timed_out) {  
        // 估计需要等待的时间.  
        slack = select_estimate_accuracy(end_time);  
    }  
  
    retval = 0;  
    for (;;) {  
        unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;  
  
        inp = fds->in;  
        outp = fds->out;  
        exp = fds->ex;  
        rinp = fds->res_in;  
        routp = fds->res_out;  
        rexp = fds->res_ex;  
        // 遍历所有的描述符, i 文件描述符  
        for (i = 0; i < n; ++rinp, ++routp, ++rexp) {  
            unsigned long in, out, ex, all_bits, bit = 1, mask, j;  
            unsigned long res_in = 0, res_out = 0, res_ex = 0;  
            const struct file_operations *f_op = NULL;  
            struct file *file = NULL;  
            // 检查当前的 slot 中的描述符  
            in = *inp++;  
            out = *outp++;  
            ex = *exp++;  
            all_bits = in | out | ex;  
            if (all_bits == 0) { // 没有需要监听的描述符, 下一个slot  
                i += __NFDBITS;  
                continue;  
            }  
  
            for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {  
                int fput_needed;  
                if (i >= n) {  
                    break;  
                }  
                // 不需要监听描述符 i  
                if (!(bit & all_bits)) {  
                    continue;  
                }  
                // 取得文件结构  
                file = fget_light(i, &fput_needed);  
                if (file) {  
                    f_op = file->f_op;  
                    // 没有 f_op 的话就认为一直处于就绪状态  
                    mask = DEFAULT_POLLMASK;  
                    if (f_op && f_op->poll) {  
                        // 设置等待事件的掩码  
                        wait_key_set(wait, in, out, bit);  
                        /* 
                        static inline void wait_key_set(poll_table *wait, unsigned long in, 
                        unsigned long out, unsigned long bit) 
                        { 
                        wait->_key = POLLEX_SET;// (POLLPRI) 
                        if (in & bit) 
                        wait->_key |= POLLIN_SET;//(POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLERR) 
                        if (out & bit) 
                        wait->_key |= POLLOUT_SET;//POLLOUT_SET (POLLWRBAND | POLLWRNORM | POLLOUT | POLLERR) 
                        } 
                        */  
                        // 获取当前的就绪状态, 并添加到文件的对应等待队列中  
                        mask = (*f_op->poll)(file, wait);  
                        // 和poll完全一样  
                    }  
                    fput_light(file, fput_needed);  
                    // 释放文件  
                    // 检查文件 i 是否已有事件就绪,  
                    if ((mask & POLLIN_SET) && (in & bit)) {  
                        res_in |= bit;  
                        retval++;  
                        // 如果已有就绪事件就不再向其他文件的  
                        // 等待队列中添加回调函数  
                        wait = NULL;  
                    }  
                    if ((mask & POLLOUT_SET) && (out & bit)) {  
                        res_out |= bit;  
                        retval++;  
                        wait = NULL;  
                    }  
                    if ((mask & POLLEX_SET) && (ex & bit)) {  
                        res_ex |= bit;  
                        retval++;  
                        wait = NULL;  
                    }  
                }  
            }  
            if (res_in) {  
                *rinp = res_in;  
            }  
            if (res_out) {  
                *routp = res_out;  
            }  
            if (res_ex) {  
                *rexp = res_ex;  
            }  
            cond_resched();  
        }  
        wait = NULL; // 该添加回调函数的都已经添加了  
        if (retval || timed_out || signal_pending(current)) {  
            break;   // 信号发生,监听事件就绪或超时  
        }  
        if (table.error) {  
            retval = table.error; // 产生错误了  
            break;  
        }  
        // 转换到内核时间  
        if (end_time && !to) {  
            expire = timespec_to_ktime(*end_time);  
            to = &expire;  
        }  
        // 等待直到超时, 或由回调函数唤醒, 超时后会再次遍历文件描述符  
        if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,  
                                   to, slack)) {  
            timed_out = 1;  
        }  
    }  
  
    poll_freewait(&table);  
  
    return retval;  
}  
最近发表
标签列表