linux poll機制分析(二)


一、回顧

linux poll機制使用(一)寫了個實現poll機制的簡單例子。在驅動模塊中需要實現struct file_operations.poll成員。在驅動模塊中xxx_poll函數的的作用是將當前進程添加到等待隊列中;然后判斷事件是否發生,發生則返回POLLIN | POLLRDNORM,否則返回0(可以看看上一章的例子);接下來分析一下 linux 內核中 poll 機制的實現。

二、poll機制分析

1、系統調用

當應用層調用poll函數時,linux發生系統調用(系統調用入口CALL(sys_poll)),程序從應用空間切換到內核空間,然后執行sys_poll函數(sys_poll函數在fs\select.c文件中)。sys_poll 函數的作用是 根據時間計算滴答頻率數,然后調用do_sys_poll函數。代碼塊如下:

/* ufds:應用層傳遞過來的struct pollfd結構體數組
 * nfds:應用層傳遞過來的struct pollfd結構體個數
 * timeout_msecs:超時時間*/
asmlinkage long sys_poll(struct pollfd __user *ufds, unsigned int nfds,long timeout_msecs)
{
    s64 timeout_jiffies;
    if (timeout_msecs > 0) {
        /* 大於0,根據時間計算滴答頻率數 */
        timeout_jiffies = msecs_to_jiffies(timeout_msecs);
    } else {
        /* Infinite (< 0) or no (0) timeout */
        timeout_jiffies = timeout_msecs;
    }
    /* 然后調用do_sys_poll函數*/
    return do_sys_poll(ufds, nfds, &timeout_jiffies);
}

2、do_sys_poll函數

do_sys_poll函數在fs\select.c文件中;do_sys_poll函數的主要作用初始化table(table->pt->qproc = __pollwait),然后初始化poll列表(poll列表的作用是存放用戶空間的struct pollfd)接着調用do_poll函數。下面的代碼塊省略了部分源代碼。代碼塊如下:

int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout)
{
    struct poll_wqueues table;
    struct poll_list *head;
    struct poll_list *walk;
    .....//省略了部分源代碼
    
    if (nfds > current->signal->rlim[RLIMIT_NOFILE].rlim_cur)
       return -EINVAL;


    /* 初始化 table */
    /* table->pt->qproc =  __pollwait
     * __pollwait 主要將當前進程掛到等待隊列中,這里只是做初始化而已。
     * 在驅動程序里調用的 poll_wait(file, &button_waitq, wait); 函數里面最終
     * 調用的就是table->pt->qproc()函數,也就是調用__pollwait()函數。
     */
    poll_initwait(&table);
    

    .....//省略部分源代碼(省略的源代碼其實就是分配空間和初始化head列表)
    


    /* do_poll函數的參數說明:
	 *
     * nfds: 應用層傳遞過來的struct pollfd結構體個數
     * head: poll列表頭,列表里面包含應用層傳進來的struct pollfd數組的信息
     * table: table->pt->qproc指向__pollwait函數(主要將當前進程掛到等待隊列中)
     * timeout: 等待超時
     */
    /* 調用do_poll函數 */
    fdcount = do_poll(nfds, head, &table, timeout);
    
    .....//省略部分源代碼(這部分的源代碼是將revents(返回的事件) copy 到應用層)
    return err;
}

3、do_poll函數

do_poll函數在fs\select.c文件中。do_poll 函數的作用主要是設置當前進程的任務狀態,然后遍歷poll列表、調用do_pollfd函數,然后根據實際情況是否將當前進程休眠或者喚醒當前進程,代碼塊如下:

//參數說明
/*nfds: 應用層傳遞過來的struct pollfd結構體個數
 *list:poll列表頭,列表里面包含應用層傳進來的struct pollfd數組的信息
 *wait:wait->pt->qproc指向__pollwait函數(主要將當前進程掛到等待隊列中)
 *timeout:等待超時時間
 */
static int do_poll(unsigned int nfds,  struct poll_list *list,
          struct poll_wqueues *wait, s64 *timeout)
{
    int count = 0;
    poll_table* pt = &wait->pt;
    
