前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux select poll源码剖析

Linux select poll源码剖析

作者头像
233333
发布2022-05-09 20:14:39
3.1K0
发布2022-05-09 20:14:39
举报

在读select、poll源码前,需要先了解的知识点:

  1. 等待队列
  2. 文件系统(主要是进程的打开文件描述符表以及struct file)
  3. poll机制
  • 资源注册监听poll() -> poll_wait(struct file *, wait_queue_t *, poll_table *pt) -> pt->qproc(struct file *, wait_queue_t *, poll_table *)
  • 资源就绪通知callback_function(wait_queue_t *, unsigned mode, int sync, void *key)
  1. select/poll主要数据结构
  • 一个select()/poll()调用对应一个struct poll_wqueues
  • 一个监听事件对应一个struct poll_table_entry

Common

poll机制是所有多路转接的共性;调用控制块(struct poll_wqueues)与监听事件项(struct poll_table_entry)是select()与poll()的共性;事件描述集(fdset)与事件描述符(struct pollfd)是select()与poll()的特性。

代码语言:javascript
复制
/*
 * Structures and helpers for sys_poll/sys_poll
 */
struct poll_wqueues {
    poll_table pt;
    struct poll_table_page *table;
    /* 
     * 每次select()都会初始化一个poll_wqueues结构与这个
     * 调用相对应。结构中的polling_task字段指向调用进程
     * (也就是current进程)的task_struct
     *
     * 资源等待队列节点wait_queue_t中的private字段指向
     * 对应的poll_wqueues对象。因此资源就绪时,通过获得
     * poll_wqueues对象,然后访问其中的polling_task字段
     * 能够得到调用进程的PCB,然后对其进行唤醒...
     */
    struct task_struct *polling_task;
    /* 已触发标记 */
    int triggered;
    int error;
    int inline_index;
    /* poll_table_entry结构缓存数组 */
    struct poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES];
};

typedef struct poll_table_struct {
    poll_queue_proc qproc;
    unsigned long key;
} poll_table;

struct poll_table_page {
    struct poll_table_page * next;
    /* 指向下一可用的poll_table_entry结构 */
    struct poll_table_entry * entry;
    struct poll_table_entry entries[0];
};

/* 真正被挂载到资源等待队列中的结构 */
struct poll_table_entry {
    /* 链接到资源文件 */
    struct file *filp;
    /* 存储监听的事件 */
    unsigned long key;
    /* 
     * 挂载到资源等待队列的节点,其中包含了唤醒回调函数
     *
     * 节点的private成员关联poll_table_entry所属的poll_wqueues,
     */
    wait_queue_t wait;
    /* 指向资源等待队列队列头 */
    wait_queue_head_t *wait_address;
};
代码语言:javascript
复制
void poll_initwait(struct poll_wqueues *pwq)
{
    init_poll_funcptr(&pwq->pt, __pollwait);
    pwq->polling_task = current;
    pwq->triggered = 0;
    pwq->error = 0;
    pwq->table = NULL;
    pwq->inline_index = 0;
}

static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
{
    pt->qproc = qproc;
    pt->key   = ~0UL; /* all events enabled */
}

/* Add a new entry */
/**
 * __pollwait - 将poll_table_entry挂载到资源文件的监听队列
 * @file: 被监听的资源文件
 * @wait_address: 被监听的资源文件的等待队列头
 * @p: 在poll_initwait()中设置的poll_tbale
 */
static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,
                poll_table *p)
{
    /* 获取poll_wqueues */
    struct poll_wqueues *pwq = container_of(p, struct poll_wqueues, pt);
    /* 从poll_wqueues中取得一个poll_table_entry结构 */
    struct poll_table_entry *entry = poll_get_entry(pwq);
    if (!entry)
        return;
    /* 增加资源文件引用计数 */
    get_file(filp);
    /* 关联资源文件 */
    entry->filp = filp;
    /* 保存资源文件监听队列队列头 */
    entry->wait_address = wait_address;
    /* 设置想要监听事件 */
    entry->key = p->key;
    /* 
     * 初始化一个等待队列节点,其中唤醒函数设置为pollwake
     *
     * 重点!!!:
     * 唤醒函数为pollwake
     */
    init_waitqueue_func_entry(&entry->wait, pollwake);
    /*
     * 来看一下为什么等待队列节点的private要这样设计:
     * 1. 实际linux内核设计:
     *    每个wait_queue_t的private字段指向同一个poll_wqueues,然后
     *    共用的poll_wqueues中保存了指向调用进程PCB的指针,这样总共
     *    需要n + 1个指针...
     * 2. 假想设计:
     *    每个wait_queue_t的private字段指向调用进程PCB,对应的事件
     *    结构poll_table_entry中每个都保存了指向同一个poll_wqueues
     *    的指针,这样总共需要n + n个指针...
     */
    entry->wait.private = pwq;
    /* 将poll_table_entry挂载到资源文件的监听队列中 */
    add_wait_queue(wait_address, &entry->wait);
}

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;

        /* 分配一页的内存给poll_table_page使用 */
        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++;
}

