來源:百問網(wǎng)
作者:韋東山
本文字?jǐn)?shù):2344,閱讀時長:4分鐘
1.適用場景
在前面引入中斷時,我們曾經(jīng)舉過一個例子:

媽媽怎么知道臥室里小孩醒了?
- 時不時進(jìn)房間看一下:查詢方式
簡單,但是累 - 進(jìn)去房間陪小孩一起睡覺,小孩醒了會吵醒她:休眠-喚醒
不累,但是媽媽干不了活了 - 媽媽要干很多活,但是可以陪小孩睡一會,定個鬧鐘:poll方式
要浪費(fèi)點時間,但是可以繼續(xù)干活。媽媽要么是被小孩吵醒,要么是被鬧鐘吵醒。 - 媽媽在客廳干活,小孩醒了他會自己走出房門告訴媽媽:異步通知
媽媽、小孩互不耽誤
使用休眠-喚醒的方式等待某個事件發(fā)生時,有一個缺點:等待的時間可能很久。我們可以加上一個超時時間,這時就可以使用poll機(jī)制。
- App不知道驅(qū)動程序中是否有數(shù)據(jù),可以先調(diào)用poll函數(shù)查詢一下,poll函數(shù)可以傳入超時時間;
- APP進(jìn)入內(nèi)核態(tài),調(diào)用到驅(qū)動程序的poll函數(shù),如果有數(shù)據(jù)的話立刻返回;
- 如果發(fā)現(xiàn)沒有數(shù)據(jù)時就休眠一段時間;
- 當(dāng)有數(shù)據(jù)時,比如當(dāng)按下按鍵時,驅(qū)動程序的中斷服務(wù)程序被調(diào)用,它會記錄數(shù)據(jù)、喚醒APP;
- 當(dāng)超時時間到了之后,內(nèi)核也會喚醒APP;
- APP根據(jù)poll函數(shù)的返回值就可以知道是否有數(shù)據(jù),如果有數(shù)據(jù)就調(diào)用read得到數(shù)據(jù)
2.使用流程
媽媽進(jìn)入房間時,會先看小孩醒沒醒,鬧鐘響之后走出房間之前又會再看小孩醒沒醒。
注意:看了2次小孩!
POLL機(jī)制也是類似的,流程如下:

函數(shù)執(zhí)行流程如上圖①~⑧所示,重點從③開始看。假設(shè)一開始無按鍵數(shù)據(jù):
③ APP調(diào)用poll之后,進(jìn)入內(nèi)核態(tài);
④ 導(dǎo)致驅(qū)動程序的drv_poll被調(diào)用:
注意,drv_poll要把自己這個線程掛入等待隊列wq中;假設(shè)不放入隊列里,那以后發(fā)生中斷時,中斷服務(wù)程序去哪里找到你嘛?
drv_poll還會判斷一下:有沒有數(shù)據(jù)啊?返回這個狀態(tài)。
⑤ 假設(shè)當(dāng)前沒有數(shù)據(jù),則休眠一會;
⑥ 在休眠過程中,按下了按鍵,發(fā)生了中斷:
在中斷服務(wù)程序里記錄了按鍵值,并且從wq中把線程喚醒了。
⑦ 線程從休眠中被喚醒,繼續(xù)執(zhí)行for循環(huán),再次調(diào)用drv_poll:
drv_poll返回數(shù)據(jù)狀態(tài)
⑧ 哦,你有數(shù)據(jù),那從內(nèi)核態(tài)返回到應(yīng)用態(tài)吧
⑨ APP調(diào)用read函數(shù)讀數(shù)據(jù)
如果一直沒有數(shù)據(jù),調(diào)用流程也是類似的,重點從③開始看,如下:
③ APP調(diào)用poll之后,進(jìn)入內(nèi)核態(tài);
④ 導(dǎo)致驅(qū)動程序的drv_poll被調(diào)用:
注意,drv_poll要把自己這個線程掛入等待隊列wq中;假設(shè)不放入隊列里,那以后發(fā)生中斷時,中斷服務(wù)程序去哪里找到你嘛?
drv_poll還會判斷一下:有沒有數(shù)據(jù)啊?返回這個狀態(tài)。
⑤ 假設(shè)當(dāng)前沒有數(shù)據(jù),則休眠一會;
⑥ 在休眠過程中,一直沒有按下了按鍵,超時時間到:內(nèi)核把這個線程喚醒;
⑦ 線程從休眠中被喚醒,繼續(xù)執(zhí)行for循環(huán),再次調(diào)用drv_poll:
drv_poll返回數(shù)據(jù)狀態(tài)
⑧ 哦,你還是沒有數(shù)據(jù),但是超時時間到了,那從內(nèi)核態(tài)返回到應(yīng)用態(tài)吧
⑨ APP不能調(diào)用read函數(shù)讀數(shù)據(jù)
注意幾點:
- drv_poll要把線程掛入隊列wq,但是并不是在drv_poll中進(jìn)入休眠,而是在調(diào)用drv_poll之后休眠
- drv_poll要返回數(shù)據(jù)狀態(tài)
- APP調(diào)用一次poll,有可能會導(dǎo)致drv_poll被調(diào)用2次
- 線程被喚醒的原因有2:中斷發(fā)生了去隊列wq中把它喚醒,超時時間到了內(nèi)核把它喚醒
- APP要判斷poll返回的原因:有數(shù)據(jù),還是超時。有數(shù)據(jù)時再去調(diào)用read函數(shù)。
3. 驅(qū)動編程
使用poll機(jī)制時,驅(qū)動程序的核心就是提供對應(yīng)的drv_poll函數(shù)。
在drv_poll函數(shù)中要做2件事:
- 把當(dāng)前線程掛入隊列wq:poll_wait
APP調(diào)用一次poll,可能導(dǎo)致drv_poll被調(diào)用2次,但是我們并不需要把當(dāng)前線程掛入隊列2次。可以使用內(nèi)核的函數(shù)poll_wait把線程掛入隊列,如果線程已經(jīng)在隊列里了,它就不會再次掛入。 - 返回設(shè)備狀態(tài):
APP調(diào)用poll函數(shù)時,有可能是查詢“有沒有數(shù)據(jù)可以讀”:POLLIN,也有可能是查詢“你有沒有空間給我寫數(shù)據(jù)”:POLLOUT。所以drv_poll要返回自己的當(dāng)前狀態(tài):(POLLIN | POLLRDNORM) 或 (POLLOUT | POLLWRNORM)。POLLRDNORM等同于POLLIN,為了兼容某些APP把它們一起返回。POLLWRNORM等同于POLLOUT ,為了兼容某些APP把它們一起返回。
APP調(diào)用poll后,很有可能會休眠。對應(yīng)的,在按鍵驅(qū)動的中斷服務(wù)程序中,也要有喚醒操作。
驅(qū)動程序中poll的代碼如下:
static unsigned int gpio_key_drv_poll(struct file *fp, poll_table * wait)
{ printk("%s %s line %dn", __FILE__, __FUNCTION__, __LINE__);
poll_wait(fp, &gpio_key_wait, wait);
return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
}
4 應(yīng)用編程
注意:APP可以調(diào)用poll或select函數(shù),這2個函數(shù)的作用是一樣的。
poll/select函數(shù)可以監(jiān)測多個文件,可以監(jiān)測多種事件:
事件類型 說明
POLLIN 有數(shù)據(jù)可讀
POLLRDNORM 等同于POLLIN
POLLRDBAND Priority band data can be read,有優(yōu)先級較較高的“band data”可讀
linux系統(tǒng)中很少使用這個事件
POLLPRI 高優(yōu)先級數(shù)據(jù)可讀
POLLOUT 可以寫數(shù)據(jù)
POLLWRNORM 等同于POLLOUT
POLLWRBAND Priority data may be written
POLLERR 發(fā)生了錯誤
POLLHUP 掛起
POLLNVAL 無效的請求,一般是fd未open