曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 6341|回复: 2
打印 上一主题 下一主题

[文件I/O] poll() | 多路复用 I/O

[复制链接]

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
跳转到指定楼层
楼主
发表于 2009-10-14 10:57:12 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
和 select() 函数一样,poll() 函数也可以用于执行多路复用 I/O 。但 poll() 与 slect()相比,用起来更加直观容易。使用该函数,需要包含 #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 是否已经无效。

在使用中,poll 中的第 1 个参数 __fds 一般为 struct pollfd 的结构数组;而第 2 个参数 __nfds 指出 __fds 中的成员个数。

和 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。这里也可能会因为某个信号而中断该等待。

对于任意一个文件描述符如果其上没有一个感兴趣的事件发生(无错误),那么 poll() 就会被阻塞直到有感兴趣的事件发生为止。和 select 一样,文件描述符是否阻塞对 poll 是否阻塞没有任何影响。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
沙发
 楼主| 发表于 2009-10-14 14:54:02 | 只看该作者

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 。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
板凳
 楼主| 发表于 2009-10-15 14:51:38 | 只看该作者

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 时间。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2025-5-3 23:36 , Processed in 0.076263 second(s), 21 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表