void poll_freewait(struct poll_wqueues *pwq)
{
    struct poll_table_page * p = pwq->table;
    int i;
    /* 对缓存数组中的poll_table_entry进行卸载 */
    for (i = 0; i < pwq->inline_index; i++)
        free_poll_entry(pwq->inline_entries + i);
    /* 对动态内存中的poll_table_entry进行卸载 */
    while (p) {
        struct poll_table_entry * entry;
        struct poll_table_page *old;

        entry = p->entry;
        do {
            /* 一个poll_table_page对象中至少分配了一个
             * poll_table_entry,所以entry--是安全的 */
            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);
}

/**
 * pollwake - 唤醒回调函数,这个函数验证资源当前状态中是否有我们所关心的
 *            事件,如果没有,就忽略这次唤醒;如果有,就转调用__pollwake...
 * @wait: poll_table_entry.wait
 * @mode:
 * @key: 携带资源当前状态
 */
static int pollwake(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
    struct poll_table_entry *entry;

    /* 通过poll_wqueues中的wait获取poll_table_entry */
    entry = container_of(wait, struct poll_table_entry, wait);
    /* 如果资源的已就绪的状态中没有我们所关心的events的话,直接忽略返回 */
    if (key && !((unsigned long)key & entry->key))
        return 0;
    /* 有我们所关心的events,那就转调用__pollwake去处理吧... */
    return __pollwake(wait, mode, sync, key);
}

/* 资源就绪时真正调用的唤醒回调函数 */
static int __pollwake(wait_queue_t *wait, unsigned mode, int sync, void *key)
{
    /* 见__pollwait()中关于等待队列节点private字段的注释 */
    struct poll_wqueues *pwq = wait->private;
    /* 构造一个有效的等待队列节点,private字段指向调用进程的PCB */
    DECLARE_WAITQUEUE(dummy_wait, pwq->polling_task);

    /*
     * Although this function is called under waitqueue lock, LOCK
     * doesn't imply write barrier and the users expect write
     * barrier semantics on wakeup functions.  The following
     * smp_wmb() is equivalent to smp_wmb() in try_to_wake_up()
     * and is paired with set_mb() in poll_schedule_timeout.
     */
    smp_wmb();
    /* 设置已触发状态标志 */
    pwq->triggered = 1;

    /*
     * Perform the default wake up operation using a dummy
     * waitqueue.
     *
     * TODO: This is hacky but there currently is no interface to
     * pass in @sync.  @sync is scheduled to be removed and once
     * that happens, wake_up_process() can be used directly.
     */
    /* 唤醒select()的调用进程 */
    return default_wake_function(&dummy_wait, mode, sync, key);
}

select

调用链:

代码语言:javascript
复制
sys_select() -> core_sys_select() -> do_select() -> 1. poll_initwait()
                                                 -> 2. f_op->poll() [ -> poll_wait() -> __pollwait() -> poll_get_entry() ]
                                                 -> 3. [ block -> goto 2 ]
                                                 -> 4. poll_freewait() -> free_poll_entry()

pollwake() -> __pollwake()
代码语言:javascript
复制
typedef __kernel_fd_set     fd_set;

#define __NFDBITS   (8 * sizeof(unsigned long))
#define __FD_SETSIZE    1024
#define __FDSET_LONGS   (__FD_SETSIZE/__NFDBITS)

/* fd_set中能够表示的最大文件描述符为__FD_SETSIZE - 1 */
typedef struct {
    unsigned long fds_bits [__FDSET_LONGS];
} __kernel_fd_set;

/*
 * Scaleable version of the fd_set.
 */
typedef struct {
    unsigned long *in, *out, *ex;
    unsigned long *res_in, *res_out, *res_ex;
} fd_set_bits;

/*
 * How many longwords for "nr" bits?
 */
/* 一个long包含的bit数 */
#define FDS_BITPERLONG  (8*sizeof(long))
/* nr个bit需要多少long才能装下 */
#define FDS_LONGS(nr)   (((nr)+FDS_BITPERLONG-1)/FDS_BITPERLONG)
/* nr个bit需要多大的long数组才能装下 */
#define FDS_BYTES(nr)   (FDS_LONGS(nr)*sizeof(long))
代码语言:javascript
复制
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;

    /* 
     * 1. 永久等待      tvp == NULL
     * 2. 根本不等待    tvp->tv_sec == 0 && tvp->tc_nsec == 0
     * 3. 等待指定时间  tvp->tv_sec != 0 || tvp->tc_nsec != 0
     */
    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;
    }

    /*
     * 处理完超时时间之后:
     * 1. 永久等待      to == NULL
     * 2. 根本不等待    to->ts_sec == 0 && to->ts_nsec == 0
     * 3. 等待指定时间  to指向的timespec结构表示绝对超时时间
     */
    /* 主线(至此,我们只进行了超时时间处理) */
    ret = core_sys_select(n, inp, outp, exp, to);
    /* 将剩余超时时间通过tvp指向的timeval结构返回给用户空间 */
    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;
    /* Allocate small arguments on the stack to save memory and be faster */
    /*
     * 我们需要将用户空间传进来的inset、outset、exset拷贝到内核空间,并且
     * 需要等容量的空间来存储结果集,之后会将结果集的内容写回到用户空间。
     * 我们先在栈上分配一块缓冲区,用于缓存输入集以及结果集,如果缓存的
     * 空间大小不够,那么再使用kmalloc()动态分配,优先使用栈缓存而不用动态
     * 内存可以加快访问...
     */
    long stack_fds[SELECT_STACK_ALLOC/sizeof(long)];

    ret = -EINVAL;
    if (n < 0)
        goto out_nofds;

    /* max_fds can increase, so grab it once to avoid race */
    /* rdlock加锁:保护struct files的访问 */
    rcu_read_lock();
    fdt = files_fdtable(current->files);
    max_fds = fdt->max_fds;
    rcu_read_unlock();
    /*
     * 根据当前文件描述符表能表示的最大文件描述符对输入参数n进行修正
     *
     * fdtable.maxfd的意义:
     * 在expand_fdtable() -> alloc_fdtable()中有以下代码:
     *  fdt = kmalloc(sizeof(struct fdtable), GFP_KERNEL);
     *  fdt->max_fds = nr;
     *  data = alloc_fdmem(nr * sizeof(struct file *));
     *  fdt->fd = (struct file **)data;
     */
    if (n > max_fds)
        n = max_fds;

    /*
     * We need 6 bitmaps (in/out/ex for both incoming and outgoing),
     * since we used fdset we need to allocate memory in units of
     * long-words. 
     */
    /* 
     * n个bits至少需要size个long才能装下(之后我们使用long表示bits段)
     * 为了存储输入集与结果集,我们需要6*size个long的存储空间
     * 如果我们在栈上分配的那个缓冲区够用,那么就用它;而如果空间
     * 容纳不下的话,那么我们只好kmalloc()动态分配内存了...
     */
    size = FDS_BYTES(n);
    bits = stack_fds;
    if (size > sizeof(stack_fds) / 6) {
        /* Not enough space in on-stack array; must use kmalloc */
        ret = -ENOMEM;
        bits = kmalloc(6 * size, GFP_KERNEL);
        if (!bits)
            goto out_nofds;
    }
    /* 
     * 将数组划分为6份...
     * inset、outset、exset
     * res_inset、res_outset、res_exset
     */
    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;
    /* 将结果集清0 */
    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) {
        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;
}

/**
 * do_select - select()核心函数
 * @n: select()的第一个参数
 * @fd_set_bits: core_sys_select()处理的描述符集
 * @end_time: 绝对超时时间
 */
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;
    /* timed_out指示是否已经超时,超时1,未超时0 */
    int retval, i, timed_out = 0;
    unsigned long slack = 0;

    /* 根据输入集以及当前文件描述符打开情况对n修正 */
    rcu_read_lock();
    retval = max_select_fd(n, fds);
    rcu_read_unlock();

    if (retval < 0)
        return retval;
    n = retval;

    /* ------------------------------------------------------------- */
    /* ---------------------------- main --------------------------- */
    /* ------------------------------------------------------------- */
    /* 
     * 注意:
     * poll_table被封装在了poll_wqueues结构体中,以便之后向资源
     * 注册监听的时候,能够用poll_table得到对应的poll_wqueues
     *
     * 初始化poll_wqueues
     * 1. 初始化poll_wqueues中的poll_table:
     *      * 设置监听注册函数为__pollwait
     *      * 设置想要监听的事件为所有事件(没必要,之后会修改)
     * 2. 设置polling_task指向当前进程PCB
     *
     * 重点:资源注册函数为__pollwait
     */
    poll_initwait(&table);
    wait = &table.pt;
    /* 根本不等待 */
    if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
        /* 注意:wait设为NULL了!!! */
        wait = NULL;
        /* 还没开始就已经超时,这样就实现了根本不等待... */
        timed_out = 1;
    }

    /* 重新估算相对超时时间... */
    if (end_time && !timed_out)
        slack = 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遍历[0, n)范围内的文件描述符 */
        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;

            in = *inp++; out = *outp++; ex = *exp++;
            all_bits = in | out | ex;
            /* 跳过我们不关心的bits段 */
            if (all_bits == 0) {
                i += __NFDBITS;
                continue;
            }

            /* 遍历bits段中的每一个bit */
            for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {
                int fput_needed;
                /* 超出了关心的文件描述符范围[0, n),那么跳出... */
                if (i >= n)
                    break;
                
                /* 跳过我们不关心的bit */
                if (!(bit & all_bits))
                    continue;
                /* 通过current->files->fdt->fd[i]获得当前进程中描述符i
                 * 对应文件的struct file...同时,增加struct file的引用
                 * 计数,防止在获取struct file之后,它被异步删除... */
                file = fget_light(i, &fput_needed);
                /* 因为没有rdlock加锁,因此当前进程中描述符i对应的文件可能已经
                 * 被异步关闭。这就是为什么需要判断file是否为空的原因...
                 */
                if (file) {
                    f_op = file->f_op;
                    /* 注意:
                     * mask = POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM; */
                    mask = DEFAULT_POLLMASK;
                    /*
                     * 如果这个文件支持poll(),那么我们就向这个文件
                     * 注册监听函数;如果不支持,那么我们就忽略掉这
                     * 个文件描述符...
                     *
                     * 还有三种特殊情况:
                     *  1. 我们将超时时间设置为根本不等待
                     *  2. 已经发生了就绪事件
                     *  3. 已经注册了监听函数
                     *  这三种情况下,wait都为NULL,此时poll()只是
                     *  简单地返回资源文件当前状态...
                     */
                    if (f_op && f_op->poll) {
                        /* 设置poll_table中想要监听的事件 */
                        wait_key_set(wait, in, out, bit);
                        /* 对文件注册监听函数,并返回资源的当前状态 */
                        mask = (*f_op->poll)(file, wait);
                    }
                    fput_light(file, fput_needed);
                    /*
                     * 如果文件不支持poll()的话,那么mask总为DEFAULT_POLLMASK,
                     * 表示当前资源对于任何事件都就绪...
                     * 对于不支持poll()的文件,主要是ext2/ext3/ext4等块设备上
                     * 的文件,这些文件总是对任意事件就绪的,读写操作不会引起
                     * 阻塞,有数据就返回数据,没有数据就返回0表示end-of-file
                     */
                    /* events验证,其中retval表示就绪的资源数 */
                    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;
                    }
                }
            } // end of for (j = 0; j < __NFDBITS; ++j)

            /* 写出结果,注意写出的目的地是传进来的fd_set_bits */
            if (res_in)
                *rinp = res_in;
            if (res_out)
                *routp = res_out;
            if (res_ex)
                *rexp = res_ex;
            /* 提供一个调度时机,给资源就绪更多的准备时间... */
            cond_resched();
        } // end of for (i = 0; i < n; )
        /* 将wait设为NULL,表示我们不希望再进行监听注册... */
        wait = NULL;
        /* 
         * 1. 有事件发生了(retval)
         * 2. 超时了(timed_out)
         * 3. 发生了中断(signal_pending(current))
         * 任意事件发生了都跳出死循环...
         */
        /*
         * 感觉这里存在问题:
         * e.g.
         * 假设我们监听文件描述符集#1#2#3#4,poll(#1)与poll(#2)时
         * 向资源文件注册监听函数并返回0 events,poll(#3)时向资源
         * 文件注册监听函数并返回可读events,然后wait被置NULL,
         * poll(#4)时由于wait为NULL,所以只是返回#4的当前文件状态。
         * 遍历描述符集完毕,retval != 0,此时跳出循环,返回用户
         * 空间#3与#4的文件状态。但是在处理后续文件描述符的时候,
         * #1和#2可能异步就绪,但是我们没有返回它们...
         */
        if (retval || timed_out || signal_pending(current))
            break;
        /* 发生了错误,我们也跳出死循环... */
        if (table.error) {
            retval = table.error;
            break;
        }

        /*
         * If this is the first loop and we have a timeout
         * given, then we convert to ktime_t and set the to
         * pointer to the expiry value.
         */
        if (end_time && !to) {
            expire = timespec_to_ktime(*end_time);
            to = &expire;
        }

        /* 能够到达这一步就说明没有发生就绪、中断以及超时 */
        /* 
         * 判断poll_wqueues是否已触发,如果还没有触发,那就设置
         * 当前运行状态为可中断阻塞并进行睡眠,等待被唤醒...
         * 被唤醒之后重新进行迭代,获取资源就绪情况...
         * 在向资源注册监听与判断poll_wqueues是否已触发这段时间
         * 内,可能资源异步就绪了,如果没有触发标志,那么可能就
         * 会丢失资源就绪这个事件,可能导致select()永久沉睡...
         * 这就是为什么需要poll_wqueues.triggered字段的原因...
         */
        if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,
                       to, slack))
            timed_out = 1;
    } // end of for ( ; ; )

    /* 
     * 1. 卸载安装到资源监听队列上的poll_table_entry
     * 2. 释放poll_wqueues占用的资源
     */
    poll_freewait(&table);

    return retval;
}

