歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> 關於Linux

Select函數實現原理分析

select需要驅動程序的支持,驅動程序實現fops內的poll函數。select通過每個設備文件對應的poll函數提供的信息判斷當前是否有資源可用(如可讀或寫),如果有的話則返回可用資源的文件描述符個數,沒有的話則睡眠,等待有資源變為可用時再被喚醒繼續執行。

 

下面我們分兩個過程來分析select:

 

1. select的睡眠過程

 

支持阻塞操作的設備驅動通常會實現一組自身的等待隊列如讀/寫等待隊列用於支持上層(用戶層)所需的BLOCK或NONBLOCK操作。當應用程序通過設備驅動訪問該設備時(默認為BLOCK操作),若該設備當前沒有數據可讀或寫,則將該用戶進程插入到該設備驅動對應的讀/寫等待隊列讓其睡眠一段時間,等到有數據可讀/寫時再將該進程喚醒。

 

select就是巧妙的利用等待隊列機制讓用戶進程適當在沒有資源可讀/寫時睡眠,有資源可讀/寫時喚醒。下面我們看看select睡眠的詳細過程。

 

select會循環遍歷它所監測的fd_set內的所有文件描述符對應的驅動程序的poll函數。驅動程序提供的poll函數首先會將調用select的用戶進程插入到該設備驅動對應資源的等待隊列(如讀/寫等待隊列),然後返回一個bitmask告訴select當前資源哪些可用。當select循環遍歷完所有fd_set內指定的文件描述符對應的poll函數後,如果沒有一個資源可用(即沒有一個文件可供操作),則select讓該進程睡眠,一直等到有資源可用為止,進程被喚醒(或者timeout)繼續往下執行。

 

下面分析一下代碼是如何實現的。

select的調用path如下:sys_select -> core_sys_select -> do_select

其中最重要的函數是do_select, 最主要的工作是在這裡, 前面兩個函數主要做一些准備工作。do_select定義如下:

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

{

struct poll_wqueues table;

poll_table *wait;

int retval, i;

 

rcu_read_lock();

retval = max_select_fd(n, fds);

rcu_read_unlock();

 

if (retval < 0)

return retval;

n = retval;

 

poll_initwait(&table);

wait = &table.pt;

if (!*timeout)

wait = NULL;

retval = 0; //retval用於保存已經准備好的描述符數,初始為0

for (;;) {

unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;

long __timeout;

 

set_current_state(TASK_INTERRUPTIBLE); //將當前進程狀態改為TASK_INTERRUPTIBLE

 

inp = fds->in; outp = fds->out; exp = fds->ex;

rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex;

 

for (i = 0; i < n; ++rinp, ++routp, ++rexp) { //遍歷每個描述符

unsigned long in, out, ex, all_bits, bit = 1, mask, j;

unsigned long res_in = 0, res_out = 0, res_ex = 0;

const struct file_operations *f_op = NULL;

struct file *file = NULL;

 

in = *inp++; out = *outp++; ex = *exp++;

all_bits = in | out | ex;

if (all_bits == 0) {

i += __NFDBITS; // //如果這個字沒有待查找的描述符, 跳過這個長字(32位)

continue;

}

 

for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) { //遍歷每個長字裡的每個位

int fput_needed;

if (i >= n)

break;

if (!(bit & all_bits))

continue;

file = fget_light(i, &fput_needed);

if (file) {

f_op = file->f_op;

MARK(fs_select, "%d %lld",

i, (long long)*timeout);

mask = DEFAULT_POLLMASK;

if (f_op && f_op->poll)

/* 在這裡循環調用所監測的fd_set內的所有文件描述符對應的驅動程序的poll函數 */

mask = (*f_op->poll)(file, retval ? NULL : wait);

fput_light(file, fput_needed);

if ((mask & POLLIN_SET) && (in & bit)) {

res_in |= bit; //如果是這個描述符可讀, 將這個位置位

retval++; //返回描述符個數加1

}

if ((mask & POLLOUT_SET) && (out & bit)) {

res_out |= bit;

retval++;

}

if ((mask & POLLEX_SET) && (ex & bit)) {

res_ex |= bit;

retval++;

}

}

cond_resched();

}

//返回結果

if (res_in)

*rinp = res_in;

if (res_out)

*routp = res_out;

if (res_ex)

*rexp = res_ex;

}

wait = NULL;

/* 到這裡遍歷結束。retval保存了檢測到的可操作的文件描述符的個數。如果有文件可操作,則跳出for(;;)循環,直接返回。若沒有文件可操作且timeout時間未到同時沒有收到signal,則執行schedule_timeout睡眠。睡眠時間長短由__timeout決定,一直等到該進程被喚醒。

那該進程是如何被喚醒的?被誰喚醒的呢?

我們看下面的select喚醒過程*/

if (retval || !*timeout || signal_pending(current))

break;

if(table.error) {

retval = table.error;

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;

Copyright © Linux教程網 All Rights Reserved