select/poll

以 open 系統呼叫開啟檔案時,預設會以「blocking mode」開啟,block 指的是 process 為了等待某件事情發生,而進入 sleep 狀態的情形。 Linux 應用程式多以 blocking mode 開發,而 windows 的主流則是 non-blocking mode。 對應用程式來說,如果 read 系統呼叫會被 block 的話,有時可能會引發設計上的問題,所以 linux 準備了以下的措施:
1.Non-block 模式
2.同時執行多個同步 I/O 工作

啟用 non-blocking 模式後,read() 與 write() 就不會被 block 了,而是傳回「EAGAIN」錯誤碼(errno)。

#define EAGAIN   11 /*Try again*/

在應用程式中,如果要使用 non-blocking 模式的話,就要在 open() 開檔時,以 OR 指定「O_NONBLOCK」選項。

fd = open(DEVFILE, O_RDWR | O_NONBLOCK); 
if ( fd == -1 ) { 
    perror("open"); 
    exit(1); 
}

同時執行多個同步 I/O 工作

同時執行多個同步 I/O 工作(synchronous I/O multiplexing) 指的是使用 select 系統呼叫。 透過 select 系統呼叫,可以監視多個 file handle 在三種狀態之的變化(可讀取、可寫入、發生錯誤)。 select 系統呼叫本身會被 block,但是可以指定 timeout。 read() 呼叫後,會一直等到資料抵達為止,所以可以在呼叫 read() 之前先呼叫 select() 判斷資料抵達了沒有(能不能讀取),如果可以讀取,再呼叫 read()。

retval = select(fd+1, &rfds, NULL, NULL, &tv); 
if ( retval ) { 
read(fd, buf, sizeof(buf)); 
}

使用 select() 時,有幾個經常用到的函式巨集

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

參數nfds需要檢查的文件描述符個數,數值應該比是三組fdset中最大數更大,而不是實際文件描述符的總數。參數 timeout為結構timeval,用來設置select()的等待時間, readfds(用來檢查可讀性的一組文件描述符)。writefds(用來檢查可寫性的一組文件描述符) 和exceptfds(用來檢查意外狀態的文件描述符) 稱為描述片語,是用來回傳該描述詞的讀,寫或例外的狀況。select()函數主要是建立在fd_set型態的基礎的,fd_set是一組檔案描述檔元(fd)的集合。底下的宏提供了處理這三種描述片語的方式:

void FD_CLR(int fd, fd_set *set); 將fd從set集合中清除
void FD_ISSET(int fd, fd_set *set); 測試fd是否在set集合中
void FD_SET(int fd, fd_set *set); 將fd 加入set 集合
void FD_ZERO(fd_set *set); 將set清為零,使集合中不含任何fd
fd_set set;

FD_ZERO(&set);

將set的所有位置0,如set在記憶體中佔8位則將set置為00000000

FD_SET(0,&set);

將set的第0位置1,如set原來是00000000,則現在變為10000000,這樣fd==1的檔案描述元就被加進set中了

FD_CLR(4,&set);

將set的第4位置0,如set原來是10001000,則現在變為10000000,這樣fd==4的檔案描述元就被從set中清除了

FD_ISSET(5,&set);

測試set的第5位是否為1,如果set原來是10000100,則返回非零,表明fd==5的檔案描述元在set中;否則返回0

另外還有個跟 select() 很類似的「poll()」系統呼叫,以基本功能來說 select() 與 poll() 都一樣,但指定 file handle 的方式不同。 而且,在指定多個 file handle 時,poll() 走訪所有 file handle 的速度較快

int poll(struct pollfd *fds, nfds_t nfds, int timeout); 

struct pollfd { 
int fd; /* file descriptor */ 
short events; /* requested events */ 
short revents; /* returned events */ 
};

第一個參數是指向一個結構數組的第一個元素的指針,每個元素都是一個pollfd結構,用於指定測試某個給定描述符的條件。第二個參數是要監聽的文件描述符的個數,也就是數組fds的元素個數;第三個參數意義與select相同。struct pollfd * fds是一个struct pollfd結構類型的數組,用於存放需要檢測其狀態的fd描述檔;每當調用這個函数之後,系统不需要清空這個數組,操作起來比較方便;特别是對於 fd連接比較多的情况下,在一定程度上可以提高處理的效率;這一點與select()函數不同,調用select()函數之後,select() 函數需要清空它所檢測的socket描述檔集合,導致每次调用select()之前都必须把fd描述檔重新加入到待檢測的集合中;因此,select()函數適合於只檢測少量fd描述檔的情况,而poll()函數適合於大量描述檔的情況。 要知道fd上的讀、寫、異常事件,就可以這樣做:

struct pollfd fds;

fds[nIndex].events=POLLIN | POLLOUT | POLLERR;

驅動程式的實作

應用程式想同時執行多個同步 I/O 工作時,可以使用的系統呼叫有好幾個,包含 select 與 poll 等,但驅動程式只需要準備一個函式就夠了,kernel 只會呼叫驅動程式提供的這個函式。 Character 類型的裝置想支援同時執行多個同步 I/O 工作的話,只要在驅動程式準備 poll 方法即可。

unsigned int (*poll) (struct file *, struct poll_table_struct *);

poll 方法會在 kernel 處理 select 與 poll 之類的系統呼叫時用到,它必須執行的工作很簡單,包含:
1.在 wait queue 登記
2.傳回目前可以操作的狀態

在呼叫執行多個同步 I/O 工作的系統呼叫時,「block 直到狀態變化」的動作,指的是在 kernel 裡面 sleep。 要 sleep ,需要先準備 wait queue (wait_queue_head_t),由驅動程式負責提供。 Wait queue 可以參考「Sleep 與 Wake Up」。
登記 wait queue 的工作可透過 poll_wait() 完成,定義在「linux/poll.h」:

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

在呼叫同時執行多個同步 I/O 工作的系統呼叫時,解除 block 的時機,是驅動程式透過傳給 poll_wait() 的 wait queue 被喚醒的時候。
Kernel 在被 wait queue 喚醒之後,會再次呼叫驅動程式的 poll() 確認是否已成為等待中的狀態 (可寫入或可讀出)。
如果是的話 FD_ISSET 巨集就會成立,要判斷是否為這種狀態,當然還是需要驅動程式提供資訊才行,這個資訊就是透過 poll() 方法的傳回值來表示。
傳回值要透過 linux/poll.h 定義的巨集 OR 起來表示,以下是常用到的組合:

Bit OR 意義
POLLIN / POLLRDNORM 可讀取
POLLOUT / POLLWRNORM 可寫入
POLLIN / POLLRDNORM / POLLOUT / POLLWRNORM 可讀寫
POLLERR 發生錯誤
POLLHUP 裝置離線(EOF)

下列是 poll 方法的程式片段,poll 方法在向 wait queue 登記完成之後必須立刻返回,不能在 poll 方法裡 sleep:

unsigned int devone_poll(struct file *filp, poll_table *wait) { 
struct devone_data *dev = filp->private_data; 
unsigned int mask = 0; 

if (dev == NULL) 
return -EBADFD; 
poll_wait(filp, &dev->read_wait, wait); 
if (dev->timeout_done == 1) { /* readable */ 
mask |= POLLIN | POLLRDNORM; 
} 
printk("%s returned (mask 0x%x)\n", __func__, mask); 
return mask; 
}

results matching ""

    No results matching ""