Poll() | 多路复用 I/O

和 select() 函数一样,poll() 函数也可以用于执行多路复用 I/O 。但 poll() 与 select()相比,用起来更加直观容易。使用该函数,需要包含 #include <poll.h>文件,实际上最终包含的是 <sys/poll.h>文件,poll.h 里的内容也就是 #include <sys/poll.h> 。


函数的原型:
引用
#include <poll.h>
extern int poll (struct pollfd *__fds, nfds_t __nfds, int __timeout);


poll() 没有像 select() 构建 fd_set 结构体的 3 个数组 ( 针对每个条件分别有一个数组 : 可读性、可写性和错误条件 ) ,然后检查从 0 到 nfds 每个文件描述符。

第一个参数 pollfd 结构体定义如下:
引用
/* Data structure describing a polling request.  */
struct pollfd
  {
    int fd;                     /* poll 的文件描述符.  */
    short int events;           /* fd 上感兴趣的事件(等待的事件).  */
    short int revents;          /* fd 上实际发生的事件.  */
  };

fd 成员表示感兴趣的,且打开了的文件描述符;
events  成员是位掩码,用于指定针对这个文件描述符感兴趣的事件;
revents  成员是位掩码,用于指定当 poll 返回时,在该文件描述符上已经发生了哪些事情。

events 和 revents 结合下列常数值(宏)指定即将唤醒的事件或调查已结束的 poll() 函数被唤醒的原因,这些宏常数如下:
  • POLLIN
events 中使用该宏常数,能够在折本文件的可读情况下,结束 poll() 函数。相反,revents 上使用该宏常数,在检查 poll() 函数结束后,可依此判断设备文件是否处于可读状态(即使消息长度是 0)。

  • POLLPRI
在 events 域中使用该宏常数,能够在设备文件的高优先级数据读取状态下,结束 poll() 函数。相反,revents 上使用该宏常数,在检查 poll() 函数结束后,可依此判断设备文件是否处于可读高优先级数据的状态(即使消息长度是 0)。该宏常数用于处理网络信息包(packet) 的数据传递。

  • POLLOUT
在 events 域中使用该宏常数,能够在设备文件的写入状态下,结束 poll() 函数。相反,revents 域上使用该宏常数,在检查 poll() 结束后,可依此判断设备文件是否处于可写状态。

  • POLLERR
在 events 域中使用该宏常数,能够在设备文件上发生错误时,结束 poll() 函数。相反,revents 域上使用该宏函数,在检查 poll() 函数结束后,可依此判断设备文件是否出错。

  • POLLHUP
在 events 域中使用该宏常数,能够在设备文件中发生 hungup 时,结束 poll() 函数 。相反,在检查 poll() 结束后,可依此判断设备文件是否发生 hungup 。

  • POLLNVAL
在 events 域中使用该宏函数,能够在文件描述符的值无效时,结束 poll() 。相反,在 revents 域上使用该宏函数时,在检查 poll() 函数后,文件描述符是否有效。可用于处理网络信息时,检查 socket handler 是否已经无效。

和 select 一样,最后一个参数 timeout 指定 poll() 将在超时前等待一个事件多长事件。这里有 3 种情况:

1) timeout 为 -1
这会造成 poll 永远等待。poll() 只有在一个描述符就绪时返回,或者在调用进程捕捉到信号时返回(在这里,poll 返回 -1),并且设置 errno 值为 EINTR 。-1 可以用宏定义常量 INFTIM 来代替(在 pth.h 中有定义) 。

2) timeout 等于0
在这种情况下,测试所有的描述符,并且 poll() 立刻返回。这允许在 poll 中没有阻塞的情况下找出多个文件描述符的状态。

3) time > 0
这将以毫秒为单位指定 timeout 的超时周期。poll() 只有在超时到期时返回,除非一个描述符变为就绪,在这种情况下,它立刻返回。如果超时周期到齐,poll() 返回 0。这里也可能会因为某个信号而中断该等待。

和 select 一样,文件描述符是否阻塞对 poll 是否阻塞没有任何影响。
 
 
poll 使用样式示例
代码:
引用
#include <stdio.h>                                
#include <string.h>                               
#include <fcntl.h>                                
#include <termios.h>                              
#include <sys/time.h>                             
#include <sys/types.h>                            
#include <unistd.h>                               
#include <poll.h>                                 

int main (int argc, char **argv)
{                               
        int sfd1, sfd2, sfd3;   

        struct pollfd Events [3];
        int retval;              
        char buff [256];         
        int readcnt;             

        sfd1 = open ("/dev/ttyS1", O_RDWR | O_NOCTTY);
        sfd2 = open ("/dev/ttyS2", O_RDWR | O_NOCTTY);
        sfd3 = open ("/dev/ttyS3", O_RDWR | O_NOCTTY);

        ...
        /*各个串行环境设定程序*/
        ...                     

        memset (Event, 0, sizeof(Events));

        Event[0].fd = sfd1;
        Event[0].events = POLLIN | POLLERR;     /*关心读取和出错事件*/

        Event[1].fd = sfd2;
        Event[1].events = POLLIN | POLLERR;     /*关心读取和出错事件*/

        Event[2].fd = sfd3;
        Event[2].events = POLLIN | POLLERR;     /*关心读取和出错事件*/

        while (1) {
                /*等待事件*/
                retval = poll ((struct pollfd *)&Events, 3, 5000);
                if (retval < 0) {                                 
                        perror ("poll");
                        exit (EXIT_FAILURE);
                }
                if (retval == 0) {
                        prinntf ("no data in 5 seconds.\n");
                        continue;
                }

                for (i = 0; i < 3; i++) {
                        /*检查错误*/
                        if (Events[i].revents & POLLERR) {
                                printf ("device error!\n");
                                exit (EXIT_FAILURE);
                        }

                        /*检查是否存在传递的数据(可读)*/
                        if (Events[i].revents & POLLIN) {
                                readcnt = read (Events[i].fd, buff, 256);
                                write (Events[i].fd, buff, readcnt);
                        }
                }
        }

        close (sfd1);
        close (sfd2);
        colse (sfd3);
}

程序说明:
sfd1 ,  sfd2,  sfd3  变量经过 open() 成功后,会记录各个串口的有效文件描述符,使用这些文件描述符来设置串行通信速的等适当的串口环境。

poll() 处理输入输出的复用。在 struct pollfd Events[3] 上设置了将要处理的事件以及事件发生的条件 --- POLLIN 和  POLLERR 。

在对 poll() 函数中相关参数初始化后,调用 poll() 函数等待感兴趣的事件(POLLIN 和 POLLERR) 的发生。在事件发生前,进程进入睡眠状态,并把相应进程的运行权限移交给其他的进程。如果发生了以上的注册事件,那么进程会重新运行,也就是说如果发生了可接收数据或因其出错时,那么 poll 的返回值 retval < 0 ;如果没有出错,那么可以利用返回值检查是发生了哪个感兴趣的事件。在没有发生任何事件时,由于使用了等待事件 5000ms ,在超时时,进程会被唤醒,此时返回值为 0 。
poll() 应用示例
在 http://www.groad.net/bbs/read.php?tid-950.html 里,使用了非阻塞文件描述符复制文件的例子。

现在用里面的程序重新读另外一个文本文件( 6.4M 大),从输出的 errors2 文件中可以看到:
引用
[beyes@localhost poll]$ cat errors2 |grep "errno = 11" | wc -l
2191

就是在这样一个紧密循环中,失败了 2191 多次,也就是说,执行了 2191 次不必要的 write 系统调用,浪费了许多 CPU 时间。

下面通过 poll() 的测试程序来测试一下性能的提升,测试代码如下:
引用
#include <stdarg.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
#include <syslog.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
                                                                                                                              

#define BUFFER_SIZE     1000000
#define LINE_LEN        256    

typedef enum {B_FALSE, B_TRUE} boolean_t;               /*定义布尔变量*/

static char buf [BUFFER_SIZE];
int daemon_proc = 0;          

