“小王,來聊聊,今天面試的情況怎麼樣,應該挺順利的吧..”看著小王平淡的眉頭,我問道。
“唉,別提了,你說,我的運氣咋這差呢,面試前你不是給我講了有關阻塞的問題嗎,我見了面試官是吧,還跟他好好的用今天排隊的例子說了有關阻塞的問題,但是..”小王哀聲歎氣地說到。
“別但是了,怎麼啦..”
“可問題是面試官壓根就沒打算問我有關阻塞的問題及解決方案,但是問我說:這樣吧,你給我說說在Linux裝置驅動中有關非阻塞的方法,我這一聽,傻眼了不是,你剛好給我講的是阻塞的東西,可人家偏要問我有關非阻塞的問題,我..”小王欲哭無淚啊..
“怎麼這樣呢,算了,機會多的是,亡羊補牢,我現在就給你說說有關非阻塞的問題----Linux裝置驅動程式之阻塞非阻塞IO----輪詢操作”。
通過上一節,我們都明白了,有關阻塞的相關知識(不知道,那我沒轍了,飯送到嘴,你還挑食,難不成我拿把起子把嘴撬開不成,自己看上一篇吧),現在就來聊聊對
立面非阻塞。
使用非阻塞I/O的應用程式通常會使用select()和poll()系統調用查詢是否可對裝置進行無阻塞的訪問,這兩個系統調用最終又會引發裝置驅動中的poll()函數被執行
,所以我們的問題就集中到了如何編寫裝置驅動中的poll()函數就可以了。二話不說,先來看看裝置驅動中的poll()函數原型:
unsigned int (*poll)(struct file *filp, struct poll_table *wait);
這個函數要進行下面兩項工作。首先,對可能引起裝置檔案狀態變化的等待隊列調用poll_wait(),將對應的等待隊列頭添加到poll_table.然後,返回表示是否能對裝置進行無阻塞讀寫訪問的掩碼。在上面提到了一個poll_wait()函數,它的原型:
void poll_wait(struct file *filp, wait_queue_head_t *queue, poll_table *wait);
它的作用就是把當前進程添加到wait參數指定的等待列表(poll_table)中。需要注意的是這個函數是不會引起阻塞的,呵呵,誰給它取得個名字帶wait的,給咱們添這麼多麻煩。
“等等,你先停停,你是高手,我可是菜鳥呢,你先給我說說poll_table結構吧,心裡總是想它是什麼..”小王打斷我道。
行行,說起這個結構,我也是費了一番周折,它定義在“include/linux/poll.h, line 38“,具體如下:
typedef struct poll_table_struct { poll_queue_proc qproc; unsigned long key;} poll_table; 看看,其實沒什麼吧,不要想的太複雜了
經過以上驅動程式的poll()函數應該返回裝置資源的可擷取狀態,即POLLIN,POLLOUT,POLLPRI,POLLERR,POLLNVAL等宏的位"或"結果.每個宏的含義都表示裝置的一種狀態,如:
| 常量 |
說明 |
| POLLIN |
普通或優先順序帶資料可讀 |
| POLLRDNORM |
普通資料可讀 |
| POLLRDBAND |
優先順序帶資料可讀 |
| POLLPRI |
高優先順序資料可讀 |
| POLLOUT |
普通資料可寫 |
| POLLWRNORM |
普通資料可寫 |
| POLLWRBAND |
優先順序帶資料可寫 |
| POLLERR |
發生錯誤 |
| POLLHUP |
發生掛起 |
| POLLNVAL |
描述字不是一個開啟的檔案 |
"小王,你明白了沒.."看著小王眨巴眨巴的小眼睛,我說。
"呵呵,你乾脆給我來個典型模板,行不?"小王苛求道。
行,沒問題,你現在特殊時期,我是有求必應。請看下邊:
static unsigned int XXX_poll(struct file *filp, poll_table *wait){ unsigned int mask = 0; struct XXX_dev *dev = filp->private_data; //獲得裝置結構指標 ... poll_wait(filp, &dev->r_wait, wait); //加讀等待對列頭 poll_wait(filp ,&dev->w_wait, wait); //加寫等待隊列頭 if(...)//可讀 { mask |= POLLIN | POLLRDNORM; //標識資料可獲得 } if(...)//可寫 { mask |= POLLOUT | POLLRDNORM; //標識資料可寫入 } .. return mask;}
"小王,這次看明白了吧,要是還看不明白,我就再給你講講使用者空間的輪詢編程,兩個結合起來也許好懂點,行不"。我補充道。
在使用者程式中,select()和poll()本質上是一樣的, 不同只是引入的方式不同,前者是在BSD UNIX中引入的,後者是在System V中引入的。用的比較廣泛的是select
系統調用。原型如下:
int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptionfds, struct timeval *timeout);
其中readfs,writefds,exceptfds分別是select()監視的讀,寫和異常處理的檔案描述符集合,numfds的值是需要檢查的號碼最高的檔案描述符加1,timeout則是一個時間上限值,超過該值後,即使仍沒有描述符準備好也會返回。
struct timeval{ int tv_sec; //秒 int tv_usec; //微秒}
涉及到檔案描述符集合的操作主要有以下幾種:
1)清除一個檔案描述符集 FD_ZERO(fd_set *set);
2)將一個檔案描述符加入檔案描述符集中 FD_SET(int fd,fd_set *set);
3)將一個檔案描述符從檔案描述符集中清除 FD_CLR(int fd,fd_set *set);
4)判斷檔案描述符是否被置位 FD_ISSET(int fd,fd_set *set);
最後我們利用上面的檔案描述符集的相關來寫個驗證添加了裝置輪詢的驅動,把上邊兩塊聯絡起來:
必要的標頭檔#include <sys/types.h>#include <sys/stat.h>#include <stdio.h>#include <fcntl.h>#include <unistd.h>#include <sys/time.h>
#define FIFO_CLEAR 0x1#define BUFFER_LEN 20main(){ int fd, num; char rd_ch[BUFFER_LEN]; fd_set rfds,wfds; /*以非阻塞方式開啟/dev/polltest裝置檔案*/ fd = open("/dev/polltest", O_RDONLY | O_NONBLOCK); if (fd != - 1) { /*FIFO清0*/ if (ioctl(fd, FIFO_CLEAR, 0) < 0) { printf("ioctl command failed\n"); } while (1) { FD_ZERO(&rfds); FD_ZERO(&wfds); FD_SET(fd, &rfds); FD_SET(fd, &wfds); select(fd + 1, &rfds, &wfds, NULL, NULL); /*資料可獲得*/ if (FD_ISSET(fd, &rfds)) { printf("Device can be read now\n"); } /*資料可寫入*/ if (FD_ISSET(fd, &wfds)) { printf("Device can be written now\n"); } } } else { printf("Device open failure now\n"); }}