我爱编程

linux系统编程-day08-文件IO(3)

2018-06-04  本文已影响0人  桔子满地

I/O多路复用

应用程序常常需要在多于一个文件描述符上阻塞。在不使用线程,尤其是独立处理每一个文件的情况下,进程无法在多个文件描述符上同时阻塞。尤其是对于网络应用程序而言,同时打开的多个套接字,会诱发潜在的问题。
非阻塞IO可以作为这个问题的一个解决方案,但这种方法效率较差。首先,进程要以某种不确定的方式不断发起I/O操作,直到某个打开的文件描述符准备好进行I/O。其次,如果程序可以睡眠的话将更加有效,可以让处理器进行其他工作,直到一个或更多文件描述符可以进行I/O时再唤醒。

I/O多路复用允许应用在多个文件描述符上同时阻塞,并在其中某个可以读写时收到通知。
这时I/O多路复用就成了应用的关键所在,一般来讲I/O多路复用的设计遵循以下原则:

  1. I/O多路复用:当任何文件描述符准备好I/O时告诉我
  2. 在一个或更多文件描述符就绪前始终处于睡眠状态
  3. 唤醒:哪个准备好了?
  4. 在不阻塞的情况下处理所有I/O就绪的文件描述符。
  5. 返回第一步,重新开始。
    Linux提供了三种I/O多路复用方案:select, poll和epoll。其中epoll是Linux特有的高级方法。
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

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

在指定的文件描述符准备好I/O之前或者超过一定的时间限制,select( )调用就会阻塞。
监测的文件描述符可以分为三类,分别等待不同的时间。

如果指定的集合为NULL,则seletc( )不对此类时间进行监视。
成功返回时,每个集合只包含对应类型的I/O就绪的文件描述符。
第一个参数n,等于所有集合中文件描述符的最大值加一。这样,select( )的调用者需要找到最大的文件描述符值,并将其加一后传给第一个参数。
最后的timeout参数是一个指向timeval结构体的指针,定义如下:

#include <sys/time.h>
struct timeval {
    long tv_sec; /* seconds */
    long tv_usec; /*  microseconds */
};

如果这个参数不是NULL,即时此时没有文件描述符处于I/O就绪状态,select( )调用也将在tv_sec秒tv_usec微秒后返回。返回时,这个结构图的状态是未定义的。

三个集合中的fds并不直接操作,而是通过以下辅助来进行管理:

fd_set writefds;
FD_ZERO(&writefds);

FD_SET(fd, &writefds);

FD_CLR(fd, &writefds);

if (FD_ISSET(fd, &readfds))
/* 'fd' is readable without blocking! */

用select( )实现可移植的sleep( )

由于select( )在各种Unix系统中都很容易实现,相对于微妙级精度的睡眠机制来讲,经常将select( )做为一种可移植的微秒级的睡眠机制。

struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 500;
/* sleep for 500 microseconds */
 select(0, NULL, NULL, NULL, &tv);
pselect( )

POSIX定义了自己的方法--pselect( ):

#define _XOPEN_SOURCE 600
#include <sys/select.h>
int pselect(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask);

四个宏定义与select的相同。
pselect( )和select( )有三点不同:

  1. pselect( )的timeout参数使用了timespec结构。timespec使用秒和纳秒,而select的timeval使用秒和豪秒。理论上timespec更精准一些。
  2. pselect( )调用并不修改timeout参数。
  3. pselect( )比select( )多一个sigmask参数。当这个参数被设置为零时,pselect( )的行为等同于select( ).

poll( )

poll( )系统调用是system V的I/O多路复用解决方案。

#include <sys/poll.h>
int poll(struct pollfd *fds, unsigned int nfds, int timeout);

poll( )使用一个简单的nfds个pollfd结构体构成的数组,fds指向该数组

#include <sys/poll.h>
struct pollfd {
  int fd; /* file descriptor */
  short events; /* requested events to watch */
  short revents;  /* returned events witnessed */
};

每个pollfd结构体指定监视单一的文件描述符。可以传递多个结构体,使得poll( )监视多个文件描述符。

下面是合法的事件:

另外,如下事件可能在revents中返回:

POLLIN|POLLPRI等价于select( )的读事件,而POLLOUT|POLLWRBAND等价于select( )的写事件。POLLIN等价于POLLRDNORM|POLLRDBAND, 而POLLOUT等价于POLLWRNORM。

ppoll( ):

Linux提供了一个poll( )的近似调用——ppoll( )。ppoll( )和pselect( )同源,然而和pselect( )不同的是,ppoll( )是Linux的专有调用:

#define _GNU_SOURCE
#include <sys/poll.h>
int ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *timeout, const sigset_t *sigmask);

像pselect( )那样,timeout参数以秒和纳秒计指定了时限,而sigmask参数提供了一组等待处理的信号。

对比poll( )与select( ):

一般来说,poll( )系统调用优于select( ):

上一篇 下一篇

猜你喜欢

热点阅读