Linux Poll 使用方法和實現
- struct pollfd {
- int fd; //檔案描述符
- short events; //要求查詢的事件掩碼
- short revents; //返回的事件掩碼
- };
- int poll(struct pollfd *ufds, unsigned int nfds, int timeout);
poll函式使用pollfd型別的結構來監控一組檔案控制代碼,ufds是要監控的檔案控制代碼集合,nfds是監控的檔案控制代碼數量,timeout是等待的毫秒數,這段時間內無論I/O是否準備好,poll都會返回。timeout為負數表示無線等待,timeout為0表示呼叫後立即返回。執行結果:為0表示超時前沒有任何事件發生;-1表示失敗;成功則返回結構體中revents不為0的檔案描述符個數。pollfd結構監控的事件型別如下:
- #define POLLIN 0x0001
- #define POLLPRI 0x0002
- #define POLLOUT 0x0004
- #define POLLERR 0x0008
- #define POLLHUP 0x0010
- #define POLLNVAL 0x0020
- #define POLLRDNORM 0x0040
- #define POLLRDBAND 0x0080
- #define POLLWRNORM 0x0100
- #define POLLWRBAND 0x0200
- #define POLLMSG 0x0400
- #define POLLREMOVE 0x1000
- #define POLLRDHUP 0x2000
如上是events事件掩碼的值域,POLLIN|POLLPRI類似於select的讀事件,POLLOUT|POLLWRBAND類似於select的寫事件。當events屬性為POLLIN|POLLOUT,表示監控是否可讀或可寫。在poll返回時,即可通過檢查revents變數對應的標誌位與events是否相同,比如revents中POLLIN事件標誌位被設定,則表示檔案描述符可以被讀取。程式碼段示例:
- int sockfd; //套接字控制代碼
- struct pollfd pollfds;
- int timeout;
- timeout = 5000;
- pollfds.fd = sockfd; //設定監控sockfd
- pollfds.events = POLLIN|POLLPRI; //設定監控的事件
- for(;;){
- switch(poll(&pollfds,1,timeout)){ //開始監控
- case -1: //函式調用出錯
- printf("poll error \r\n"
- break;
- case 0:
- printf("time out \r\n");
- break;
- default: //得到資料返回
- printf("sockfd have some event \r\n");
- printf("event value is 0x%x",pollfds.revents);
- break;
- }
- }
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
poll()函式:這個函式是某些Unix系統提供的用於執行與select()函式同等功能的函式,下面是這個函式的宣告:#include <poll.h>
int poll(struct pollfd fds[], nfds_t nfds, int timeout);
引數說明:
fds:是一個struct pollfd結構型別的陣列,用於存放需要檢測其狀態的Socket描述符;每當呼叫這個函式之後,系統不會清空這個陣列,操作起來比較方便;特別是對於 socket連線比較多的情況下,在一定程度上可以提高處理的效率;這一點與select()函式不同,呼叫select()函式之後,select() 函式會清空它所檢測的socket描述符集合,導致每次呼叫select()之前都必須把socket描述符重新加入到待檢測的集合中;因 此,select()函式適合於只檢測一個socket描述符的情況,而poll()函式適合於大量socket描述符的情況;
nfds:nfds_t型別的引數,用於標記陣列fds中的結構體元素的總數量;
timeout:是poll函式呼叫阻塞的時間,單位:毫秒;
返回值:
>0:陣列fds中準備好讀、寫或出錯狀態的那些socket描述符的總數量;
==0:陣列fds中沒有任何socket描述符準備好讀、寫,或出錯;此時poll超時,超時時間是timeout毫秒;換句話說,如果所檢測的 socket描述符上沒有任何事件發生的話,那麼poll()函式會阻塞timeout所指定的毫秒時間長度之後返回,如果timeout==0,那麼 poll() 函式立即返回而不阻塞,如果timeout==INFTIM,那麼poll() 函式會一直阻塞下去,直到所檢測的socket描述符上的感興趣的事件發 生是才返回,如果感興趣的事件永遠不發生,那麼poll()就會永遠阻塞下去;
-1: poll函式呼叫失敗,同時會自動設定全域性變數errno;
如果待檢測的socket描述符為負值,則對這個描述符的檢測就會被忽略,也就是不會對成員變數events進行檢測,在events上註冊的事件也會被忽略,poll()函式返回的時候,會把成員變數revents設定為0,表示沒有事件發生;
另外,poll() 函式不會受到socket描述符上的O_NDELAY標記和O_NONBLOCK標記的影響和制約,也就是說,不管socket是阻塞的還是非阻塞 的,poll()函式都不會收到影響;而select()函式則不同,select()函式會受到O_NDELAY標記和O_NONBLOCK標記的影 響,如果socket是阻塞的socket,則呼叫select()跟不呼叫select()時的效果是一樣的,socket仍然是阻塞式TCP通訊,相 反,如果socket是非阻塞的socket,那麼呼叫select()時就可以實現非阻塞式TCP通訊;
所以poll() 函式的功能和返回值的含義與 select() 函式的功能和返回值的含義是完全一樣的,兩者之間的差別就是內部實現方式不一樣,select()函式基本上可以在所有支援檔案描述符操作的系統平臺上運 行(如:Linux 、Unix 、Windows、MacOS等),可移植性好,而poll()函式則只有個別的的作業系統提供支援(如:SunOS、Solaris、AIX、HP提供 支援,但是Linux不提供支援),可移植性差;
strust pollfd結構說明:
typedef struct pollfd {
int fd; /* 需要被檢測或選擇的檔案描述符*/
short events; /* 對檔案描述符fd上感興趣的事件 */
short revents; /* 檔案描述符fd上當前實際發生的事件*/
} pollfd_t;
typedef unsigned long nfds_t;
經常檢測的事件標記: POLLIN/POLLRDNORM(可讀)、POLLOUT/POLLWRNORM(可寫)、POLLERR(出錯)
如果是對一個描述符上的多個事件感興趣的話,可以把這些常量標記之間進行按位或運算就可以了;
比如:對socket描述符fd上的讀、寫、異常事件感興趣,就可以這樣做:struct pollfd fds;
fds[nIndex].events=POLLIN | POLLOUT | POLLERR;
當 poll()函式返回時,要判斷所檢測的socket描述符上發生的事件,可以這樣做: struct pollfd fds;
檢測可讀TCP連線請求:
if((fds[nIndex].revents & POLLIN) == POLLIN){//接收資料/呼叫accept()接收連線請求}
檢測可寫:
if((fds[nIndex].revents & POLLOUT) == POLLOUT){//傳送資料}
檢測異常:
if((fds[nIndex].revents & POLLERR) == POLLERR){//異常處理}
每一個pollfd結構體指定了一個被監視的檔案描述符,可以傳遞多個結構體,指示poll()監視多個檔案描述符。每個結構體的events域是監視該檔案描述符的事件掩碼,由使用者來設定這個域。revents域是檔案描述符的操作結果事件掩碼。核心在呼叫返回時設定這個域。events域中請求的任何事件都可能在revents域中返回。合法的事件如下:
POLLIN
有資料可讀。
POLLRDNORM
有普通資料可讀。
POLLRDBAND
有優先資料可讀。
POLLPRI
有緊迫資料可讀。
POLLOUT
寫資料不會導致阻塞。
POLLWRNORM
寫普通資料不會導致阻塞。
POLLWRBAND
寫優先資料不會導致阻塞。
POLLMSG
SIGPOLL 訊息可用。
此外,revents域中還可能返回下列事件:
POLLER
指定的檔案描述符發生錯誤。
POLLHUP
指定的檔案描述符掛起事件。
POLLNVAL
指定的檔案描述符非法。
這些事件在events域中無意義,因為它們在合適的時候總是會從revents中返回。使用poll()和select()不一樣,你不需要顯式地請求異常情況報告。
POLLIN | POLLPRI等價於select()的讀事件,POLLOUT |POLLWRBAND等價於select()的寫事件。POLLIN等價於POLLRDNORM |POLLRDBAND,而POLLOUT則等價於POLLWRNORM。
例如,要同時監視一個檔案描述符是否可讀和可寫,我們可以設定 events為POLLIN |POLLOUT。在poll返回時,我們可以檢查revents中的標誌,對應於檔案描述符請求的events結構體。如果POLLIN事件被設定,則檔案描述符可以被讀取而不阻塞。如果POLLOUT被設定,則檔案描述符可以寫入而不導致阻塞。這些標誌並不是互斥的:它們可能被同時設定,表示這個檔案描述符的讀取和寫入操作都會正常返回而不阻塞。
timeout引數指定等待的毫秒數,無論I/O是否準備好,poll都會返回。timeout指定為負數值表示無限超時;timeout為0指示poll呼叫立即返回並列出準備好I/O的檔案描述符,但並不等待其它的事件。這種情況下,poll()就像它的名字那樣,一旦選舉出來,立即返回。
返回值和錯誤程式碼
成功時,poll()返回結構體中revents域不為0的檔案描述符個數;如果在超時前沒有任何事件發生,poll()返回0;失敗時,poll()返回-1,並設定errno為下列值之一:
EBADF
一個或多個結構體中指定的檔案描述符無效。
EFAULT
fds指標指向的地址超出程序的地址空間。
EINTR
請求的事件之前產生一個訊號,呼叫可以重新發起。
EINVAL
nfds引數超出PLIMIT_NOFILE值。
ENOMEM
可用記憶體不足,無法完成請求。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Poll就是監控檔案是否可讀的一種機制,作用與select一樣。
應用程式的呼叫函式如下:
int poll(struct pollfd *fds,nfds_t nfds, int timeout);
Poll機制會判斷fds中的檔案是否可讀,如果可讀則會立即返回,返回的值就是可讀fd的數量,如果不可讀,那麼就程序就會休眠timeout這麼長的時間,然後再來判斷是否有檔案可讀,如果有,返回fd的數量,如果沒有,則返回0.
核心實現流程:
當應用程式呼叫poll函式的時候,會呼叫到系統呼叫sys_poll函式,該函式最終呼叫do_poll函式,do_poll函式中有一個死循 環,
在裡面又會利用do_pollfd函式去呼叫驅動中的poll函式(fds中每個成員的字元驅動程式都會被掃描到),驅動程式中的Poll函式的工作 有兩個,
一個就是呼叫poll_wait 函式,把程序掛到等待佇列中去(這個是必須的,你要睡眠,必須要在一個等待佇列上面,否則到哪裡去喚醒你呢??),
另一個是確定相關的fd是否有內容可 讀,如果可讀,就返回1,否則返回0,如果返回1 ,do_poll函式中的count++,
然後 do_poll函式然後判斷三個條件(if (count ||!timeout || signal_pending(current)))如果成立就直接跳出,如果不成立,
就睡眠timeout個jiffes這麼長的時間(呼叫schedule_timeout實現睡眠),如果在這段時間內沒有其他程序去喚醒它,
那麼第二次執行判斷的時候就會跳出死迴圈。如果在這段時間內有其他程序喚醒它,那麼也可以跳出死迴圈返回
(例如我們可以利用中斷處理函式去喚醒它,這樣的話一有資料可讀,就可以讓它立即返回)。
1. sys_poll函式位於fs/select.c檔案中,程式碼如下:
asmlinkagelong sys_poll(struct pollfd __user *ufds, unsigned int nfds, long timeout_msecs)
{
s64 timeout_jiffies;
if (timeout_msecs > 0) {
#ifHZ > 1000
/* We can only overflow if HZ >1000 */
if (timeout_msecs / 1000 >(s64)0x7fffffffffffffffULL / (s64)HZ)
timeout_jiffies = -1;
else
#endif
timeout_jiffies =msecs_to_jiffies(timeout_msecs);
}
else
{
/* Infinite (< 0) or no (0)timeout */
timeout_jiffies = timeout_msecs;
}
return do_sys_poll(ufds,nfds, &timeout_jiffies);
}
它對超時引數稍作處理後,直接呼叫do_sys_poll。
2. do_sys_poll函式也位於位於fs/select.c檔案中,我們忽略其他程式碼:
intdo_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout)
{
……
poll_initwait(&table);
……
fdcount = do_poll(nfds, head,&table, timeout);
……
}
poll_initwait函式非常簡單,它初始化一個poll_wqueues變數table:
poll_initwait> init_poll_funcptr(&pwq->pt, __pollwait); > pt->qproc = qproc;
即table->pt->qproc= __pollwait,__pollwait將在驅動的poll函式裡用到。
3. do_sys_poll函式位於fs/select.c檔案中,程式碼如下:
static int do_poll(unsigned int nfds, struct poll_list *list, struct poll_wqueues *wait, s64 *timeout)
{
01 ……
02 for (;;){
03 ……
04 if(do_pollfd(pfd, pt)) {
05 count++;
06 pt = NULL;
07 }
08 ……
09 if(count || !*timeout || signal_pending(current))
10 break;
11 count= wait->error;
12 if(count)
13 break;14
15 if(*timeout < 0) {
16 /*Wait indefinitely */
17 __timeout= MAX_SCHEDULE_TIMEOUT;
18 }else if (unlikely(*timeout >= (s64)MAX_SCHEDULE_TIMEOUT-1)) {
19 /*
20 * Wait for longer than MAX_SCHEDULE_TIMEOUT. Do it in
21 * a loop
22 */
23 __timeout= MAX_SCHEDULE_TIMEOUT - 1;
24 *timeout-= __timeout;
25 }else {
26 __timeout= *timeout;
27 *timeout= 0;
28 }29
30 __timeout= schedule_timeout(__timeout); // 休眠時間由應用提供
31 if(*timeout >= 0)
32 *timeout+= __timeout;
33 }
34 __set_current_state(TASK_RUNNING);
35 returncount;
36 }
分析其中的程式碼,可以發現,它的作用如下:
① 從02行可以知道,這是個迴圈,它退出的條件為:
a. 09行的3個條件之一(count非0,超時、有訊號等待處理)
count順0表示04行的do_pollfd至少有一個成功。
b. 11、12行:發生錯誤
② 重點在do_pollfd函式,後面再分析
③ 第30行,讓本程序休眠一段時間,注意:應用程式執行poll呼叫後,如果①②的條件不滿足,程序就會進入休眠。那麼,誰喚醒呢?除了休眠到指定時間被系統喚醒外,還可以被驅動程式喚醒──記住這點,這就是為什麼驅動的poll裡要呼叫poll_wait的原因,後面分析。
4. do_pollfd函式位於fs/select.c檔案中,程式碼如下:
static inline unsigned int do_pollfd(struct pollfd*pollfd, poll_table *pwait)
{
……
if(file->f_op && file->f_op->poll)
mask= file->f_op->poll(file, pwait);
……
}
可見,它就是呼叫我們的驅動程式裡註冊的poll函式。
二、驅動程式:
驅動程式裡與poll相關的地方有兩處:一是構造file_operation結構時,要定義自己的poll函式。二是通過poll_wait來呼叫上面說到的__pollwait函式,pollwait的程式碼如下:
staticinline 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);
}
p->qproc就是__pollwait函式,從它的程式碼可知,它只是把當前程序掛入我們驅動程式裡定義的一個佇列裡而已。它的程式碼如下:
staticvoid __pollwait(struct file *filp, wait_queue_head_t *wait_address, poll_table *p)
{
struct poll_table_entry *entry =poll_get_entry(p);
if (!entry)
return;
get_file(filp);
entry->filp = filp;
entry->wait_address = wait_address;
init_waitqueue_entry(&entry->wait,current);
add_wait_queue(wait_address,&entry->wait);
}
執行到驅動程式的poll_wait函式時,程序並沒有休眠,我們的驅動程式裡實現的poll函式是不會引起休眠的。讓程序進入休眠,是前面分析的do_sys_poll函式的30行“__timeout = schedule_timeout(__timeout)”。
poll_wait只是把本程序掛入某個佇列,應用程式呼叫poll > sys_poll> do_sys_poll > poll_initwait,do_poll > do_pollfd > 我們自己寫的poll函式後,再呼叫schedule_timeout進入休眠。如果我們的驅動程式發現情況就緒,可以把這個佇列上掛著的程序喚醒。可見,poll_wait的作用,只是為了讓驅動程式能找到要喚醒的程序。即使不用poll_wait,我們的程式也有機會被喚醒:chedule_timeout(__timeout),只是休眠__time_out這段時間。
現在來總結一下poll機制:
1. poll > sys_poll > do_sys_poll >poll_initwait,poll_initwait函式註冊一下回調函式__pollwait,它就是我們的驅動程式執行poll_wait時,真正被呼叫的函式。
2. 接下來執行file->f_op->poll,即我們驅動程式裡自己實現的poll函式
它會呼叫poll_wait把自己掛入某個佇列,這個佇列也是我們的驅動自己定義的;
它還判斷一下裝置是否就緒。
3. 如果裝置未就緒,do_sys_poll裡會讓程序休眠一定時間,這個時間是應用提供的“超時時間”
4. 程序被喚醒的條件有2:一是上面說的“一定時間”到了,二是被驅動程式喚醒。驅動程式發現條件就緒時,就把“某個佇列”上掛著的程序喚醒,這個佇列,就是前面通過poll_wait把本程序掛過去的佇列。
5. 如果驅動程式沒有去喚醒程序,那麼chedule_timeout(__timeou)超時後,會重複2、3動作1次,直到應用程式給定的時間, 然後返回。
驅動的poll函式編寫模板如下:
static DECLARE_WAIT_QUEUE_HEAD(my_waitq); //休眠要掛的等待佇列
static unsigned drv_poll(struct file *file, poll_table *wait)
{
unsigned int mask = 0;
poll_wait(file, &my_waitq, wait); // 不會立即休眠
if (有資料)
mask |= POLLIN | POLLRDNORM;
return mask;
}