LDD3源碼分析之poll分析

編譯環境:Ubuntu 10.10linux

內核版本:2.6.32-38-generic-pae數組

LDD3源碼路徑:examples/scull/pipe.c  examples/scull/main.c數據結構

 

本文分析LDD3第6章的poll(輪詢)操做。要理解驅動程序中poll函數的做用和實現,必須先理解用戶空間中poll和select函數的用法。本文與前面的文章介紹的順序有所不一樣,首先分析測試程序,以此理解用戶空間中的poll和select函數的用法。而後再分析驅動程序怎樣對用戶空間的poll和select函數提供支持。socket

 

1、poll函數的使用ide

用戶態的poll函數用以監測一組文件描述符是否能夠執行指定的I/O操做,若是被監測的文件描述符都不能執行指定的I/O操做,則poll函數會阻塞,直到有文件描述符的狀態發生變化,能夠執行指定的I/O操做才解除阻塞。poll函數還能夠指定一個最長阻塞時間,若是時間超時,將直接返回。poll函數的函數原型以下:函數

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

要監測的文件描述符由第一個參數fds指定,它是一個struct pollfd數組,pollfd結構體定義以下:學習

struct pollfd {
  int fd; /* file descriptor */
  short events; /* requested events */
  short revents; /* returned events */
};

pollfd結構體的第一個成員fd是文件描述符,表明一個打開的文件。第二個成員events是一個輸入參數,用於指定poll監測哪些事件(如可讀、可寫等等)。第三個成員revents是一個輸出參數,由內核填充,指示對於文件描述符fd,發生了哪些事件(如可讀、可寫等等)。測試

poll函數的第二個參數nfds表明監測的文件描述符的個數,即fds數組的成員個數。ui

poll函數的第三個參數timeout表明阻塞時間(以毫秒爲單位),若是poll要求監測的事件沒有發生,則poll會阻塞最多timeout毫秒。若是timeout設置爲負數,則poll會一直阻塞,直到監測的事件發生。this

poll函數若是返回一個正數,表明內核返回了狀態(保存在pollfd.revents中)的文件描述符的個數。若是poll返回0,代表是由於超時而返回的。若是poll返回-1,代表poll調用出錯。

poll函數能夠監測哪些狀態(由pollfd.events指定),以及內核能夠返回哪些狀態(保存在pollfd.revents中),由下面的宏設定:

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設備的poll操做(內核態poll)的測試程序,該程序使用了咱們前面介紹的poll函數(用戶態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都是可寫的。可是由於沒有向其中寫入任何內容,因此讀的時候會阻塞住,由於測試程序設置最長阻塞時間爲10秒,因此10秒後解除阻塞退出,並打印」No data within ten seconds.」。

下面再次測試讀操做,由於設備中沒有內容,仍是阻塞住,可是在10秒鐘內,從另一個終端向/dev/scullpipe2寫入數據,這裏是寫入字符串」hello」,能夠看到,寫入數據後,測試程序解除阻塞,並打印」/dev/scullpipe2 is readable!」和」hello」字符串,這個過程以下圖所示:

   

 

 

2、select函數的使用

select函數的做用與poll函數相似,也是監測一組文件描述符是否準備好執行指定的I/O操做。若是沒有任何一個文件描述符能夠完成指定的操做,select函數會阻塞住。select函數能夠指定一個最長阻塞時間,若是超時,則直接返回。

select函數的函數原型以下:

int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);

select經過三個獨立的文件描述符集(fd_set)readfds,writefds,exceptfds指示監測哪些文件描述符,其中readfds中的文件描述符監測是否可進行非阻塞的讀操做。writefds數組中的文件描述符監測是否可進行非阻塞的寫操做。exceptfds數組中的文件描述符監測是否有異常(exceptions)。

select的第一個參數nfds是三個文件描述符集readfds、writefds、exceptfds中最大文件描述符值加1。

