本節繼續在上一節中斷按鍵程序里改進,添加poll機制.
那么我們為什么還需要poll機制呢。之前的測試程序是這樣:
while (1) { read(fd, &key_val, 1); printf("key_val = 0x%x\n", key_val); }
在沒有poll機制的情況下,大部分時間程序都處在read中休眠的那個位置。如果我們不想讓程序停在這個位置,而是希望當有按鍵按下時,我們再去read,因此我們編寫poll函數,測試程序調用poll函數根據返回值,來決定是否執行read函數。
poll機制作用:相當於定時器,設置一定時間使進程等待資源,如果時間到了中斷還處於睡眠狀態(等待隊列),poll機制就會喚醒中斷,獲取一次資源
1.poll機制內核框架
如下圖所示,在用戶層上,使用poll或select函數時,和open、read那些函數一樣,也要進入內核sys_poll函數里,接下來我們分析sys_poll函數來了解poll機制(位於/fs/select.c)
1.1 sys_poll代碼如下:
asmlinkage long sys_poll(struct pollfd __user *ufds, unsigned int nfds,long timeout_msecs) { if (timeout_msecs > 0) //參數timeout>0 { timeout_jiffies = msecs_to_jiffies(timeout_msecs); //通過頻率來計算timeout時間需要多少計數值 } else { timeout_jiffies = timeout_msecs; //如果timeout時間為0,直接賦值 } return do_sys_poll(ufds, nfds, &timeout_jiffies); //調用do_sys_poll。 }
1.2 然后進入do_sys_poll(位於fs/select.c):
int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout) { ... ... /*初始化一個poll_wqueues變量table*/ poll_initwait(&table); ... ... fdcount = do_poll(nfds, head, &table, timeout); ... ... }
1.3進入poll_initwait函數,發現主要實現以下一句,后面會分析這里:
table ->pt-> qproc=__pollwait; //__pollwait將在驅動的poll函數里的poll_wait函數用到
1.4然后進入do_poll函數, (位於fs/select.c):
static int do_poll(unsigned int nfds, struct poll_list *list, struct poll_wqueues *wait, s64 *timeout) { …… for (;;) { …… set_current_state(TASK_INTERRUPTIBLE); //設置為等待隊列狀態 ...... for (; pfd != pfd_end; pfd++) { //for循環運行多個poll機制 /*將pfd和pt參數代入我們驅動程序里注冊的poll函數*/ if (do_pollfd(pfd, pt)) //若返回非0,count++,后面並退出 { count++; pt = NULL; } } …… /*count非0(.poll函數返回非0),timeout超時計數到0,有信號在等待*/ if (count || !*timeout || signal_pending(current)) break; …… /*進入休眠狀態,只有當timeout超時計數到0,或者被中斷喚醒才退出,*/ __timeout = schedule_timeout(__timeout); …… } __set_current_state(TASK_RUNNING); //開始運行 return count; }
1.4.1上面do_pollfd函數到底是怎么將pfd和pt參數代入的?代碼如下(位於fs/select.c):
static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait) { …… if (file->f_op && file->f_op->poll) mask = file->f_op->poll(file, pwait); …… return mask; }
上面file->f_op 就是我們驅動里的file_oprations結構體,如下圖所示:
所以do_pollfd(pfd, pt)就執行了我們驅動程序里的.poll(pfd, pt)函數(第2小節開始分析.poll函數)
1.4.2當poll進入休眠狀態后,又是誰來喚醒它?這就要分析我們的驅動程序.poll函數(第2小節開始分析.poll函數)
2寫驅動程序.poll函數,並分析.poll函數:
在上一節驅動程序里添加以下代碼:
#include <linux/poll.h> //添加頭文件
/* .poll驅動函數: third_poll */ static unsigned int third_poll(struct file *fp, poll_table * wait) //fp:文件 wait: { unsigned int mask =0; poll_wait(fp, &button_wait, wait); if(even_press) //中斷事件標志, 1:退出休眠狀態 0:進入休眠狀態 mask |= POLLIN | POLLRDNORM ; return mask; //當超時,就返給應用層為0 ,被喚醒了就返回POLLIN | POLLRDNORM ; } static struct file_operations third_drv_fops={ .owner = THIS_MODULE, .open = third_drv_open, .read = third_drv_read, .release=third_drv_class, .poll = third_poll, //創建.poll函數 };
2.1 在我們1.4小節do_poll函數有一段以下代碼:
if (do_pollfd(pfd, pt)) //若返回非0,count++,后面並退出 {
count++; pt = NULL; }
且在1.4.1分析出: do_pollfd(pfd, pt)就是指向的驅動程序third_poll()函數,
所以當我們有按鍵按下時, 驅動函數third_poll()就會返回mask非0值,然后在內核函數do_poll里的count就++,poll機制並退出睡眠.
2.2分析在內核中poll機制如何被驅動里的中斷喚醒的
在驅動函數third_poll()里有以下一句:
poll_wait(fp, &button_wait, wait);
如上圖所示,代入參數,poll_wait()就是執行了: p->qproc(filp, button_wait, p);
剛好對應了我們1.3小節的:
table ->pt-> qproc=__pollwait;
所以poll_wait()函數就是調用了: __pollwait(filp, button_wait, p);
然后我們來分析__pollwait函數,pollwait的代碼如下:
static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,poll_table *p) { ... ... //把current進程掛載到&entry->wait下 init_waitqueue_entry(&entry->wait, current); //再&entry->wait把添加到到button_wait中斷下 add_wait_queue(wait_address, &entry->wait); }
它是將poll進程添加到了button_wait中斷隊列里,這樣,一有按鍵按下時,在中斷服務函數里就會喚醒button_wait中斷,同樣也會喚醒poll機制,使poll機制重新進程休眠計數
2.3 驅動程序.poll函數返回值介紹
當中斷休眠狀態時,返回mask為0
當運行時返回:mask |= POLLIN | POLLRDNORM
其中參數意義如下:
常量 |
說明 |
POLLIN |
普通或優先級帶數據可讀 |
POLLRDNORM |
normal普通數據可讀 |
POLLRDBAND |
優先級帶數據可讀 |
POLLPRI |
Priority高優先級數據可讀 |
POLLOUT |
普通數據可寫 |
POLLWRNORM |
normal普通數據可寫 |
POLLWRBAND |
band優先級帶數據可寫 |
POLLERR |
發生錯誤 |
POLLHUP |
發生掛起 |
POLLNVAL |
描述字不是一個打開的文件 |
所以POLLIN | POLLRDNORM:普通數據可讀|優先級帶數據可讀
mask就返回到應用層poll函數,
3.改進測試程序third_poll_text.c(添加poll函數)
在linux中可以通過man poll 來查看poll函數如何使用
poll函數原型如下(#include <poll.h>):
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
參數介紹:
1) *fds:是一個poll描述符結構體數組(可以處理多個poll),結構體pollfd如下:
struct pollfd { int fd; /* file descriptor 文件描述符*/ short events; /* requested events 請求的事件*/ short revents; /* returned events 返回的事件(函數返回值)*/ };
其中events和revents值參數如下:
常量 |
說明 |
POLLIN |
普通或優先級帶數據可讀 |
POLLRDNORM |
normal普通數據可讀 |
POLLRDBAND |
優先級帶數據可讀 |
POLLPRI |
Priority高優先級數據可讀 |
POLLOUT |
普通數據可寫 |
POLLWRNORM |
normal普通數據可寫 |
POLLWRBAND |
band優先級帶數據可寫 |
POLLERR |
發生錯誤 |
POLLHUP |
發生掛起 |
POLLNVAL |
描述字不是一個打開的文件 |
2) nfds:表示多少個poll,如果1個,就填入1
3) timeout:定時多少ms
返回值介紹:
返回值為0:表示超時或者fd文件描述符無法打開
返回值為 -1:表示錯誤
返回值為>0時 :就是以下幾個常量
常量 |
說明 |
POLLIN |
普通或優先級帶數據可讀 |
POLLRDNORM |
normal普通數據可讀 |
POLLRDBAND |
優先級帶數據可讀 |
POLLPRI |
Priority高優先級數據可讀 |
POLLOUT |
普通數據可寫 |
POLLWRNORM |
normal普通數據可寫 |
POLLWRBAND |
band優先級帶數據可寫 |
POLLERR |
發生錯誤 |
POLLHUP |
發生掛起 |
POLLNVAL |
描述字不是一個打開的文件 |
最終改進的測試代碼如下:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <string.h> #include <poll.h> //添加poll頭文件 /*useg: thirdtext */ int main(int argc,char **argv) { int fd,ret; unsigned int val=0; struct pollfd fds; //定義poll文件描述結構體 fd=open("/dev/buttons",O_RDWR);
if(fd<0) {printf("can't open!!!\n"); return -1;} fds.fd=fd; fds.events= POLLIN; //請求類型是 普通或優先級帶數據可讀 while(1) { ret=poll(&fds,1,5000) ; //一個poll, 定時5000ms,進入休眠狀態 if(ret==0) //超時 { printf("time out \r\n"); } else if(ret>0) //poll機制被喚醒,表示有數據可讀 { read(fd,&val,1); //讀取一個值 printf("key_val=0X%x\r\n",val); } } return 0; }
效果如下:
若5S沒有數據,則打印time out
下節開始學習——使用異步通知來通知信號