    /* Optimise the no-wait case */
    if (!(*timeout))
        pt = NULL;
     /* 執行poll的時候有一個大循環 */
    for (;;) {
        struct poll_list *walk;
        long __timeout;
        /* 設置當前的任務狀態為可中斷休眠 */
        set_current_state(TASK_INTERRUPTIBLE);
        /* 遍歷poll列表 */
        for (walk = list; walk != NULL; walk = walk->next) {
            struct pollfd * pfd, * pfd_end;
            /* 獲取頭部的地址 */
            pfd = walk->entries;
            /* 獲取尾部指針 */
            pfd_end = pfd + walk->len;
            /* 遍歷struct pollfd結構體數組 */
            for (; pfd != pfd_end; pfd++) {
              
                /*------------------------------------------------------------------*/
                 /* do_pollfd其實就是調用開發者所寫的xxx_poll函數了(里面調用的的是struct file_operations .poll函數)
                  * do_pollfd函數下面的代碼塊分析
                  */
				  
                 /* 執行完do_pollfd后,這個進程就被掛到等待隊列里面了。
                  * 這里僅僅是掛到等待隊列而已,進程的休眠是在下面
                  * 執行 schedule_timeout 函數的時候休眠的
                  */
                 
                if (do_pollfd(pfd, pt)) {
                    /* 如果執行驅動程序的poll返回的是非0值 則count++ */
                    /* 表名設備有數據要返回給應用程序 */
                    count++;
                    pt = NULL;
                }
	            /*------------------------------------------------------------------*/
            }
        }
        
        pt = NULL;
        /* 結束大循環的條件有三個:
         * count: 表示設備有數據返回給應用程序
         * timeout: 等待超時時間到
         * signal_pending(current): 當前進程接收到信號
         */
        if (count || !*timeout || signal_pending(current))
            break;
        /* 發生錯誤退出 */
        count = wait->error;
        if (count)
            break;
        if (*timeout < 0) {
            /* Wait indefinitely */
            __timeout = MAX_SCHEDULE_TIMEOUT;
        } else if (unlikely(*timeout >= (s64)MAX_SCHEDULE_TIMEOUT-1)) {
            /*
             * Wait for longer than MAX_SCHEDULE_TIMEOUT. Do it in
             * a loop
             */
            __timeout = MAX_SCHEDULE_TIMEOUT - 1;
            *timeout -= __timeout;
        } else {
            __timeout = *timeout;
            *timeout = 0;
        }
        /* 執行到這里之后 timeout 的值已經為零,下一次循環就會超時返回 */
        
        
        /* 這里讓當前進程休眠一會
         * 喚醒進程的處理休眠的時間到之外,還可以由驅動程序喚醒
         */
        __timeout = schedule_timeout(__timeout);
        if (*timeout >= 0)
            *timeout += __timeout;
    }
    /* 將當前進程設置為就緒狀態,等待CPU調度 */
    __set_current_state(TASK_RUNNING);
    return count;
}

4、do_pollfd

do_pollfd函數在fs\select.c文件中,函數的作用就是,根據文件描述符找到struct file結構體,然后調用驅動程序的poll函數

//參數說明
/* pollfd: struct pollfd結構體
 * pwait:pwait->qproc指向__pollwait函數(主要將當前進程掛到等待隊列中)
 */
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結構
         * file結構是每打開一個文件就會有一個file
         */
        file = fget_light(fd, &fput_needed);
        mask = POLLNVAL;
        if (file != NULL) {
            mask = DEFAULT_POLLMASK;
            if (file->f_op && file->f_op->poll)
                /* 調用驅動里面的poll函數,這個函數是驅動開發者添加的poll函數 */
                /* 如果驅動有數據讓應用程序讀的話,就返回非0, 否側返回0 */
                mask = file->f_op->poll(file, pwait);
            /* Mask out unneeded events. */
            /* 根據應用層傳遞的events,來屏蔽不需要的事件 */
            mask &= pollfd->events | POLLERR | POLLHUP;
            fput_light(file, fput_needed);
        }
    }
    pollfd->revents = mask;
    /* 設備有數據可讀則返回非0 */
    /* 否則返回0 */
    return mask;
}

三、總結

  1. poll > sys_poll > do_sys_poll > poll_initwait,poll_initwait函數注冊一下回調函數__pollwait,它就是我們的驅動程序執行poll_wait時,真正被調用的函數。
  2. 接下來執行file->f_op->poll,即我們驅動程序里自己實現的poll函數
    它會調用poll_wait把自己掛入某個隊列,這個隊列也是我們的驅動自己定義的;
    它還判斷一下設備是否就緒
  3. 如果設備未就緒,do_sys_poll里會讓進程休眠一定時間
  4. 進程被喚醒的條件有兩個 :1、“一定時間”到了 ;2、二是被驅動程序喚醒。驅動程序發現條件就緒時,就把“某個隊列”上掛着的進程喚醒,這個隊列,就是前面通過poll_wait把本進程掛過去的隊列。
  5. 如果驅動程序沒有去喚醒進程,那么chedule_timeout(__timeou)超時后,會重復2、3動作,直到應用程序的poll調用傳入的時間到達。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM