美文网首页
select,poll源码分析

select,poll源码分析

作者: 柯基是只dog | 来源:发表于2019-01-10 18:10 被阅读0次

1.0版本的select已经看过了,2.6中已经完全重构了,代码每次看都好像懂了,但每次回忆核心流程又感觉有点勉强,我希望通过一种关键流程的形式去分析,而不是贴一大堆的代码

  1. 关键结构对象poll_wqueues,在每次do_select中会被new一个出来并初始化,初始化的关键是把__pollwait方法指针设置到pt中,后面会分析到poll_wait方法的时候,实际上去执行的就是这儿设置的__pollwait
/*
 * Structures and helpers for select/poll syscall
 */
struct poll_wqueues {
    poll_table pt;
    struct poll_table_page *table;  // 当inline_entries数组使用完了之后,会动态申请的页
    struct task_struct *polling_task;  // 当前等待的进程
    int triggered;  // 当被唤醒后该值被设置,以防重复唤醒
    int error;
    int inline_index;  // 当前inline_entries使用到的位置
    struct poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES];
};

// do_select中对poll_wqueues进行初始化
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;
}
  1. *f_op->poll,linux使用了类似接口的模块开发,file对象只是一个接口层,每个file对象都有一个f_op对象,是实际上真正驱动程序的代码。所以,*f_op->poll就是调用不同的硬件驱动的poll方法。
    一般对应的驱动中都会有一个自己的等待队列而且是统一的,linux给等待队列定义了两个对象wait_queue_head_twait_queue_t,wait_queue_head_t是一个包含了自旋锁的双向链表,链表当然也是使用了linux中的list_head了,注意了,该链表除了表头也就是wait_queue_head_t,其他项都是wait_queue_t结构了,而wait_queue_t则是存放等待进程的真正地方。
// 真正存放等待队列中等待进程的地方
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;
};

struct __wait_queue_head {
    spinlock_t lock;
    struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
  1. 那回到刚才说的__pollwait方法,因为最终设备的驱动都会调用poll_wait方法来触发,该方法比较重要,值得多贴一些代码来解释,该方法第一个参数是监听fd对应的file,第二个参数是驱动设备的等待队列头,第三个参数是咱们第一条说的注入进去的poll_table,所有fd都共用这一个poll_table。
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
    if (p && wait_address)
        p->qproc(filp, wait_address, p);
}

/* Add a new entry */
static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,
                poll_table *p)
{
    // 根据poll_table的指针取得外层对象poll_wqueues的指针
    struct poll_wqueues *pwq = container_of(p, struct poll_wqueues, pt);
    // 先从数组中inline_entries中取空闲的,如果用完了则申请新页存放
    struct poll_table_entry *entry = poll_get_entry(pwq);
    if (!entry)
        return;
    get_file(filp);
    entry->filp = filp;
    entry->wait_address = wait_address;
    // 设置监听的类型=poll和读写
    entry->key = p->key;
    // 对wait_queue链表初始化,flags=0,唤醒func=pollwake
    init_waitqueue_func_entry(&entry->wait, pollwake);
    entry->wait.private = pwq;
    // 加入到该设备的等待队列中
    add_wait_queue(wait_address, &entry->wait);
}

这里还有个细节,一旦*f_op->poll返回的mask不为0并且是监听的类型,则会把wait也就是那个wait_table对象设置为null,这样对下一个file调用poll方法的时候就不会把自己加入到等待队列中了。

  1. 退出,当有任何一个事件可用的时候直接返回,当poll_table发生错误的时候也退出。剩下就根据超时去睡眠,方法第一次执行的时候会根据end_time设置超时expire。然后会调用poll_schedule_timeout去睡眠,如果该方法返回0代表是超时事件唤醒的则设置timeout,注意不论是超时唤醒还是被某个驱动程序的事件唤醒后,都会再走一次循环,再检查一遍每一个事件的状态。
// 第一次完毕后如果任何一个事件都没有发生,也会置wait为NULL,防止下一次循环再一次设置等待队列
wait = NULL;
// 如果有事件发生,或者timeout都会退出循环
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;
}

// 开始睡眠
if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,
                to, slack))
    timed_out = 1;
  1. 扫尾,从每个等待队列中删除该进程的等待项,方法是poll_freewait,就是遍历inline_entries数组,取出poll_table_entry然后挨个调用free_poll_entry方法从队列中删除。然后检查是否使用了申请的内存,有的话也要一一退出
void poll_freewait(struct poll_wqueues *pwq)
{
    struct poll_table_page * p = pwq->table;
    int i;
    // 从数组中取出poll_table_entry然后挨个调用free_poll_entry从队列释放
    for (i = 0; i < pwq->inline_index; i++)
        free_poll_entry(pwq->inline_entries + i);
    
    // 如果使用了申请的内存,则也要一一释放
    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);
    }
}
  1. 唤醒,驱动程序可以写的时候会唤醒等待队列上的进程,调用的是wake_up_interruptible

相关文章

网友评论

      本文标题:select,poll源码分析

      本文链接:https://www.haomeiwen.com/subject/rxmarqtx.html