高級輪詢技術——/dev/poll接口與kqueue

寫在轉載之前的:

在nginx源碼src/event/ngx_event.h中涉及了好幾種網絡模型:

windows select
windows IOCP
select
poll
epoll
devpoll
kqueue
eventport
廢棄的:
glibc aio
rtsig

可以說不同的系統,使用的網絡模型不盡相同,是時候對它們總結一下了。

以下是轉載內容:

這裏介紹兩種機制,它們跟select和poll這兩個函數具備類似的特性。

/dev/poll接口

Solaris上名爲/dev/poll的特殊文件提供了一個可擴展的輪詢大量描述符的方法。select和poll存在的一個問題是,每次調用它們都得傳遞待查詢的文件描述符。輪詢設備能在調用之間維持狀態,因此輪詢進程可以預先設置好待查詢描述符的列表,然後進入一個循環等待事件發生,每次循環回來時不必再次設置該列表。

打開/dev/poll之後,輪詢進程必須先初始化一個pollfd結構(即poll函數使用的結構,不過本機制不使用其中的revents成員)數組,再調用write往/dev/poll設備上寫這個結構數組以把它傳遞給內核,然後執行DP_POLL命令阻塞自身以等待事件發生。

傳遞給ioctl調用的結構如下:

struct dvpoll {

    struct pollfd     *dp_fds;

    int                   dp_nfds;

    int                   dp_timeout;

};

其中dp_fds成員指向一個緩衝區,供ioctl在返回時存放一個pollfd結構數組。dp_nfds成員指定該緩衝區。ioctl調用將一直阻塞到任何一個被輪詢描述符上發生所關心的事件,或者流逝時間超過經由dp_timeout成員指定的毫秒數爲止。dp_timeout指定爲0將導致ioctl立即返回,從而提供了使用本接口的非阻塞手段。dp_timeout指定爲-1表示沒有超時設置。

以下利用/dev/poll機制對函數str_cli進行改編。

#include "unp.h"

#include 

void

str_cli(FILE *fp, int sockfd)

{

    int stdineof;

    char buf[MAXLINE];

    int n;

    int wfd;

    struct pollfd pollfd[2];

    struct dvpoll dopoll;

    int i;

    int result;

    wfd = open("/dev/poll", O_RDWR, 0);

    pollfd[0].fd = fileno(fp);

    pollfd[0].events = POLLIN;

    pollfd[0].revents = 0;

    pollfd[1].fd = sockfd;

    pollfd[1].events = POLLIN;

    pollfd[1].revents = 0;

    write(wfd, pollfd, sizeof(struct pollfd) * 2);

    stdineof = 0;

    for ( ; ; ) {

        dopoll.dp_timeout = -1;

        dopoll.dp_nfds = 2;

        dopoll.dp_fds = pollfd;

        result = ioctl(wfd, DP_POLL, &dopoll);

        for (i = 0; i < result; i++) {

            if (dopoll.dp_fds[i].fd == sockfd) {

                if ( (n = read(sockfd, buf, MAXLINE)) == 0) {

                    if (stdineof == 1)

                        return; 

                    else

                        err_quit("str_cli: server terminated prematurely");

                }

                write(fileno(stdout), buf, n);

            }

            else {

                if ((n = read(fileno(fp), buf, MAXLINE)) == 0) {

                    stdineof = 1;

                    shutdown(sockfd, SHUT_WR);     

                    continue;

                }

                write(sockfd, buf, n);

            }

        }

    }

}

kqueue接口

FreeBSD引入了kqueue接口。本接口允許進程向內核註冊描述所關注kqueue事件的事件過濾器。事件除了與select所關注類似的文件I/O和超時外,還有異步I/O、文件修改通知(例如文件被刪除或修改時發出的通知)、進程跟蹤(例如進程調用exit或fork時發出的通知)和信號處理。kqueue接口有如下兩個函數和一個宏。

#include 

#include 

#include 

int kqueue(void);

int kevent(int kq,const struct kevent *changelist, int nchanges,

       struct kevent *eventlist, int nevents,

       const struct timespec *timeout);

void EV_SET(struct kevent *kev,uintptr_t ident, short filter,

       u_short flags, u_int fflags, intptr_t data, void *udata);

kqueue函數返回一個新的kqueue描述符,用於後續的kevent調用中。kevent函數既用於註冊所關注的事件,也用於確定是否有所關注的事件發生。changelist和nchanges這兩個參數給出對所關注事件做出的更改,若無更改則分別取值NULL和0。如果nchanges不爲0,kevent函數就執行changelist數組中所請求的每個事件過濾器更改。其條件已經觸發的任何事件(包括剛在changelist中增設的那些事件)由kevent函數通過eventlist參數返回,它指向一個由nevents個元素構成的kevent結構數組。kevent函數在eventlist中返回的事件數目作爲函數返回值返回,0表示超時。超時通過timeout參數設置,其處理類似select:NULL阻塞進程,非0值timespec指定明確的超時值,0值timespec執行非阻塞事件檢查。注意,kevent使用的timespec結構不同於select使用的timeval結構,前者的分辨率爲納秒,後者的分辨率爲微秒。

kevent結構在頭文件中定義:

struct kevent {

    uintptr_t ident;     

    short filter;         

    u_short flags;      

    u_int fflags         

    intptr_t data;       

    void *udata;        

};

其中的flags成員在調用時指定過濾器更改行爲,在返回時額外給出條件,如下圖所示。


filter成員指定的過濾器類型如下圖所示。

以下利用kqueue機制對函數str_cli進行改編。

void

str_cli(FILE *fp, int sockfd)

{

    int kq, i, n, nev, stdineof = 0, isfile;

    char buf[MAXLINE];

    struct kevent kev[2];

    struct timespec ts;

    struct stat st;

    isfile = ((fstat(fileno(fp), &st) == 0) &&

    (st.st_mode & S_IFMT) == S_IFREG);

    EV_SET(&kev[0], fileno(fp), EVFILT_READ, EV_ADD, 0, 0, NULL);

    EV_SET(&kev[1], sockfd, EVFILT_READ, EV_ADD, 0, 0, NULL);

    kq = kqueue();

    ts.tv_sec = ts.tv_nsec = 0;

    kevent(kq, kev, 2, NULL, 0, &ts);

 

    for ( ; ; ) {

        nev = kevent(kq, NULL, 0, kev, 2, NULL);

 

        for (i = 0; i < nev; i++) {

            if (kev[i].ident == sockfd) { 

                if ( (n = read(sockfd, buf, MAXLINE)) == 0) {

                    if (stdineof == 1)

                        return; 

                    else

                        err_quit("str_cli: server terminated prematurely");

                }

                write(fileno(stdout), buf, n);

            }

 

            if (kev[i].ident == fileno(fp)) {  

                n = read(fileno(fp), buf, MAXLINE);

                if (n > 0)

                    write(sockfd, buf, n);

 

                if (n == 0 || (isfile && n == kev[i].data)) {

                    stdineof = 1;

                    shutdown(sockfd, SHUT_WR); 

                    kev[i].flags = EV_DELETE;

                    kevent(kq, &kev[i], 1, NULL, 0, &ts); 

                    continue;

                }

            }

        }

    }

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章