美文网首页我爱编程
linux系统编程-day08-文件IO(3)

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

作者: 桔子满地 | 来源:发表于2018-06-04 15:39 被阅读0次

I/O多路复用

应用程序常常需要在多于一个文件描述符上阻塞。在不使用线程,尤其是独立处理每一个文件的情况下,进程无法在多个文件描述符上同时阻塞。尤其是对于网络应用程序而言,同时打开的多个套接字,会诱发潜在的问题。
非阻塞IO可以作为这个问题的一个解决方案,但这种方法效率较差。首先,进程要以某种不确定的方式不断发起I/O操作,直到某个打开的文件描述符准备好进行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特有的高级方法。
  • select( )
    select( )系统调用提供了一种实现同步I/O多路复用的机制:
#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( )调用就会阻塞。
监测的文件描述符可以分为三类,分别等待不同的时间。

  • 监测readfds集合中的fd,确认其中是否有可读数据(也就是说,读操作可以无阻塞的完成)
  • 检测writefds集合中的fd,确认其中是否有一个写操作可以不阻塞地完成
  • 监测exceptfds集合中的fd,确认其中是否有出现异常发生或者出现带外(out-of-band)数据(这种情况只适用于套接字)。

如果指定的集合为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微秒后返回。返回时,这个结构图的状态是未定义的。

  • 较新版本的Linux会自动将该值改为剩余的时间。
  • 如果时限中的两个值都是零,调用会立即返回,并报告调用时所有事件对应的文件描述符均不可用,且不等待任何后续事件。

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

  • FD_ZERO从指定的集合中移除所有文件描述符:

fd_set writefds;
FD_ZERO(&writefds);

  • FD_SET想指定集合中添加一个文件描述符

FD_SET(fd, &writefds);

  • FD_CLR从指定集合中移除一个文件描述符,设计良好的代码应该从不使用FD_CLR,一般来讲,很少使用该宏

FD_CLR(fd, &writefds);

  • FD_ISSET测试一个文件描述符在不在给定集合中。如果在,返回一个非0值,不在,返回0。一般在select( )调用返回后使用FD_ISSET来检查一个文件描述符是否就绪。

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

  • 由于文件描述符集合是静态建立的,所以对于文件描述符数量的上限和文件描述符的最大值均有限制。二者都由FD_SETSIZE设定。在Linux上,这个值是1024。
用select( )实现可移植的sleep( )

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

  • 该方法通过将三个集合值设为空NULL,将超时值设置为non-NULL来实现。
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( ).
  • 添加pselect( )到Unix工具箱的主要原因是为了增加sigmask参数,以此来解决信号和等待文件描述符之间的竞争关系。
  • pselect( )提供了一组可阻塞的信号,阻塞的信号直到解除阻塞才会被处理。
  • 如果不考虑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( )监视多个文件描述符。

  • events是要监视的fd事件的一组位掩码,用户设置这个字段
  • revents是发生在该fd上的事件的位掩码,内核在返回时设置这个字段

下面是合法的事件:

  • POLLIN:没有数据可读
  • POLLRDNORM:有正常数据可读
  • POLLRDBAND:有优先数据可读
  • POLLPRI:有高优先级数据可读
  • POLLOUT:写操作不会阻塞
  • POLLWRNORM:写正常数据不会阻塞
  • POLLBAND:写优先数据不会阻塞
  • POLLMSG:有一个SIGPOLL消息可用

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

  • POLLER:给出文件描述符上有错误
  • POLLHUP:文件描述符上有挂起事件
  • POLLNVAL:给出的文件描述符非法

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

  • 举例来说,监视一个文件描述符是否可读写,需设置events为POLLIN|POLLOUT。返回时,将在revents中检查是否有相应的标志。
  • timeout参数指定在任何I/O就绪前需要等待时间的长度,以毫秒计。负值表示永远等待。一个零值表示调用立即返回,列出所有未准备好的I/O,但不等待任何其他事件。这种情况下,poll( )就如图其名,轮询一次后立即返回。
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( ):

  • poll( )无需使用者计算最大的文件描述符值加一和传递该参数
  • poll( )应对较大值的fd时更具效率。
  • 使用poll( )可以创建合适大小的数组,只需要监视一项或仅仅传递一个结构体。
  • poll( )系统调用分离了输入events字段和输出revents字段,数组无需改变即而重用。
  • select的timeout参数在返回时是未定义的。可移植的代码需要重新初始化它。然而pselect没有这个问题。

相关文章

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

    I/O多路复用 应用程序常常需要在多于一个文件描述符上阻塞。在不使用线程,尤其是独立处理每一个文件的情况下,进程无...

  • day08-文件编辑vim2-笔记

    Day08-文件编辑vim2 导读 今日内容1.什么是用户2.windows系统和linux系统的用户有什么区别3...

  • Linux编程学习笔记 | Linux IO学习[2] - 标准

    在上一篇Linux编程学习笔记 | Linux IO学习[1] - 文件IO中,我总结了Linux下的文件IO。文...

  • 浅析 Linux 文件 IO 读写

    浅析 Linux 文件 IO 读写 Linux的文件IO子系统是Linux中最复杂的一个子系统(没有之一)。读者可...

  • Linux系统编程笔记-文件IO

    本文主要介绍了如下内容: C标准库函数与系统函数的关系 进程控制块 文件描述符 系统调用:open、close、r...

  • Linux 文件IO 和 标准IO

    [TOC] Linux 文件IO 和 标准IO Linux 文件IO Linux中做文件IO最常用到的5个函数是:...

  • Linux/UNIX系统编程手册-文件IO

    Linux/UNIX系统编程手册 [德] Michael Kerrisk 第4章 文件I/O: 通用的I/O模型 ...

  • 软件架构设计-操作系统

    操作系统 直接IO与缓冲IO 缓冲io又称作标准I/O,大多数文件系统的默认IO操作都是缓冲IO。在linux的缓...

  • 11.标准IO库

    标准IO和文件IO有什么区别 看起来使用时都是函数,但是:标准IO是C库函数,而文件IO是linux系统的API ...

  • 几种IO复用简介

    Linux服务端编程 IO复用 select: 连接数有限制需要修改linux系统设置 遍历所有句柄,确定那些有事...

网友评论

    本文标题:linux系统编程-day08-文件IO(3)

    本文链接:https://www.haomeiwen.com/subject/svtssftx.html