美文网首页
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