美文网首页
File I/O Multiplexed I/O

File I/O Multiplexed I/O

作者: 无无吴 | 来源:发表于2019-08-12 14:11 被阅读0次

select()

#include <sys/select.h>
int select(int n, fd_set *readfds, fd_set *writefds, 
      fd_set *exceptfds, struct timeval *timeout);
FD_CLR(int fd, fs_set *set);
FD_ISSET(int fd, fs_set *set);
FD_SET(int fd, fs_set *set);
FD_ZERO(int fd, fs_set *set);

select()的调用会一直阻塞,直到给定的文件描述符可以执行I/O,或是已过了可选指定的超时。
readfds中列出的文件描述符,以查看数据是否可用于读取。
writefds中列出的文件描述符,以查看写入操作是否将在不阻塞的情况下完成。
exceptfds中列出的文件描述符,以查看是否发生异常,或是否带外数据可用(这些状态仅适用于sockets)。
上述这些可以设置为NULL, 表示select不监视这个事件。
第一个参数n等于任何集合中值最高的文件描述符的值,再加上1。
timeout参数是指向timeval结构的指针,定义如下:

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

如果这个参数不是NULL, select将会返回超时返回,即使没有任何的文件描述符准备好执行IO。
**因为这个结构体的状态在各个UNIX系统中都是未定的,所以在每次调用select前,都要重新初始化。连同文件描述符sets。
如果timeout的两个参数都设置为0,那么调用将立即返回,报告调用时尚未处理的任何事件,但不等待任何后续事件。

文件描述符集不是直接操作的,而是通过宏进行管理。

  • FD_ZERO
    移除指定sets中的所有文件描述符,它应该在每次调用select前被调用。
fd_set writefds;
FD_ZERO(&writefds);
  • FD_SET
    在一个给定的sets中增加一个文件描述符
  • FD_CLR
    在一个给定的sets中移除一个文件描述符
  • FD_SET(fd, &writesfds);
  • FD_CLR(fd, &writesfds);
FD_SET(fd, &writefds); /* add 'fd' to the set */
FD_CLR(fd, &writefds); /* oops, 
  remove 'fd' from the set */

设计良好的代码不应该使用FD_CLR,而且很少使用它。

  • FD_ISSET
    测试文件描述符是否是给定set的一部分。
    如果文件描述符在集合中,则返回一个非零整数,如果不返回,则返回0。fd_ISSET是在SELECT()调用返回以测试给定的文件描述符是否已准备好操作后使用的:
if(FD_ISSET(fd, &writefds))
    /*'fd' is readable without blocking*/

在成功的情况下,select()返回所有三个集合中为I/O准备的文件描述符的数量。如果提供了超时,则返回值可能为0。如果出现错误,则调用返回−1,并设置errno。 对于以下值之一:

  • EBADF
    在其中一个集合中提供了一个无效的文件描述符。
  • EINTR
    等待时捕捉到信号,可以重新发出呼叫。
  • EINVAL
    参数n是负的。 或者给定的超时无效。
  • ENOMEM
    内存不足,无法完成请求。

举例使用:

int selectExample() {
    struct timeval tv;
    fd_set readfds;
    int ret;

    /*wait on stdin for input*/
    FD_ZERO(&readfds);
    FD_SET(STDIN_FILENO, &readfds);

    /*Wait up to five seconds*/
    tv.tv_sec = TIMEOUT;
    tv.tv_usec = 0;

    /*All right, now block!*/
    ret = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &tv);
    if(ret == -1){
        perror("select");
        return 1;
    }
    else if(!ret){
        printf("%d seconds elapsed\n", TIMEOUT);
        return 0;
    }

    if(FD_ISSET(STDIN_FILENO, &readfds)){
        char buf[BUF_LEN+1];
        int len;
        /*guaranteed to not block*/
        len = read(STDIN_FILENO, buf, BUF_LEN);
        if(len == -1){
            perror("read");
            return -1;
        }
        if(len){
            buf[len] = '\0';
            printf("read: %s\n", buf);
        }
        return 0;
    }

    fprintf(stderr, "This should not happen!\n");
    return 1;
}

int main()
{
    while(1){
        selectExample();
    }
    return 0;
}
实验结果

用select实现可移植的延时,delay, sleep

因为select()在不同的Unix系统上比用于亚秒分辨率休眠的机制更容易实现,所以它通常被用作提供的可移植的睡眠方式。

int selectSleep(int sec, int usec) {
    struct timeval tv;
    tv.tv_sec = sec;
    tv.tv_usec = usec;

    /*select for 500 microseconds*/
    select(0, NULL, NULL, NULL, &tv);
    return 0;
}

int main()
{
    //testO_TRUNC();
    //testMode();
    //testTruncate();
    while(1){
        printf("Hello!\n");
        selectSleep(1, 0);
    }
    return 0;
}
delay 1s 实验

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);

/* these are the same as those used by select() */
FD_CLR(int fd, fd_set *set);
FD_ISSET(int fd, fd_set *set);
FD_SET(int fd, fd_set *set);
FD_ZERO(fd_set *set);

pselect()和select()之间有三个不同之处:

  • pselect()使用timespec结构,而不是timeval结构,作为timeout参数。timespec结构使用sec和nanosec,而不sec和usec,提供理论上优越的超时分辨率。然而,在实践中,这两个都不能可靠地提供微秒分辨率。
  • 调用pselect()不修改超时参数。因此,此参数不需要在后续调用上重新初始化。
  • 在select()系统调用中没有sigmask参数。关于信号, 当此参数设置为NULL时,pselect()的行为类似SELECT()。
    timspec结构的定义如下:
