Linux之poll機制分析
應用程序訪問1個設備文件時可用阻塞/非阻塞方式.如果是使用阻塞方式,則直接調用open()、read()、write(),但是在驅動程序層會判斷是否可讀/可寫,如果不可讀/不可寫,則將當前進程休眠,直到被喚醒。如果是使用非阻塞方式,就需要采用poll/select機制,而且打開文件時標記文件的訪問權限位為O_NONBLOCK。
1 int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
FD_CLR(inr fd,fd_set* set);用來清除描述詞組set中相關fd 的位
FD_ISSET(int fd,fd_set *set);用來測試描述詞組set中相關fd 的位是否為真
FD_SET(int fd,fd_set*set);用來設置描述詞組set中相關fd的位
FD_ZERO(fd_set *set);用來清除描述詞組set的全部位
如果參數timeout設為:NULL:則表示select()沒有timeout,select將一直被阻塞,直到某個文件描述符上發生了事件。0:僅檢測描述符集合的狀態,然後立即返回,並不等待外部事件的發生。特定的時間值:如果在指定的時間段裏沒有事件發生,select將超時返回。
1 int poll(struct pollfd *fds, nfds_t nfds, inttimeout);這兩個函數其實本質類似.
fds 可以傳遞多個結構體,也就是說可以監測多個驅動設備所產生的事件,只要有一個產生了請求事件,就能立即返回
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 請求的事件類型,監視驅動文件的事件掩碼 */
short revents; /* 驅動文件實際返回的事件 */
} ;
nfds 監測驅動文件的個數
timeout 超時時間,單位為ms
事件類型events 可以為下列值:
POLLIN 有數據可讀
POLLRDNORM 有普通數據可讀,等效與POLLIN
POLLPRI 有緊迫數據可讀
POLLOUT 寫數據不會導致阻塞
POLLER 指定的文件描述符發生錯誤
POLLHUP 指定的文件描述符掛起事件
POLLNVAL 無效的請求,打不開指定的文件描述符
返回值
有事件發生 返回revents域不為0的文件描述符個數(也就是說事件發生,或者錯誤報告)
超時 返回0;
失敗 返回-1,並設置errno為錯誤類型
理解select模型:
理解select模型的關鍵在於理解fd_set,為說明方便,取fd_set長度為1字節,fd_set中的每個bit 可以對應一個文件描述符fd。則1字節長的fd_set最大可以對應8個fd。
(1)執行fd_set set; FD_ZERO(&set);則set用位表示是0000,0000。
(2)若fd=5,執行FD_SET(fd,&set);後set變為0001,0000(第5位置為1)
(3)若再加入fd=2,fd=1,則set變為0001,0011
(4)執行select(6,&set,0,0,0)阻塞等待
(5)若fd=1,fd=2上都發生可讀事件,則select返回,此時set變為0000,0011。
註意:沒有事件 發生的fd=5被清空。
從內核態理解poll機制
我們從應用程序直接調用poll函數,系統會走以下流程
1 app:poll 2 kernel:sys_poll 3 do_sys_poll 4 poll_initwait(&table) 5 do_poll(nfds, head, &table, timeout) 6 for (;;) { 7 for (; pfd != pfd_end; pfd++) { /* 可以監測多個驅動設備所產生的事件 */ 8 if (do_pollfd(pfd, pt)) { 9 count++; 10 pt = NULL; 11 } 12 if (count || !*timeout || signal_pending(current)) 13 break; 14 __timeout = schedule_timeout(__timeout); 15 } 16 } 17 18 19 do_pollfd(pfd, pt){ 20 ... 21 if (file->f_op && file->f_op->poll) 22 mask = file->f_op->poll(file, pwait); 23 return mask; 24 ... 25 }基於內核源碼版本為linux-2.6.22.6
使用poll_initwait(&table),就是將__pollwait設為回調函數,後面會去調用驅動程序的poll函數,poll函數調用pollwait就等於調用__pollwait,將當前進程加入到等待隊列中。然後一直在循環,do_pollfd就是去調用驅動程序的poll函數,驅動程序的poll函數,poll函數開始調用pollwait就等於調用__pollwait回調函數,將當前進程加入到等待隊列中,以便喚醒休眠後的當前進程。然後返回當前驅動設備的狀態(mask).
如果do_pollfd返回的mask為非0,即count非0,就會馬上返回,應用程序就可以使用FD_ISSET了解此時設備狀態。當然,如果超時或者此進程有其他信號要處理超時,此進程有其他信號要處理,也會馬上返回,但是應用程序使用FD_ISSET了解到此時設備狀態還是不可用時,又繼續輪詢。如果do_pollfd返回的mask為0,而且未超時且未有其他信號發生,就會進程調度,讓此進程休眠。在前面已經將此進程加入到驅動程序的等待隊列中了,如果設備可用時,就會喚醒等待隊列中的進程,也就喚醒了此進程,又去poll_initwait(&table).
參考:https://www.cnblogs.com/amanlikethis/p/6915485.html
http://www.cnblogs.com/shihaochangeworld/p/5747490.html
Linux之poll機制分析