作者:劉昊昱
博客:http://blog.csdn.net/liuhaoyutz
編譯環(huán)境:Ubuntu 10.10
內(nèi)核版本:2.6.32-38-generic-pae
LDD3源碼路徑:examples/scull/pipe.c examples/scull/main.c
本文分析LDD3第6章的poll(輪詢)操作。要理解驅(qū)動程序中poll函數(shù)的作用和實現(xiàn),,必須先理解用戶空間中poll和select函數(shù)的用法,。本文與前面的文章介紹的順序有所不同,首先分析測試程序,,以此理解用戶空間中的poll和select函數(shù)的用法,。然后再分析驅(qū)動程序怎樣對用戶空間的poll和select函數(shù)提供支持。
一,、poll函數(shù)的使用
用戶態(tài)的poll函數(shù)用以監(jiān)測一組文件描述符是否可以執(zhí)行指定的I/O操作,,如果被監(jiān)測的文件描述符都不能執(zhí)行指定的I/O操作,則poll函數(shù)會阻塞,,直到有文件描述符的狀態(tài)發(fā)生變化,,可以執(zhí)行指定的I/O操作才解除阻塞。poll函數(shù)還可以指定一個最長阻塞時間,,如果時間超時,,將直接返回。poll函數(shù)的函數(shù)原型如下:
- int poll(struct pollfd *fds, nfds_t nfds, int timeout);
要監(jiān)測的文件描述符由第一個參數(shù)fds指定,,它是一個struct pollfd數(shù)組,,pollfd結(jié)構(gòu)體定義如下:
- struct pollfd {
- int fd;
- short events;
- short revents;
- };
pollfd結(jié)構(gòu)體的第一個成員fd是文件描述符,代表一個打開的文件,。第二個成員events是一個輸入?yún)?shù),,用于指定poll監(jiān)測哪些事件(如可讀、可寫等等),。第三個成員revents是一個輸出參數(shù),,由內(nèi)核填充,指示對于文件描述符fd,,發(fā)生了哪些事件(如可讀,、可寫等等)。
poll函數(shù)的第二個參數(shù)nfds代表監(jiān)測的文件描述符的個數(shù),,即fds數(shù)組的成員個數(shù),。
poll函數(shù)的第三個參數(shù)timeout代表阻塞時間(以毫秒為單位),,如果poll要求監(jiān)測的事件沒有發(fā)生,則poll會阻塞最多timeout毫秒,。如果timeout設(shè)置為負數(shù),,則poll會一直阻塞,直到監(jiān)測的事件發(fā)生,。
poll函數(shù)如果返回一個正數(shù),,代表內(nèi)核返回了狀態(tài)(保存在pollfd.revents中)的文件描述符的個數(shù)。如果poll返回0,,表明是因為超時而返回的,。如果poll返回-1,表明poll調(diào)用出錯,。
poll函數(shù)可以監(jiān)測哪些狀態(tài)(由pollfd.events指定),,以及內(nèi)核可以返回哪些狀態(tài)(保存在pollfd.revents中),由下面的宏設(shè)定:
POLLIN:There is data to read.
POLLOUT:Writing now will not block.
POLLPRI:There
is urgent data to read (e.g., out-of-band data on TCP socket;
pseudo-terminal master in packet mode has seen state change in slave).
POLLRDHUP:
(since Linux 2.6.17) Stream socket peer closed connection, or shut down
writing half of connection. The _GNU_SOURCE feature test macro must be
defined
in order to obtain this definition.
POLLERR:Error condition (output only).
POLLHUP:Hang up (output only).
POLLNVAL:Invalid request: fd not open (output only).
When compiling with
_XOPEN_SOURCE defined, one also has the following, which convey no
further information beyond the bits listed above:
POLLRDNORM:Equivalent to POLLIN.
POLLRDBAND:Priority band data can be read (generally unused on Linux).
POLLWRNORM:Equivalent to POLLOUT.
POLLWRBAND:Priority data may be written.
下面我們看一個測試scullpipe設(shè)備的poll操作(內(nèi)核態(tài)poll)的測試程序,,該程序使用了我們前面介紹的poll函數(shù)(用戶態(tài)poll),。其代碼如下:
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <fcntl.h>
- #include <unistd.h>
- #include <linux/poll.h>
- #include <sys/time.h>
- #include <sys/types.h>
- #include <sys/stat.h>
-
- int main(int argc, char *argv[])
- {
- int fd0, fd1, fd2, fd3;
- struct pollfd poll_fd[4];
- char buf[100];
- int retval;
-
- if(argc != 2 || ((strcmp(argv[1], "read") != 0) && (strcmp(argv[1], "write") != 0)))
- {
- printf("usage: ./poll_test read|write\n");
- return -1;
- }
-
- fd0 = open("/dev/scullpipe0", O_RDWR);
- if ( fd0 < 0)
- {
- printf("open scullpipe0 error\n");
- return -1;
- }
-
- fd1 = open("/dev/scullpipe1", O_RDWR);
- if ( fd1 < 0)
- {
- printf("open scullpipe1 error\n");
- return -1;
- }
-
- fd2 = open("/dev/scullpipe2", O_RDWR);
- if ( fd2 < 0)
- {
- printf("open scullpipe2 error\n");
- return -1;
- }
-
- fd3 = open("/dev/scullpipe3", O_RDWR);
- if ( fd3 < 0)
- {
- printf("open scullpipe3 error\n");
- return -1;
- }
-
- if(strcmp(argv[1], "read") == 0)
- {
- poll_fd[0].fd = fd0;
- poll_fd[1].fd = fd1;
- poll_fd[2].fd = fd2;
- poll_fd[3].fd = fd3;
-
- poll_fd[0].events = POLLIN | POLLRDNORM;
- poll_fd[1].events = POLLIN | POLLRDNORM;
- poll_fd[2].events = POLLIN | POLLRDNORM;
- poll_fd[3].events = POLLIN | POLLRDNORM;
-
- retval = poll(poll_fd, 4, 10000);
- }
- else
- {
- poll_fd[0].fd = fd0;
- poll_fd[1].fd = fd1;
- poll_fd[2].fd = fd2;
- poll_fd[3].fd = fd3;
-
- poll_fd[0].events = POLLOUT | POLLWRNORM;
- poll_fd[1].events = POLLOUT | POLLWRNORM;
- poll_fd[2].events = POLLOUT | POLLWRNORM;
- poll_fd[3].events = POLLOUT | POLLWRNORM;
-
- retval = poll(poll_fd, 4, 10000);
- }
-
- if (retval == -1)
- {
- printf("poll error!\n");
- return -1;
- }
- else if (retval)
- {
- if(strcmp(argv[1], "read") == 0)
- {
- if(poll_fd[0].revents & (POLLIN | POLLRDNORM))
- {
- printf("/dev/scullpipe0 is readable!\n");
- memset(buf, 0, 100);
- read(fd0, buf, 100);
- printf("%s\n", buf);
- }
-
- if(poll_fd[1].revents & (POLLIN | POLLRDNORM))
- {
- printf("/dev/scullpipe1 is readable!\n");
- memset(buf, 0, 100);
- read(fd1, buf, 100);
- printf("%s\n", buf);
- }
-
- if(poll_fd[2].revents & (POLLIN | POLLRDNORM))
- {
- printf("/dev/scullpipe2 is readable!\n");
- memset(buf, 0, 100);
- read(fd2, buf, 100);
- printf("%s\n", buf);
- }
-
- if(poll_fd[3].revents & (POLLIN | POLLRDNORM))
- {
- printf("/dev/scullpipe3 is readable!\n");
- memset(buf, 0, 100);
- read(fd3, buf, 100);
- printf("%s\n", buf);
- }
- }
- else
- {
- if(poll_fd[0].revents & (POLLOUT | POLLWRNORM))
- {
- printf("/dev/scullpipe0 is writable!\n");
- }
-
- if(poll_fd[1].revents & (POLLOUT | POLLWRNORM))
- {
- printf("/dev/scullpipe1 is writable!\n");
- }
-
- if(poll_fd[2].revents & (POLLOUT | POLLWRNORM))
- {
- printf("/dev/scullpipe2 is writable!\n");
- }
-
- if(poll_fd[3].revents & (POLLOUT | POLLWRNORM))
- {
- printf("/dev/scullpipe3 is writable!\n");
- }
- }
- }
- else
- {
- if(strcmp(argv[1], "read") == 0)
- {
- printf("No data within ten seconds.\n");
- }
- else
- {
- printf("Can not write within ten seconds.\n");
- }
- }
-
- return 0;
- }
測試過程如下圖所示:
從上圖可以看出,scullpipe0 - scullpipe3都是可寫的,。但是因為沒有向其中寫入任何內(nèi)容,,所以讀的時候會阻塞住,因為測試程序設(shè)置最長阻塞時間為10秒,,所以10秒后解除阻塞退出,,并打印”No
data within ten seconds.”。
下面再次測試讀操作,,因為設(shè)備中沒有內(nèi)容,,還是阻塞住,但是在10秒鐘內(nèi),,從另外一個終端向/dev/scullpipe2寫入數(shù)據(jù),,這里是寫入字符串”hello”,可以看到,,寫入數(shù)據(jù)后,測試程序解除阻塞,,并打印”/dev/scullpipe2
is readable!”和”hello”字符串,,這個過程如下圖所示:
二、select函數(shù)的使用
select函數(shù)的作用與poll函數(shù)類似,,也是監(jiān)測一組文件描述符是否準備好執(zhí)行指定的I/O操作,。如果沒有任何一個文件描述符可以完成指定的操作,select函數(shù)會阻塞住,。select函數(shù)可以指定一個最長阻塞時間,,如果超時,則直接返回。
select函數(shù)的函數(shù)原型如下:
- int select(int nfds, fd_set *readfds, fd_set *writefds,
- fd_set *exceptfds, struct timeval *timeout);
select通過三個獨立的文件描述符集(fd_set)readfds,,writefds,,exceptfds指示監(jiān)測哪些文件描述符,其中readfds中的文件描述符監(jiān)測是否可進行非阻塞的讀操作,。writefds數(shù)組中的文件描述符監(jiān)測是否可進行非阻塞的寫操作,。exceptfds數(shù)組中的文件描述符監(jiān)測是否有異常(exceptions)。
select的第一個參數(shù)nfds是三個文件描述符集readfds,、writefds,、exceptfds中最大文件描述符值加1。
select的最后一個參數(shù)timeout代表最長阻塞時間,。
select函數(shù)返回滿足指定I/O要求的文件描述符的個數(shù),,如果是超時退出的,select函數(shù)返回0,。如果出錯,,select函數(shù)返回-1。
有四個宏用來操作文件描述符集:
void FD_ZERO(fd_set *set);
清空一個文件描述符集,。
void FD_SET(int fd, fd_set *set); 將一個文件描述符fd加入到指定的文件描述符集set中,。
void FD_CLR(int fd, fd_set *set); 將一個文件描述符fd從指定的文件描述符集set中刪除。
int FD_ISSET(int fd, fd_set *set); 測試文件描述符fd是否在指定的文件描述符集set中,。
下面我們看使用select函數(shù)實現(xiàn)的測試驅(qū)動中poll操作的測試程序,,其代碼如下:
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <fcntl.h>
- #include <unistd.h>
- #include <sys/time.h>
- #include <sys/types.h>
- #include <sys/stat.h>
-
- int main(int argc, char *argv[])
- {
- fd_set rfds, wfds;
- int fd0, fd1, fd2, fd3;
- char buf[100];
- int retval;
-
-
- struct timeval tv;
- tv.tv_sec = 10;
- tv.tv_usec = 0;
-
- if(argc != 2 || ((strcmp(argv[1], "read") != 0) && (strcmp(argv[1], "write") != 0)))
- {
- printf("usage: ./select_test read|write\n");
- return -1;
- }
-
- fd0 = open("/dev/scullpipe0", O_RDWR);
- if ( fd0 < 0)
- {
- printf("open scullpipe0 error\n");
- return -1;
- }
-
- fd1 = open("/dev/scullpipe1", O_RDWR);
- if ( fd1 < 0)
- {
- printf("open scullpipe1 error\n");
- return -1;
- }
-
- fd2 = open("/dev/scullpipe2", O_RDWR);
- if ( fd2 < 0)
- {
- printf("open scullpipe2 error\n");
- return -1;
- }
-
- fd3 = open("/dev/scullpipe3", O_RDWR);
- if ( fd3 < 0)
- {
- printf("open scullpipe3 error\n");
- return -1;
- }
-
- if(strcmp(argv[1], "read") == 0)
- {
- FD_ZERO(&rfds);
- FD_SET(fd0, &rfds);
- FD_SET(fd1, &rfds);
- FD_SET(fd2, &rfds);
- FD_SET(fd3, &rfds);
- retval = select(fd3 + 1, &rfds, NULL, NULL, &tv);
- }
- else
- {
- FD_ZERO(&wfds);
- FD_SET(fd0, &wfds);
- FD_SET(fd1, &wfds);
- FD_SET(fd2, &wfds);
- FD_SET(fd3, &wfds);
- retval = select(fd3 + 1, NULL, &wfds, NULL, &tv);
- }
-
- if (retval == -1)
- {
- printf("select error!\n");
- return -1;
- }
- else if (retval)
- {
- if(strcmp(argv[1], "read") == 0)
- {
- if(FD_ISSET(fd0, &rfds))
- {
- printf("/dev/scullpipe0 is readable!\n");
- memset(buf, 0, 100);
- read(fd0, buf, 100);
- printf("%s\n", buf);
- }
-
- if(FD_ISSET(fd1, &rfds))
- {
- printf("/dev/scullpipe1 is readable!\n");
- memset(buf, 0, 100);
- read(fd1, buf, 100);
- printf("%s\n", buf);
- }
-
- if(FD_ISSET(fd2, &rfds))
- {
- printf("/dev/scullpipe2 is readable!\n");
- memset(buf, 0, 100);
- read(fd2, buf, 100);
- printf("%s\n", buf);
- }
-
- if(FD_ISSET(fd3, &rfds))
- {
- printf("/dev/scullpipe3 is readable!\n");
- memset(buf, 0, 100);
- read(fd3, buf, 100);
- printf("%s\n", buf);
- }
- }
- else
- {
- if(FD_ISSET(fd0, &wfds))
- {
- printf("/dev/scullpipe0 is writable!\n");
- }
-
- if(FD_ISSET(fd1, &wfds))
- {
- printf("/dev/scullpipe1 is writable!\n");
- }
-
- if(FD_ISSET(fd2, &wfds))
- {
- printf("/dev/scullpipe2 is writable!\n");
- }
-
- if(FD_ISSET(fd3, &wfds))
- {
- printf("/dev/scullpipe3 is writable!\n");
- }
- }
- }
- else
- {
- if(strcmp(argv[1], "read") == 0)
- {
- printf("No data within ten seconds.\n");
- }
- else
- {
- printf("Can not write within ten seconds.\n");
- }
- }
-
- return 0;
- }
測試過程如下圖所示:
從上圖可以看出,scullpipe0 - scullpipe3都是可寫的,。但是因為沒有向其中寫入任何內(nèi)容,,所以讀的時候會阻塞住,因為測試程序設(shè)置最長阻塞時間為10秒,,所以10秒后解除阻塞退出,,并打印”No
data within ten seconds.”。
下面再次測試讀操作,,因為設(shè)備中沒有內(nèi)容,,還是阻塞住,但是在10秒鐘內(nèi),,從另外一個終端向/dev/scullpipe2寫入數(shù)據(jù),,這里是寫入字符串”hello”,可以看到,,寫入數(shù)據(jù)后,,測試程序解除阻塞,并打印”/dev/scullpipe2
is readable!”和”hello”字符串,,這個過程如下圖所示:
三,、驅(qū)動程序中poll操作的實現(xiàn)
用戶空間的poll和select函數(shù),,最終都會調(diào)用驅(qū)動程序中的poll函數(shù),其函數(shù)原型如下:
- unsigned int (*poll) (struct file *filp, poll_table *wait);
poll函數(shù)應該實現(xiàn)兩個功能:
一是把能標志輪詢狀態(tài)變化的等待隊列加入到poll_table中,,這通過調(diào)用poll_wait函數(shù)實現(xiàn),。
二是返回指示能進行的I/O操作的標志位。
poll函數(shù)的第二個參數(shù)poll_table,,是內(nèi)核中用來實現(xiàn)poll,,select系統(tǒng)調(diào)用的結(jié)構(gòu)體,對于驅(qū)動開發(fā)者來說,,不必關(guān)心其具體內(nèi)容,,可以把poll_table看成是不透明的結(jié)構(gòu)體,只要拿過來使用就可以了,。驅(qū)動程序通過poll_wait函數(shù),,把能夠喚醒進程,改變輪詢狀態(tài)的等待隊列加入到poll_table中,。該函數(shù)定義如下:
- void poll_wait (struct file *, wait_queue_head_t *, poll_table *);
對于poll函數(shù)的第二個功能,,返回的標志位與用戶空間相對應,最常用的標志位是POLLIN | POLLRDNORM和POLLOUT | POLLWRNORM,,分別標志可進行非阻塞的讀和寫操作,。
下面看scullpipe設(shè)備對poll操作的實現(xiàn),其內(nèi)容其實非常簡單:
- 228static unsigned int scull_p_poll(struct file *filp, poll_table *wait)
- 229{
- 230 struct scull_pipe *dev = filp->private_data;
- 231 unsigned int mask = 0;
- 232
- 233
-
-
-
-
- 238 down(&dev->sem);
- 239 poll_wait(filp, &dev->inq, wait);
- 240 poll_wait(filp, &dev->outq, wait);
- 241 if (dev->rp != dev->wp)
- 242 mask |= POLLIN | POLLRDNORM;
- 243 if (spacefree(dev))
- 244 mask |= POLLOUT | POLLWRNORM;
- 245 up(&dev->sem);
- 246 return mask;
- 247}
第239行,,調(diào)用poll_wait函數(shù)將讀等待隊列加入到poll_table中,。
第240行,調(diào)用poll_wait函數(shù)將寫等待隊列加入到poll_table中,。
241 - 242行,,如果有內(nèi)容可讀,設(shè)置可讀標志位,。
243 - 244行,,如果有空間可寫,設(shè)置可寫標志位,。
246行,,將標志位返回。
驅(qū)動程序中的poll函數(shù)很簡單,,那么內(nèi)核是怎么實現(xiàn)poll和select系統(tǒng)調(diào)用的呢,?當用戶空間程序調(diào)用poll和select函數(shù)時,內(nèi)核會調(diào)用由用戶程序指定的全部文件的poll方法,,并向它們傳遞同一個poll_table結(jié)構(gòu)。poll_table結(jié)構(gòu)其實是一個生成”實際數(shù)據(jù)結(jié)構(gòu)”的函數(shù)(名為poll_queue_proc)的封裝,,這個函數(shù)poll_queue_proc,,不同的應用場景,,內(nèi)核有不同的實現(xiàn),這里我們不仔細研究對應的函數(shù),。對于poll和select系統(tǒng)調(diào)用來說,,這個”實際數(shù)據(jù)結(jié)構(gòu)”是一個包含poll_table_entry結(jié)構(gòu)的內(nèi)存頁鏈表。這里提到的相關(guān)數(shù)據(jù)結(jié)構(gòu)在linux-2.6.32-38源碼中,,定義在include/linux/poll.h文件中,,代碼如下所示:
- 30
-
-
- 33typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);
- 34
- 35typedef struct poll_table_struct {
- 36 poll_queue_proc qproc;
- 37 unsigned long key;
- 38} poll_table;
- 39
- 40static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
- 41{
- 42 if (p && wait_address)
- 43 p->qproc(filp, wait_address, p);
- 44}
- 45
- 46static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
- 47{
- 48 pt->qproc = qproc;
- 49 pt->key = ~0UL;
- 50}
- 51
- 52struct poll_table_entry {
- 53 struct file *filp;
- 54 unsigned long key;
- 55 wait_queue_t wait;
- 56 wait_queue_head_t *wait_address;
- 57};
poll操作我們就分析完了,內(nèi)核要求驅(qū)動程序做的事并不多,,但是我們在學習時,,要把poll操作和前面介紹的阻塞型read/write函數(shù)以及scullpipe設(shè)備的等待隊列等結(jié)合起來考慮,因為scullpipe設(shè)備是一個完整的程序模塊,。