#define FDS_IN(fds, n)      (fds->in + n)
#define FDS_OUT(fds, n)     (fds->out + n)
#define FDS_EX(fds, n)      (fds->ex + n)

#define BITS(fds, n)    (*FDS_IN(fds, n)|*FDS_OUT(fds, n)|*FDS_EX(fds, n))

static int max_select_fd(unsigned long n, fd_set_bits *fds)
{
    unsigned long *open_fds;
    unsigned long set;
    int max;
    struct fdtable *fdt;

    /* handle last in-complete long-word first */
    /* 
     * set = ~(1111....11111111 << (n % __NFDBITS))
     * e.g.
     * n = 16, set = (unsigned long)0000....00000000B
     * n = 18, set = (unsigned long)0000....00000011B
     * n = 23, set = (unsigned long)0000....01111111B
     */
    set = ~(~0UL << (n & (__NFDBITS-1)));
    n /= __NFDBITS;
    fdt = files_fdtable(current->files);
    open_fds = fdt->open_fds->fds_bits+n;
    max = 0;

    /* 处理最后一个不完整的bits段 */
    if (set) {
        set &= BITS(fds, n);
        if (set) {
            /* 有效性验证:
             * 输入集中不能含有未打开的文件描述符 */
            if (!(set & ~*open_fds))
                goto get_max;
            return -EBADF;
        }
    }
    /* 循环处理完整的bits段 */
    while (n) {
        open_fds--;
        n--;
        set = BITS(fds, n);
        if (!set)
            continue;
        /* 有效性验证:
         * 输入集中不能含有未打开的文件描述符 */
        if (set & ~*open_fds)
            return -EBADF;
        /* 判断max是否被设置而continue,而不是根据max是否被设置
         * 而break,是想对fd_set中设置的所有文件描述符都进行有效
         * 性验证 */
        if (max)
            continue;
get_max:
        /* max会是__NFDBITS的整数倍 */
        do {
            max++;
            set >>= 1;
        } while (set);
        max += n * __NFDBITS;
    }

    return max;
}

