select 實現分析 –2 【整理】


select 實現分析 –2 【整理】

 

l  select相關的結構體

比較重要的結構體由四個:struct poll_wqueuesstruct poll_table_pagestruct poll_table_entrystruct poll_table_struct

 

每一個調用select()系統調用的應用進程都會存在一個struct poll_wqueues結構體,用來統一輔佐實現這個進程中所有待監測的fd的輪詢工作,后面所有的工作和都這個結構體有關,所以它非常重要。

 

struct poll_wqueues {

       poll_table pt;

       struct poll_table_page *table;

       struct task_struct *polling_task; //保存當前調用select的用戶進程struct task_struct結構體

       int triggered;            // 當前用戶進程被喚醒后置成1,以免該進程接着進睡眠

       int error;                 // 錯誤碼

       int inline_index;        // 數組inline_entries的引用下標

       struct poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES];

};

 

實際上結構體poll_wqueues內嵌的poll_table_entry數組inline_entries[] 的大小是有限的,如果空間不夠用,后續會動態申請物理內存頁以鏈表的形式掛載poll_wqueues.table上統一管理。接下來的兩個結構體就和這項內容密切相關:

 

struct poll_table_page { // 申請的物理頁都會將起始地址強制轉換成該結構體指針

       struct poll_table_page   *next;      // 指向下一個申請的物理頁

       struct poll_table_entry  *entry;     // 指向entries[]中首個待分配(空的) poll_table_entry地址

       struct poll_table_entry  entries[0]; // page頁后面剩余的空間都是待分配的poll_table_entry結構體

};

 

 

對每一個fd調用fop->poll() => poll_wait() => __pollwait()都會先從poll_wqueues.inline_entries[]中分配一個poll_table_entry結構體,直到該數組用完才會分配物理頁掛在鏈表指針poll_wqueues.table上然后才會分配一個poll_table_entry結構體poll_get_entry函數)。

poll_table_entry具體用處:函數__pollwait聲明如下:

static void __pollwait(struct file *filp, wait_queue_head_t *wait_address, poll_table *p);

 

該函數調用時需要3個參數,第一個是特定fd對應的file結構體指針,第二個就是特定fd對應的硬件驅動程序中的等待隊列頭指針,第3個是調用select()的應用進程中poll_wqueues結構體的poll_table該進程監測的所有fd調用fop->poll函數都用這一個poll_table結構體

 

struct poll_table_entry {

       struct file     *filp;                 // 指向特定fd對應的file結構體;

       unsigned long   key;                   // 等待特定fd對應硬件設備的事件掩碼,如POLLIN POLLOUTPOLLERR;

       wait_queue_t    wait;                  // 代表調用select()的應用進程,等待在fd對應設備的特定事件 (讀或者寫)的等待隊列頭上,的等待隊列項;

       wait_queue_head_t   *wait_address;     // 設備驅動程序中特定事件的等待隊列頭(該fd執行fop->poll,需要等待時在哪等,所以叫等待地址);

};

 

總結幾點:

1.  特定的硬件設備驅動程序的事件等待隊列頭是有限個數的,通常是有讀事件和寫事件的等待隊列頭;

2.  而一個調用了select()的應用進程只存在一個poll_wqueues結構體;

3.  該應用程序可以有多個fd在進行同時監測其各自的事件發生,但該應用進程中每一個fd有多少個poll_table_entry存在,那就取決於fd對應的驅動程序中有幾個事件等待隊列頭了,也就是說,通常驅動程序的poll函數中需要對每一個事件的等待隊列頭調用poll_wait()函數。比如,如果有讀寫兩個等待隊列頭,那么就在這個應用進程中存在兩個poll_table_entry結構體,在這兩個事件的等待隊列頭中分別將兩個等待隊列項加入;

4.  如果有多個應用進程使用select()方式同時在訪問同一個硬件設備,此時硬件驅動程序中加入等待隊列頭中的等待隊列項對每一個應用程序來說都是相同數量的一個事件等待隊列頭一個,數量取決於事件等待隊列頭的個數

 

 

do_select函數中,遍歷所有nfd,對每一個fd調用對應驅動程序中的poll函數。

驅動程序中poll一般具有如下形式:

static unsigned int XXX_poll(struct file *filp, poll_table *wait)

