*在高併發編程中,多次使用IO複用select函數,本篇就來深入剖析一下其內核源碼。。。→_→*
瞭解poll機制底層原理請戳傳送門——IO複用——poll機制內核源代碼分析
瞭解select應用實例請戳傳送門——IO複用——select函數應用實例
< – 2017-08-11 23:40 – >
還有一部分代碼沒貼。。。明天把select這個硬骨頭啃下來。。。
< – 2017-08-16 13:32 – >
宿舍沒網。。我的流量啊。。因爲源代碼大部分都在Select.c中。。。所以會有點多。。。看官請直接進sys_select。。。
//linux-2.4.0\fs\Select.c
//提供了6個宏函數,返回要求位圖或結果位圖中對應的下標元素的值
#define __IN(fds, n) (fds->in + n)
#define __OUT(fds, n) (fds->out + n)
#define __EX(fds, n) (fds->ex + n)
#define __RES_IN(fds, n) (fds->res_in + n)
#define __RES_OUT(fds, n) (fds->res_out + n)
#define __RES_EX(fds, n) (fds->res_ex + n)
//這個元素下標可以同時對應三種位圖,所以在一個位圖中監聽存在就行
#define BITS(fds, n) (*__IN(fds, n)|*__OUT(fds, n)|*__EX(fds, n))
static int max_select_fd(unsigned long n, fd_set_bits *fds)
{
unsigned long *open_fds;
unsigned long set;
int max;
/* handle last in-complete long-word first */
set = ~(~0UL << (n & (__NFDBITS-1)));
n /= __NFDBITS; //將所監聽的文件描述符的個數轉化爲位圖的元素下標
open_fds = current->files->open_fds->fds_bits+n;
max = 0; //記錄最大的序號
if (set) {
set &= BITS(fds, n);
if (set) {
if (!(set & ~*open_fds))
goto get_max;
return -EBADF;
}
}
while (n) {
open_fds--;
n--;
set = BITS(fds, n); //判斷在位圖下標位n的元素中是否有要監聽的文件描述符
if (!set) //如果位圖中下標爲n的元素中沒有要監聽的文件描述符,就繼續尋找
continue;
if (set & ~*open_fds)
return -EBADF;
if (max) //這裏的最大序號可以理解爲一個標誌位
continue;
get_max:
do {
max++;
set >>= 1;
} while (set);
max += n * __NFDBITS; //再將位圖中的元素序號轉化爲文件描述符對應的個數
}
return max;
}
#define BIT(i) (1UL << ((i)&(__NFDBITS-1)))
#define MEM(i,m) ((m)+(unsigned)(i)/__NFDBITS)
#define ISSET(i,m) (((i)&*(m)) != 0)
#define SET(i,m) (*(m) |= (i))
#define POLLIN_SET (POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLERR)
#define POLLOUT_SET (POLLWRBAND | POLLWRNORM | POLLOUT | POLLERR)
#define POLLEX_SET (POLLPRI)
int do_select(int n, fd_set_bits *fds, long *timeout)
{
//poll_table類型結構在下面有解釋說明
poll_table table, *wait;
int retval, i, off;
long __timeout = *timeout;
read_lock(¤t->files->file_lock);
//計算所監聽的文件描述符在位圖中的最大的序號是多少,高於這個序號的文件描述符都與本次操作無關
//max_select_fd定義在上面
retval = max_select_fd(n, fds);
read_unlock(¤t->files->file_lock);
if (retval < 0)
return retval;
n = retval;
//當一個進程要進入睡眠,而想要某個設備的驅動程序在設備的狀態發生變化時將其喚醒,就要準備一個wait_queue_t數據結構,並將這個數據結構掛入目標設備的某個等待隊列中。而wait_queue_t就封裝在poll_table類型結構中
//初始化poll_table類型結構變量,將table成員置爲NULL,error成員置爲0
poll_initwait(&table);
wait = &table;
if (!__timeout)
wait = NULL;
retval = 0;
//進入for循環,直到所監聽的事件就緒,或指定的睡眠等待時間到期,或者當前進程收到了信號時纔會結束
for (;;) {
set_current_state(TASK_INTERRUPTIBLE); //將當前進程的狀態置爲可中斷阻塞,即當前進程將會進入淺睡眠狀態
//內層for循環中,對所要監視的文件描述符對應的位圖進行一次掃描
for (i = 0 ; i < n; i++) {
unsigned long bit = BIT(i);
unsigned long mask;
struct file *file;
off = i / __NFDBITS; //將文件描述符的個數i轉化爲位圖中所對應的元素下標off
if (!(bit & BITS(fds, off))) //如果三種位圖的某一位爲1,就對相應的文件描述符做一次詢問
continue;
file = fget(i);
mask = POLLNVAL;
//文件的具體詢問方式和其類型有關,即是通過file_operations數據結構中的函數指針poll進行的。關於poll操作將會在另一篇文章中總結。。
if (file) {
mask = DEFAULT_POLLMASK;
if (file->f_op && file->f_op->poll)
mask = file->f_op->poll(file, wait);
fput(file);
}
//retval記錄一共有幾個事件就緒
//將詢問的輸入結果彙集到fds所指的fd_set_bits變量中
if ((mask & POLLIN_SET) && ISSET(bit, __IN(fds,off))) {
SET(bit, __RES_IN(fds,off));
retval++; //記錄一共有多少個事件就緒
wait = NULL;
}
//將詢問的輸出結果彙集到fds所指的fd_set_bits變量中
if ((mask & POLLOUT_SET) && ISSET(bit, __OUT(fds,off))) {
SET(bit, __RES_OUT(fds,off));
retval++; //記錄一共有多少個事件就緒
wait = NULL;
}
//將詢問的異常結果彙集到fds所指的fd_set_bits變量中
if ((mask & POLLEX_SET) && ISSET(bit, __EX(fds,off))) {
SET(bit, __RES_EX(fds,off));
retval++; //記錄一共有多少個事件就緒
wait = NULL;
}
}
wait = NULL;
//對所有的文件描述符進行詢問後,檢查是否有事件就緒、睡眠等待時間超時、接收到了信號,如果有條件滿足,就不會再進入睡眠狀態,直接結束大循環
//統計好就緒事件後,此時retval不爲0,從break跳出,結束大循環
if (retval || !__timeout || signal_pending(current))
break;
//檢查是否出錯,如果出錯,也不會進入睡眠狀態,直接結束大循環
if(table.error) {
retval = table.error;
break;
}
//進入睡眠狀態,被喚醒後再進行一次掃描詢問
//除第一次以外,以後都是在進程被喚醒時才執行一遍循環,從本質上講是一種do-while循環
__timeout = schedule_timeout(__timeout);
}
current->state = TASK_RUNNING; //設置當前進程的狀態爲運行態
//將所有進程對應的wait_queue_t結構從各個等待隊列中刪除
//poll_freewait定義在下面
poll_freewait(&table);
/*
* Up-to-date the caller timeout.
*/
*timeout = __timeout;
return retval; //返回就緒事件的總數
}
static void *select_bits_alloc(int size)
{
return kmalloc(6 * size, GFP_KERNEL); //通過kmalloc分配6個位圖
}
static void select_bits_free(void *bits, int size)
{
kfree(bits);
}
/*
* We can actually return ERESTARTSYS instead of EINTR, but I'd
* like to be certain this leads to no problems. So I return
* EINTR just for safety.
*
* Update: ERESTARTSYS breaks at least the xview clock binary, so
* I'm trying ERESTARTNOHAND which restart only when you want to.
*/
#define MAX_SELECT_SECONDS \
((unsigned long) (MAX_SCHEDULE_TIMEOUT / HZ)-1)
//n表示調用時的參數表中一共有多少個位圖,即需要監聽的文件描述符最大值,一般爲最大值加1
//fd_set類型結構在下面有解釋說明
//fd_set表示已打開文件的位圖,位圖的每一位都代表着當前進程的一個已打開文件,根據其結構定義得知,select最多可以監聽1024個文件描述符
//timeval類型結構在下面有解釋說明
//tvp表示睡眠等待的最長時間,如果爲0則表示立即返回,如果爲NULL則表示阻塞等待,直到所監聽的事件就緒
asmlinkage long
sys_select(int n, fd_set *inp, fd_set *outp, fd_set *exp, struct timeval *tvp)
{
//fd_set_bits類型結構在下面有解釋說明
//其中分別保存了3種位圖的要求和結果
fd_set_bits fds;
char *bits;
long timeout;
int ret, size;
timeout = MAX_SCHEDULE_TIMEOUT;
if (tvp) {
time_t sec, usec;
//將所需數據數據從用戶空間拷貝到內核空間
if ((ret = verify_area(VERIFY_READ, tvp, sizeof(*tvp)))
|| (ret = __get_user(sec, &tvp->tv_sec))
|| (ret = __get_user(usec, &tvp->tv_usec)))
goto out_nofds;
ret = -EINVAL;
if (sec < 0 || usec < 0)
goto out_nofds;
if ((unsigned long) sec < MAX_SELECT_SECONDS) {
timeout = ROUND_UP(usec, 1000000/HZ);
timeout += sec * (unsigned long) HZ;
}
}
ret = -EINVAL;
if (n < 0)
goto out_nofds;
//判斷文件描述符數量有沒有超過最大值
if (n > current->files->max_fdset)
n = current->files->max_fdset;
/*
* 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.
*/
ret = -ENOMEM;
size = FDS_BYTES(n);
//select_bits_alloc定義在上面
//一共分配6個位圖
bits = select_bits_alloc(size);
//爲要求和結果,一共6個位圖初始化
if (!bits)
goto out_nofds;
fds.in = (unsigned long *) bits;
fds.out = (unsigned long *) (bits + size);
fds.ex = (unsigned long *) (bits + 2*size);
fds.res_in = (unsigned long *) (bits + 3*size);
fds.res_out = (unsigned long *) (bits + 4*size);
fds.res_ex = (unsigned long *) (bits + 5*size);
//將3個要求位圖從用戶空間複製到內核空間中的fds的要求位圖
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;
//將內核空間的fds的結果位圖初始化爲0
zero_fd_set(n, fds.res_in);
zero_fd_set(n, fds.res_out);
zero_fd_set(n, fds.res_ex);
//好戲開始。。。do_select定義在上面。。。→_→
// ret記錄就緒事件的總數
ret = do_select(n, &fds, &timeout);
if (tvp && !(current->personality & STICKY_TIMEOUTS)) {
time_t sec = 0, usec = 0;
if (timeout) {
sec = timeout / HZ;
usec = timeout % HZ;
usec *= (1000000/HZ);
}
put_user(sec, &tvp->tv_sec);
put_user(usec, &tvp->tv_usec);
}
if (ret < 0)
goto out;
if (!ret) {
ret = -ERESTARTNOHAND;
if (signal_pending(current))
goto out;
ret = 0;
}
//將3個結果位圖的內容複製到用戶空間中
set_fd_set(n, inp, fds.res_in);
set_fd_set(n, outp, fds.res_out);
set_fd_set(n, exp, fds.res_ex);
out:
select_bits_free(bits, size); //釋放要求和結果位圖的6個位圖的空間
out_nofds:
return ret; //返回就緒事件的總數
}
// linux-2.4.0\include\linux\\Poll.h
//記錄要求的3個位圖和結果的3個位圖
typedef struct {
unsigned long *in, *out, *ex; //要求
unsigned long *res_in, *res_out, *res_ex; //結果
} fd_set_bits;
//linux-2.4.0\include\linux\Time.h
struct timeval {
time_t tv_sec; /* 秒數 seconds */
suseconds_t tv_usec; /* 微秒數 microseconds */
};
//fd_set類型的定義
//linux-2.4.0\include\linux\Posix_types.h
#undef __NFDBITS
//__NFDBITS的值爲32
#define __NFDBITS (8 * sizeof(unsigned long))
#undef __FD_SETSIZE
#define __FD_SETSIZE 1024
#undef __FDSET_LONGS
//__FDSET_LONGS的值爲32
#define __FDSET_LONGS (__FD_SETSIZE/__NFDBITS)
#undef __FDELT
#define __FDELT(d) ((d) / __NFDBITS)
#undef __FDMASK
#define __FDMASK(d) (1UL << ((d) % __NFDBITS))
//因此fd_set實際上是一個具有32個元素的unsigned long類型的數組
typedef struct {
unsigned long fds_bits [__FDSET_LONGS];
} __kernel_fd_set;
typedef __kernel_fd_set fd_set;
//linux-2.4.0\include\linux\Poll.h
//poll_table_page類型結構在下面有解釋說明
typedef struct poll_table_struct {
int error;
struct poll_table_page * table;
} poll_table;
//linux-2.4.0\fs\Select.c
struct poll_table_entry {
struct file * filp;
wait_queue_t wait; //被封裝的wait_queue_t
wait_queue_head_t * wait_address; //等待隊列的隊頭
};
struct poll_table_page {
//一個頁面用完了就再分配一個,通過next鏈成一條單鏈
struct poll_table_page * next;
//poll_table_entry類型結構上面有解釋說明
//entry總是指向entries中第一個空閒的poll_table_entry結構,根據需要動態的分配entries中的表項
struct poll_table_entry * entry;
//表示該數組可以動態地確定大小,實際使用中分配一個頁面,頁面中能容納幾個poll_table_entry,這個數組就有多大
struct poll_table_entry entries[0];
};
//linux-2.4.0\fs\Select.c
void poll_freewait(poll_table* pt)
{
struct poll_table_page * p = pt->table; //p指向第一個poll_table_page結構,poll_table_page結構由next成員鏈成單鏈
//當p不爲NULL時,繼續循環
while (p) {
struct poll_table_entry * entry;
struct poll_table_page *old;
entry = p->entry; //entry指向entries中第一個空閒的poll_table_entry結構,entries是一個數組
do {
entry--; //entry前移
remove_wait_queue(entry->wait_address,&entry->wait); //將entry表示的wait_queue_t結構從等待隊列中刪除
fput(entry->filp);
} while (entry > p->entries); //判斷此entries數組中是否還有元素未刪除
old = p; //此時poll_table_page結構中的entries數組中已沒有元素,此時old記錄當前poll_table_page 結構
p = p->next; //p指向下一個poll_table_page 結構
free_page((unsigned long) old); //釋放old所指向的poll_table_page結構頁面
}
}
*哇。。。終於分析完了!!!一會兒直接上poll操作的源碼分析和select的應用實例。。。→_→*