#define POLLIN_SET (POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLERR)
#define POLLOUT_SET (POLLWRBAND | POLLWRNORM | POLLOUT | POLLERR)
#define POLLEX_SET (POLLPRI)

static inline void wait_key_set(poll_table *wait, unsigned long in,
                unsigned long out, unsigned long bit)
{
    if (wait) {
        wait->key = POLLEX_SET;
        if (in & bit)
            wait->key |= POLLIN_SET;
        if (out & bit)
            wait->key |= POLLOUT_SET;
    }
}

poll

调用链:

代码语言:javascript
复制
sys_poll() -> do_sys_poll() -> poll_initwait()
                            -> do_poll() -> 1. do_pollfd() -> f_op->poll() [ -> poll_wait() -> __pollwait() -> poll_get_entry() ]
                                         -> 2. [ block -> goto 1 ]
                            -> poll_freewait() -> free_poll_entry()

pollwake() -> __pollwake()
代码语言:javascript
复制
struct pollfd {
    /* 想要监听的文件描述符 */
    int fd;
    /* 关心的events */
    short events;
    /* 就绪的events */
    short revents;
};

struct poll_list {
    struct poll_list *next;
    int len;
    struct pollfd entries[0];
};
代码语言:javascript
复制
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;
        poll_select_set_timeout(to, timeout_msecs / MSEC_PER_SEC,
            NSEC_PER_MSEC * (timeout_msecs % MSEC_PER_SEC));
    }

    /* 主线(至此,我们只进行了超时时间处理) */
    ret = do_sys_poll(ufds, nfds, to);

    /* ... */
    if (ret == -EINTR) {
        struct restart_block *restart_block;

        restart_block = &current_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;

        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;
    /* Allocate small arguments on the stack to save memory and be
       faster - use long to make sure the buffer is aligned properly
       on 64 bit archs to avoid unaligned access */
    /* poll_list缓存数组 */
    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;

    /* 将用户空间中的pollfd拷贝到内核空间 */
    len = min_t(unsigned int, nfds, N_STACK_PPS);
    for (;;) {
        walk->next = NULL;
        walk->len = len;
        if (!len)
            break;

        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_initwait(&table);
    /* 主线(至此,我们只进行了描述符集处理) */
    fdcount = do_poll(nfds, head, &table, end_time);
    poll_freewait(&table);

    /* 将结果写给用户空间 */
    for (walk = head; walk; walk = walk->next) {
        struct pollfd *fds = walk->entries;
        int j;

        for (j = 0; j < walk->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;

    /* Optimise the no-wait case */
    if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
        pt = NULL;
        timed_out = 1;
    }

    if (end_time && !timed_out)
        slack = estimate_accuracy(end_time);

    for (;;) {
        struct poll_list *walk;

        /* 遍历poll_list */
        for (walk = list; walk != NULL; walk = walk->next) {
            struct pollfd * pfd, * pfd_end;

            /* 遍历pollfd */
            pfd = walk->entries;
            pfd_end = pfd + walk->len;
            for (; pfd != pfd_end; pfd++) {
                /*
                 * Fish for events. If we found one, record it
                 * and kill the poll_table, so we don't
                 * needlessly register any other waiters after
                 * this. They'll get immediately deregistered
                 * when we break out and return.
                 */
                /* 
                 * 对每一个想要监听的pollfd调用do_pollfd()
                 * 返回非0表示监听的事件已发生...
                 */
                if (do_pollfd(pfd, pt)) {
                    count++;
                    pt = NULL;
                }
            }
        }
        /*
         * All waiters have already been registered, so don't provide
         * a poll_table to them on the next loop iteration.
         */
        pt = NULL;
        if (!count) {
            count = wait->error;
            if (signal_pending(current))
                count = -EINTR;
        }
        if (count || timed_out)
            break;

        /*
         * If this is the first loop and we have a timeout
         * given, then we convert to ktime_t and set the to
         * pointer to the expiry value.
         */
        if (end_time && !to) {
            expire = timespec_to_ktime(*end_time);
            to = &expire;
        }

        if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack))
            timed_out = 1;
    }
    return count;
}

/*
 * Fish for pollable events on the pollfd->fd file descriptor. We're only
 * interested in events matching the pollfd->events mask, and the result
 * matching that mask is both recorded in pollfd->revents and returned. The
 * pwait poll_table will be used by the fd-provided poll handler for waiting,
 * if non-NULL.
 */
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;

        file = fget_light(fd, &fput_needed);
        mask = POLLNVAL;
        if (file != NULL) {
            mask = DEFAULT_POLLMASK;
            if (file->f_op && file->f_op->poll) {
                if (pwait)
                    /* poll()总是设置events中的POLLERR与POLLHUP */
                    pwait->key = pollfd->events |
                            POLLERR | POLLHUP;

                mask = file->f_op->poll(file, pwait);
            }
            /* Mask out unneeded events. */
            mask &= pollfd->events | POLLERR | POLLHUP;
            fput_light(file, fput_needed);
        }
    }
    pollfd->revents = mask;

    return mask;
}

关于惊群:select()惊群与poll()惊群目前无法等到解决,因为它无法像accept()那样使得每次只有一个调用者能挂载到资源的监听队列上...虽然epoll()已经较好的解决了惊群,但只限于同一个epoll实例的ET模式。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-05-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Common
  • select
    • 调用链:
    • poll
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档