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