{

   unsigned int mask = 0;

   struct XXX_dev *dev = filp->private_data;

   ...

   poll_wait(filp, &dev->r_wait, wait);

   poll_wait(filp ,&dev->w_wait, wait);

   if(CAN_READ)//讀就緒

   {

      mask |= POLLIN | POLLRDNORM;

   }

   if(CAN_WRITE)//寫就緒

   {

      mask |= POLLOUT | POLLRDNORM;

   }

   ...

   return mask;

}

 

以字符設備evdev為例(文件drivers/input/evdev.c

static unsigned int evdev_poll(struct file *file, poll_table *wait)

{

       struct evdev_client *client = file->private_data;

       struct evdev *evdev = client->evdev;

       poll_wait(file, &evdev->wait, wait);

       return ((client->head == client->tail) ? 0 : (POLLIN | POLLRDNORM)) | (evdev->exist ? 0 : (POLLHUP | POLLERR));

}

 

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);

}

 

其中wait_address是驅動程序需要提供的等待隊列頭,來容納后續等待該硬件設備就緒的進程對應的等待隊列項。

 

typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);

 

 

typedef struct poll_table_struct {

       poll_queue_proc qproc;

       unsigned long key;

} poll_table;

 

fop->poll()函數的poll_table參數是從哪里傳進來的?好生閱讀過代碼就可以發現,do_select()函數中存在一個結構體struct poll_wqueues,其內嵌了一個poll_table的結構體,所以在后面的大循環中依次調用各個fdfop->poll()傳遞的poll_table參數都是poll_wqueues.poll_table

poll_table結構體的定義其實蠻簡單,就一個函數指針,一個key值。這個函數指針在整個select過程中一直不變,而key則會根據不同的fd的監測要求而變化。

qproc函數初始化在函數do_select() –> poll_initwait() -> init_poll_funcptr(&pwq->pt, __pollwait)中實現,回調函數就是__pollwait()

 

int do_select(int n, fd_set_bits *fds, struct timespec *end_time)

{

       struct poll_wqueues table;

       ...

       poll_initwait(&table);

       ...

}

 

 

void poll_initwait(struct poll_wqueues *pwq)

{

       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; /* all events enabled */

}

 

/* Add a new entry */

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);

entry->filp = filp;      // 保存對應的file結構體

entry->wait_address = wait_address;  // 保存來自設備驅動程序的等待隊列頭

entry->key = p->key;  // 保存對該fd關心的事件掩碼

init_waitqueue_func_entry(&entry->wait, pollwake);// 初始化等待隊列項,pollwake是喚醒該等待隊列項時候調用的函數

entry->wait.private = pwq; // poll_wqueues作為該等待隊列項的私有數據,后面使用

add_wait_queue(wait_address, &entry->wait);// 將該等待隊列項添加到從驅動程序中傳遞過來的等待隊列頭中去。

}

 

驅動程序在得知設備有IO事件時(通常是該設備上IO事件中斷),會調用wakeupwakeup –> __wake_up_common -> curr->func(pollwake)

static int pollwake(wait_queue_t *wait, unsigned mode, int sync, void *key)

{

       struct poll_table_entry *entry;

       entry = container_of(wait, struct poll_table_entry, wait);// 取得poll_table_entry結構體指針

       if (key && !((unsigned long)key & entry->key))/*這里的條件判斷至關重要,避免應用進程被誤喚醒,什么意思?*/

              return 0;

       return __pollwake(wait, mode, sync, key);

}

 

pollwake調用__pollwake,最終調用default_wake_function

 

static int __pollwake(wait_queue_t *wait, unsigned mode, int sync, void *key)

{

struct poll_wqueues *pwq = wait->private;

DECLARE_WAITQUEUE(dummy_wait, pwq->polling_task);

smp_wmb();

pwq->triggered = 1; // select()用戶進程只要有被喚醒過,就不可能再次進入睡眠,因為這個標志在睡眠的時候有用

return default_wake_function(&dummy_wait, mode, sync, key); // 默認通用的喚醒函數

}

 

最終喚醒調用select的進程,在do_select函數的schedule_timeout函數之后繼續執行(繼續for(;;),也即從新檢查每一個fd是否有事件發生),此次檢查會發現設備的該IO事件,於是select返回用戶層。

 

結合這兩節的內容,select的實現結構圖如下:

 

 

 

參考:

http://blog.csdn.net/lizhiguo0532/article/details/6568969

http://blog.csdn.net/lizhiguo0532/article/details/6568968

推薦閱讀該博客上的博文。

 


免責聲明!

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



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