int set_fsflag (int fd, int new_flags)
{                                     
        int flags;                    

        if ((flags = fcntl (fd, F_GETFL)) == -1)        /*得到 fd 上的状态*/
                return (-1);                                                

        flags |= new_flags;                             /*对 fd 添加新的属性*/

        if ((flags = fcntl (fd, F_SETFL, flags)) == -1) /*设置新属性*/
                return (-1);                                          

        return (0);
}                  

int clear_fsflag (int fd, int new_flags)
{                                       
        int flags;                      

        if ((flags = fcntl (fd, F_SETFL, flags)) == -1)
                return (-1);                           

        return (0);
}                  

static void err_common (boolean_t flag, int level, const char *text, va_list args)
{                                                                                 
        int old_errno;                                                            
        int n;                                                                    
        char buf [LINE_LEN];                                                      

        old_errno = errno;      /*获得错误号*/
#ifdef NEED_SNPRINTF                          
        n = vsprintf (buf, text, args);                 /*n 为写入到 buf 中的字节数,不包括'\0'*/
#else                                                                                           
        n = vsnprintf (buf, sizeof (buf), text, args);                                          
#endif                                                                                          
        if (flag)                                                                               
                snprintf (buf + n, sizeof (buf) - n, ": %s", strerror (old_errno));     /*附加出错具体提示*/
        strcat (buf, "\n");                                                                                 

        if (daemon_proc)
                syslog (level, buf);    /*产生日志消息*/
        else {                                          
                fflush (stdout);                        
                fprintf (stderr, "%s", buf);            
                fflush (stderr);                        
        }                                               
}                                                       

void log_msg (const char *text, ...)
{                                   
        va_list arg;                
        va_start (arg, text);       
        err_common (B_FALSE, LOG_INFO, text, arg);
        va_end (arg);                             
}                                                 

void err_msg (const char *text, ...)
{                                   
        va_list arg;                

        va_start (arg, text);
        err_common (B_TRUE, LOG_ERR, text, arg);
        va_end (arg);                           

        exit (1);
}                

void err_set (const char *text, ...)
{                                   
        va_list arg;                

        va_start (arg, text);
        err_common (B_TRUE, LOG_INFO, text, arg);
        va_end (arg);                            
}



int main (void)
{
        ssize_t n;
        ssize_t res;
        char *ptr;
        int errs;
        struct pollfd fds;

        errs = 0;
        n = read (STDIN_FILENO, buf, BUFFER_SIZE);
        log_msg ("Read %d bytes", n);

        set_fsflag (STDOUT_FILENO, O_NONBLOCK);

        fds.fd = STDOUT_FILENO;
        fds.events = POLLOUT;
        fds.revents = 0;

        ptr = buf;
        while (n > 0) {
                if (poll (&fds, 1, -1) == -1)
                        err_msg ("Can't poll");

                while ((n > 0) && ((res = write (STDOUT_FILENO, ptr, n)) > 0)) {
                        if (errs > 0) {
                                err_set ("write failed %d times\n", errs);
                                errs = 0;
                        }

                        log_msg ("Wrote %d bytes", res);
                        ptr += res;
                        n -= res;
                }
        }
        clear_fsflag (STDOUT_FILENO, O_NONBLOCK);

        return (0);
}


执行程序:
引用
[beyes@localhost poll]$ ./poll.exe < zypper.log 2> errors


不出错的话,输出读取到的 100,000 个字节内容。

查看生成的 errors 文件:
引用
[beyes@localhost poll]$ cat errors
Read 1000000 bytes                
Wrote 4059 bytes                  
Wrote 4061 bytes                  
Wrote 4061 bytes                  
Wrote 4062 bytes                  
Wrote 4064 bytes                  
Wrote 4071 bytes             
... ...


统计一下 errors 文件:
引用
[beyes@localhost poll]$ wc -l errors
247 errors


这里,errors 文件里的输出并不是表示是错误的提示,而是把每次写向标准输出的内容作为“出错”内容输出到标准错误中,然后导出到 errnos 文件中。从对 errors 文件的统计中可以看到,一共写了 247 次,终于写完了 100, 000 个字节。所以用 poll() 提升了不少的性能,节省了大量的 CPU 时间。

你可能感兴趣的:(struct,list,测试,buffer,events,structure)