select的最後一個參數timeout表明最長阻塞時間。

select函數返回知足指定I/O要求的文件描述符的個數,若是是超時退出的,select函數返回0。若是出錯,select函數返回-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函數實現的測試驅動中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;    
 
    /* Wait up to ten seconds. */
    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都是可寫的。可是由於沒有向其中寫入任何內容,因此讀的時候會阻塞住,由於測試程序設置最長阻塞時間爲10秒,因此10秒後解除阻塞退出,並打印」No data within ten seconds.」。

下面再次測試讀操做,由於設備中沒有內容,仍是阻塞住,可是在10秒鐘內,從另一個終端向/dev/scullpipe2寫入數據,這裏是寫入字符串」hello」,能夠看到,寫入數據後,測試程序解除阻塞,並打印」/dev/scullpipe2 is readable!」和」hello」字符串,這個過程以下圖所示:

   

 

 

 

3、驅動程序中poll操做的實現

用戶空間的poll和select函數,最終都會調用驅動程序中的poll函數,其函數原型以下:

unsigned int (*poll) (struct file *filp, poll_table *wait);

poll函數應該實現兩個功能:

一是把能標誌輪詢狀態變化的等待隊列加入到poll_table中,這經過調用poll_wait函數實現。

二是返回指示能進行的I/O操做的標誌位。

poll函數的第二個參數poll_table,是內核中用來實現poll,select系統調用的結構體,對於驅動開發者來講,沒必要關心其具體內容,能夠把poll_table當作是不透明的結構體,只要拿過來使用就能夠了。驅動程序經過poll_wait函數,把可以喚醒進程,改變輪詢狀態的等待隊列加入到poll_table中。該函數定義以下:

void poll_wait (struct file *, wait_queue_head_t *, poll_table *);

對於poll函數的第二個功能,返回的標誌位與用戶空間相對應,最經常使用的標誌位是POLLIN | POLLRDNORM和POLLOUT | POLLWRNORM,分別標誌可進行非阻塞的讀和寫操做。

下面看scullpipe設備對poll操做的實現,其內容其實很是簡單:

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    /*
234     * The buffer is circular; it is considered full
235     * if "wp" is right behind "rp" and empty if the
236     * two are equal.
237     */
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;    /* readable */
243    if (spacefree(dev))
244        mask |= POLLOUT | POLLWRNORM;   /* writable */
245    up(&dev->sem);
246    return mask;
247}

第239行,調用poll_wait函數將讀等待隊列加入到poll_table中。

第240行,調用poll_wait函數將寫等待隊列加入到poll_table中。

241 - 242行,若是有內容可讀,設置可讀標誌位。

243 - 244行,若是有空間可寫,設置可寫標誌位。

246行,將標誌位返回。

驅動程序中的poll函數很簡單,那麼內核是怎麼實現poll和select系統調用的呢?當用戶空間程序調用poll和select函數時,內核會調用由用戶程序指定的所有文件的poll方法,並向它們傳遞同一個poll_table結構。poll_table結構實際上是一個生成」實際數據結構」的函數(名爲poll_queue_proc)的封裝,這個函數poll_queue_proc,不一樣的應用場景,內核有不一樣的實現,這裏咱們不仔細研究對應的函數。對於poll和select系統調用來講,這個」實際數據結構」是一個包含poll_table_entry結構的內存頁鏈表。這裏提到的相關數據結構在linux-2.6.32-38源碼中,定義在include/linux/poll.h文件中,代碼以下所示:

 30/*
 31 * structures and helpers for f_op->poll implementations
 32 */
 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; /* all events enabled */
 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操做咱們就分析完了,內核要求驅動程序作的事並很少,可是咱們在學習時,要把poll操做和前面介紹的阻塞型read/write函數以及scullpipe設備的等待隊列等結合起來考慮,由於scullpipe設備是一個完整的程序模塊。

相關文章
相關標籤/搜索