select源碼剖析

asmlinkage long
sys_select(int n, fd_set __user *inp, fd_set __user *outp, fd_set __user *exp, struct timeval __user *tvp)

select是個系統調用,他進入內核態之後就調用sys_select(),這個函數的功能有以下幾點:
1、參數檢查,對n進行判斷,n的最大值就是當前進程所能打開的最大文件數量,一般情況下爲1024
2、用一個結構體保存用戶傳進來的參數,fd_set_bits結構,定義如下:
typedef struct {
	unsigned long *in, *out, *ex;
	unsigned long *res_in, *res_out, *res_ex;
} fd_set_bits;

in,out,ex分別保存用戶註冊的感興趣事件,而res_in,res_out,res_ex,分別保存這個文件描述符上的用戶感興趣的事件的已發生事件,當返回時會把res_in ,res_out,res_ex中的值賦給in,out,ex,所以這裏就有從用戶空間到內核空間,內核空間到用戶空間的拷貝問題,而且每次調用select時都會發生大量的重複來回拷貝問題,造成效率上的問題

fd_set+bits組裝好之後(保存了用戶感興趣的事件)就要做具體的事情了,那就是監聽多個文件描述符,它把fd_set_bits當做參數調用do_select()函數做具體的文件描述符上的監聽工作

int do_select(int n, fd_set_bits *fds, long *timeout)

這個函數做了以下幾件事情:
1、每次取一個long字節長度的fd過來,然後分別把他們的讀寫異常事件做邏輯或運算,如果不爲零就說明有此long 字節的fd裏面是用戶感興趣的事件對應的文件描述符,於是就調用該文件的poll方法(每個文件的類型都對應有相應的文件操作,這個操作在file->f_op中的poll方法被賦予不同的值,例如管道文件的poll方法就被賦值爲pipe_poll()),如果通過poll方法獲取到的掩碼爲零就說明這個文件描述上沒有事件發生,直接執行下次的循環,如果發生了,但是還不知道是讀或者寫或者異常中的哪一些,於是就拿1每次左移動以爲和這個long長度的fd分別做邏輯與運算,如果不爲零就說明這個位對應的fd有感興趣的事件發生,然後就拿這個l分別去和用戶傳進來的in,out,ex做邏輯與運算就知道了,並且做相應的計數++,
2、如果第一次對這個文件描述符調用poll方法的話,調用他時傳遞的參數wait就不爲空,就需要將此進程掛入該文件的等待隊列中,在file中有該文件的等待隊列的頭,此時就會調用剛剛在第一步的過程中在poll之前他會先初始化一個poll_table 的機構體,這個結構體裏面就一個函數指針,在select,poll中被初始化爲_pollwait(),當需要將該進程加入該文件的等待隊列時就要調用這個函數。
poll_table的定義:
typedef struct poll_table_struct {
	poll_queue_proc qproc;
} poll_table;
3、如果掃描一遍之後沒有時間到達就調用schedule_timeout進入睡眠,當時間到達,有信號到達,有時間到達時纔會被喚醒,有事件達到時他會重新掃描一遍所有的fd這個時候
一定可以返回,掃描得到的事件記錄在res_in,res_out,res_ex中,出了大循環之後就將這裏的值分別拷貝到用戶空間的in,out,ex中,釋放相應的數據結構,返回。


void __pollwait(struct file *filp, wait_queue_head_t *wait_address, poll_table *_p)

這個函數首先通過p根據其在poll_wqueue中的偏移量找到poll_wqueue結構體,然後通過這個結構體中的poll_table_page分配一個poll_table_entry,而加入該文件的等待隊列就是通過這個結構體實現的(由於監聽個的fd可能會很多,不可能全部在堆棧上分配,因此這裏就採用動態分配的方式,每次分配一頁的大小,用單鏈表串起來,而一頁當中有很多的poll_table_entry項)
poll_table_entry定義如下:
struct poll_table_entry {
	struct file * filp;
	wait_queue_t wait;
	wait_queue_head_t * wait_address;
};
這個裏面它wait_address中保存該文件的等待隊列的頭,wait就是一個等待隊列的節點,包含進程的task_struct,喚醒的回調函數(這裏使用默認的回調函數),如果該文件的有事件到達就會調用該函數,喚醒正在等待的進程,重新掃描一遍fd,如果計數不爲零就從select中返回了,並且還會釋放這裏的poll_wqueue等數據結構,

這裏會用到一個數據結構poll_wqueue,定義如下
struct poll_wqueues {
	poll_table pt;
	struct poll_table_page * table;
	int error;
};

缺點

1、它支持的最大文件描述符的數量爲本進程支持的最大文件數量,當有幾萬個文件描述符時,他就不支持,即使通過內核微調調整這個值,效率也不高
2、即使有一個文件描述符上有事件到達,他也得返回,但是這個肯定要循環調用它監聽,所以每次調用都需要大量重複的用戶空間到內核空間,內核空間到用戶空間的拷貝,而且每次都是全部拷貝,效率低下
3、每次調用都要重複的開闢entry將其加入相應文件的等待隊列,這裏開銷太大


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章