poll() | 多路复用 I/O
和 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() 函数被唤醒的原因,这些宏常数如下:
events 中使用该宏常数,能够在折本文件的可读情况下,结束 poll() 函数。相反,revents 上使用该宏常数,在检查 poll() 函数结束后,可依此判断设备文件是否处于可读状态(即使消息长度是 0)。
在 events 域中使用该宏常数,能够在设备文件的高优先级数据读取状态下,结束 poll() 函数。相反,revents 上使用该宏常数,在检查 poll() 函数结束后,可依此判断设备文件是否处于可读高优先级数据的状态(即使消息长度是 0)。该宏常数用于处理网络信息包(packet) 的数据传递。
在 events 域中使用该宏常数,能够在设备文件的写入状态下,结束 poll() 函数。相反,revents 域上使用该宏常数,在检查 poll() 结束后,可依此判断设备文件是否处于可写状态。
在 events 域中使用该宏常数,能够在设备文件上发生错误时,结束 poll() 函数。相反,revents 域上使用该宏函数,在检查 poll() 函数结束后,可依此判断设备文件是否出错。
在 events 域中使用该宏常数,能够在设备文件中发生 hungup 时,结束 poll() 函数 。相反,在检查 poll() 结束后,可依此判断设备文件是否发生 hungup 。
在 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./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 时间。
|
|
|