1、概念linux
一、poll情景描述編程
以按鍵驅動爲例進行說明,用阻塞的方式打開按鍵驅動文件/dev/buttons,應用程序使用read()函數來讀取按鍵的鍵值。這樣作的效果是:若是有按鍵按下了,調用該read()函數的進程,就成功讀取到數據,應用程序獲得繼續執行;假若沒有按鍵按下,則要一直處於休眠狀態,等待這有按鍵按下這樣的事件發生。app
這種功能在一些場合是適用的,可是並不能知足咱們全部的須要,有時咱們須要一個時間節點。假若沒有按鍵按下,那麼超過多少時間以後,也要返回超時錯誤信息,進程可以繼續獲得執行,而不是沒有按鍵按下,就永遠休眠。這種例子其實還有不少,比方說兩人相親,男方等待女方給個肯定相處的信,男方不可能由於女方不給信,就永遠等待下去,雙方須要一個時間節點。這個時間節點,就是說超過這個時間以後,不能再等了,程序還要繼續運行,須要採起其餘的行動來解決問題。框架
example: 函數
單片機編程,等待IIC設備一個事件的發生,若是在容許的時間內發生了就返回1(SUCCESS),不然返回0(ERROR)。ui
uint8_t I2C_WaitForEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT,int32_t delay) { while(!I2C_CheckEvent(I2Cx, I2C_EVENT) && (delay-- > 0)); if(delay < 0){ return 0; } return 1; }
此段函數代碼能夠這樣來調用,以下:spa
int8_t I2C_EE_PageWrite(u8* pBuffer, u16 WriteAddr, u8 NumByteToWrite) { ............. if(I2C_WaitForEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED, 100000) != 1){ return -1; } ............ }
這個例子是STM32單片機寫i2cflash--AT24C02,可見上述的頁寫函數調用的等待字節傳輸完成函數(I2C_EVENT_MASTER_BYTE_TRANSMITTED).net
,若是在限定的時間內(CPU將100000減到0),尚未成功寫入,那麼就將返回超時錯誤,頁寫函數也會返回寫入失敗的錯誤信息。以後,任務從新獲得了運行。pwa
對於單片機這樣一般單任務運行的情況,必須採起這樣的措施。若是沒有超時限制,那麼程序將陷入死機,不能再繼續運行。指針
二、linux應用程序poll的使用
對於相似的場景,linux系統使用poll功能來解決這樣的問題。並且,與上述單片機等待方式不一樣,linux系統再調用poll()函數時候,若是沒有發生須要的事件,那麼進程進入休眠。若是在限定的時間內獲得須要的事件,那麼成功返回,若是沒有則返回超時錯誤信息。
可見,等待期間將進程休眠,利用事件驅動來喚醒進程,將更能提升CPU的效率。下面,以一個應用例程來講明poll的應用程序使用方法:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/ioctl.h> #include <poll.h> int main(int argc, char **argv) { int i; int ret; int fd; unsigned char keys_val; struct pollfd fds[1]; fd = open("/dev/buttons", 0); // 打開設備 if (fd < 0) { printf("Can't open /dev/buttons\n"); return -1; } fds[0].fd = fd; fds[0].events = POLLIN; while (1) { ret = poll(fds,1, 5000); if(ret == 0) { printf("time out!\n"); } else { read(fd, &keys_val, sizeof(keys_val)); printf("keys_val = 0x%x\n",keys_val); } } close(fd); return 0; }
例程實現的功能是這樣的:用poll()函數監測按鍵按下的事件,若是按下了就將鍵值打印出來;若是超過5S,尚未按鍵按下,就打印出超時信息。
三、poll()函數
函數原型
int poll(struct pollfd *fds, nfds_t nfds, int timeout)
輸入參數
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爲錯誤類型
2、驅動實現方法
/* 定義一個等待隊列,這個等待隊列其實是由中斷驅動的,當中斷髮生時,會令掛接到這個等待隊列的休眠進程喚醒 */
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
static unsigned drivers_poll(struct file *file, poll_table *wait) { unsigned int mask = 0; poll_wait(file, &button_waitq, wait); /* 將進程掛接到button_waitq等待隊列下 */
/* 根據實際狀況,標記事件類型 */ if (ev_press) mask |= POLLIN | POLLRDNORM;
/* 若是mask爲0,那麼證實沒有請求事件發生;若是非零說明有時間發生 */ return mask; }
上述代碼展現了一個poll()函數功能,具體對應的底層驅動實現細節。利用這樣的框架,咱們能夠寫出相似驅動的poll功能。可是,這個框架很難理解,不知道爲何這樣編寫?爲此,咱們須要瞭解linux系統poll功能實現的機制。
3、linux內核poll實現機制
從應用程序調用poll()函數開始,一直到調用drivers_poll函數,期間的過程很複雜,撿主要的內容列出來:
app: poll | drv:sys_poll | — do_sys_poll(struct pollfd __user * ufds, unsigned int nfds, struct timespec * end_time) | - poll_initwait(&table); > 實際效果:令函數指針 table.pt.qproc = __pollwait,這個函數指針最終會傳遞給poll_wait函數調用中的wait->qproc | - do_poll(nfds, head, &table, end_time); |
_ for ( ; ; ) { for (; pfd != pfd_end; pfd++) { /* 能夠監測多個驅動設備所產生的事件 */ if (do_pollfd(pfd, pt)) { |
_ mask = file->f_op->poll(file, pwait); > 實際效果:執行咱們寫的drivers_poll(file,pwait)
|
_ poll_wait(file, &button_waitq, wait); > 實際效果:執行__pollwait(file, &button_waitq, wait),也就是將
進程掛接到button_waitq等待隊列下
|
— mask賦值 ; return mask; /* 返回事件類型 */
pollfd->revents = mask; /* 將實際事件類型返回 */
count++; pt = NULL;
}
}
if (count || timed_out) /* 若是有事件發生,或者超時,則跳出poll */
break;
if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack)) /* 若是沒有事件發生,那麼陷入休眠狀態 */
timed_out = 1;
}
因而可知,咱們的drivers_poll()函數,是系統在執行sys_poll()過程當中的一個調用,調用的目的是「將進程掛接到等待隊列下」和「返回事件類型mask」。當已經發生了請求事件,那麼經過標記mask非0,if (do_pollfd(pfd, pt))判斷爲真,令count++,從而能夠直接令poll()函數成功返回。若是尚未發生請求的事件,那麼mask被標記爲0,進程將經過函數poll_schedule_timeout()陷入休眠狀態。一旦發生了請求的事件,由於以前已經將進程掛接到等待隊列下,因此進程將被喚醒,從新執行drivers_poll(),而顯然此時可以成功返回。
備註:分析的源碼版本爲linux-2.6.30.4。
參考資料:韋東山linux教學視頻