#include <sys/time.h>
struct timespec {
        long tv_sec; /* seconds */
        long tv_nsec; /* nanoseconds */
};

在Unix工具箱中添加pselect()的主要动机是添加sigmask参数,该参数试图解决等待文件描述符和signal之间的竞争。

poll()

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

struct pollfd {
      int fd; /* file descriptor */
      short events; /* requested events to watch */
      short revents; /* returned events witnessed */
};
  • 每个pollfd结构指定一个要监视的文件描述符fd。
  • events是对该文件描述符进行监视的事件的位掩码。用户配置
  • revents字段是在文件描述符fd上见证的事件的位掩码。kernel设置并返回。

在events字段中请求的所有事件都可以在revents字段中返回。
有效事件如下:

  • POLLIN
    有数据可读。
  • POLLRDNORM
    有正常的数据可读。
  • POLLRDBAND
    有优先读取的数据。
  • POLLPRI
    有急需阅读的数据。
  • POLLOUT
    写入不会阻塞。
  • POLLWRNORM
    写入正常数据不会阻塞。
  • POLLWEBAND
    写入优先级数据不会阻塞。
  • POLLMSG
    SIGPOLL消息可用。

此外,下列事件可在revents字段中返回:

  • POLLER
    给定文件描述符上的错误。
  • POLLHUP
    挂起给定文件描述符上的事件。
  • POLLNVAL
    指定的文件描述符无效。

POLLIN | POLLPRI等价于select()的read事件
POLLOUT | POLLWRBAND 等价于select()的write事件

POLLIN == POLLRDNORM | POLLRDNORM
POLLOUT == POLLWRNORM

Timeout参数指定以毫秒为单位等待的时间长度,负值表示无限超时。值为0表示调用立即返回。

在成功情况下,poll()返回其结构具有非零revents字段的文件描述符的数量。
如果超时发生在任何事件发生之前,则返回0。
失败时,返回−1。

基本用法1:

int pollExample() {
    struct pollfd fds[2];
    int ret;

    /*watch stdin for input*/
    fds[0].fd = STDIN_FILENO;
    fds[0].events = POLLIN;

    /*watch stdout for ability to write(always true)*/
    fds[1].fd = STDOUT_FILENO;
    fds[1].events = POLLOUT;

    /*All set, block!*/
    ret = poll(fds, 2, TIMEOUT * 1000);
    if(ret == -1){
        perror("poll");
        return 1;
    }
    if(!ret){
        printf("%d seconds elapsed.\n", TIMEOUT);
        return 0;
    }

    if(fds[0].revents & POLLIN){
        printf("stdin is readable\n");
    }

    if(fds[1].revents & POLLOUT){
        printf("stdout is writeable\n");
    }

    return 0;

}

int main()
{
    pollExample();
    return 0;
}
基本用法1实验结果

基本用法2:

int pollExample() {
    struct pollfd fds[2];
    int ret;

    /*watch stdin for input*/
    fds[0].fd = STDIN_FILENO;
    fds[0].events = POLLIN;

    /*All set, block!*/
    while(1) {
        ret = poll(fds, 1, TIMEOUT * 1000);
        if (ret == -1) {
            perror("poll");
        }
        if (!ret) {
            printf("%d seconds elapsed.\n", TIMEOUT);
        }

        if (fds[0].revents & POLLIN) {
            char buf[BUF_LEN + 1];
            int len;
            /*guaranteed to not block*/
            len = read(STDIN_FILENO, buf, BUF_LEN);
            if (len == -1) {
                perror("read");
            }
            if (len) {
                buf[len] = '\0';
                printf("read: %s\n", buf);
            }
        }
    }

    return 0;

}

int main()
{
    pollExample();
    return 0;
}
基本用法2实验结果

ppoll()

Linux提供了一个ppoll轮询(),与pselect()的一脉相承。但是,与pselect()不同的是,ppoll()是一个特定于Linux的接口:

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

与pselect()一样,timeout参数指定一个以秒和纳秒为单位的超时值,sigmask参数提供了一组等待的信号。

poll()和select()对比

尽管它们执行相同的基本任务,但出于以下几个原因,轮询()系统调用优于select():

  • poll()不要求用户计算并将最高编号的文件描述符的值加上1。
  • 对于大值文件描述符,poll()更有效。想象一下,通过select()只查看一个值为900的文件描述符内核必须检查文件描述sets中每一个位设置,直到第900位。
  • select()的文件描述符集的大小为固定大小,引入了一个折衷:它们很小,限制了select()可以监视的最大文件描述符,或者它们效率低下。
  • 使用select(),文件描述符集在返回时被重构,因此每次调用都必须重新初始化它们。poll()系统调用将输入(Events)字段与输出(Rven)分隔开来。允许在不更改的情况下重用数组。
  • select()的超时值参数在返回时未定义。可移植代码需要重新初始化它。然而,这并不是poll()的问题。

select()调用确实也有一些优势功能:

  • select()更易于移植,因为一些Unix系统不支持poll()。
  • select()提供更好的超时分辨率:下降到微秒级,而poll()只提供毫秒分辨率。ppoll()和pselect()都能提供纳秒分辨率,但在实践中,这些调用都没有可靠地提供均匀的分辨率。

相关文章

网友评论

      本文标题:File I/O Multiplexed I/O

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