Linux下多路复用I/O接口

您所在的位置:网站首页 内核启动流程图 Linux下多路复用I/O接口

Linux下多路复用I/O接口

2023-04-06 01:03| 来源: 网络整理| 查看: 265

    多路复用

    1.函数说明

    前面的fcntl()函数解决了文件的共享问题,接下来该处理I/O复用的情况了。

    总的来说,I/O处理的模型有5种。

    ● 阻塞I/O模型:在这种模型下,若所调用的I/O函数没有完成相关的功能,则会使进程挂起,直到相关数据到达才会返回。如常见对管道设备、终端设备和网络设备进行读写时经常会出现这种情况。     ● 非阻塞I/O模型:在这种模型下,当请求的I/O操作不能完成时,则不让进程睡眠,而且立即返回。非阻塞I/O使用户可以调用不会阻塞的I/O操作,如open()、write()和read()。如果该操作不能完成,则会立即返回出错(如打不开文件)或者返回0(如在缓冲区中没有数据可以读取或者没空间可以写入数据)。     ● I/O多路转接模型:在这种模型下,如果请求的I/O操作阻塞,且它不是真正阻塞I/O,而是让其中的一个函数等待,在此期间,I/O还能进行其他操作。如本小节要介绍的select()和poll()函数,就是属于这种模型。     ● 信号驱动I/O模型:在这种模型下,进程要定义一个信号处理程序,系统可以自动捕获特定信号的到来,从而启动I/O。这是由内核通知用户何时可以启动一个I/O操作决定的。

    它是非阻塞的。当有就绪的数据时,内核就向该进程发送SIGIO信号。 无论我们如何处理SIGIO信号,这种模型的好处是当等待数据到达时,可以不阻塞。主程序继续执行,只有收到SIGIO信号时才去处理数据即可。

    ● 异步I/O模型:在这种模型下,进程先让内核启动I/O操作,并在整个操作完成后通知该进程。这种模型与信号驱动模型的主要区别在于:信号驱动I/O是由内核通知我们何时可以启动一个I/O操作,而异步I/O模型是由内核通知进程I/O操作何时完成的。现在,并不是所有的系统都支持这种模型。

    可以看到,select()和poll()的I/O多路转接模型是处理I/O复用的一个高效的方法。它可以具体设置程序中每一个所关心的文件描述符的条件、希望等待的时间等,从select()和poll()函数返回时,内核会通知用户已准备好的文件描述符的数量、已准备好的条件(或事件)等。通过使用select()和poll()函数的返回结果(可能是检测到某个文件描述符的注册事件或是超时,或是调用出错),就可以调用相应的I/O处理函数了。

    2.函数格式

    select()函数的语法要点如表2.8所示。

表2.8 select()函数语法要点

所需头文件 #include #include #include 函数原型 int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exeptfds, struct timeval *timeout) 函数传入值 numfds:该参数值为需要监视的文件描述符的大值加1 readfds:由select()监视的读文件描述符集合 writefds:由select()监视的写文件描述符集合 exeptfds:由select()监视的异常处理文件描述符集合 timeout NULL:永远等待,直到捕捉到信号或文件描述符已准备好为止 具体值:struct timeval类型的指针,若等待了timeout时间还没有检测到任何文件描符准备好,就立即返回 0:从不等待,测试所有指定的描述符并立即返回 函数返回值 成功:准备好的文件描述符 0:超时; 1:出错

    可以看到,select()函数根据希望进行的文件操作对文件描述符进行了分类处理,这里对文件描述符的处理主要涉及4个宏函数,如表2.9所示。

表2.9 select()文件描述符处理函数

FD_ZERO(fd_set *set) 清除一个文件描述符集 FD_SET(int fd, fd_set *set) 将一个文件描述符加入文件描述符集中 FD_CLR(int fd, fd_set *set) 将一个文件描述符从文件描述符集中清除 FD_ISSET(int fd, fd_set *set) 如果文件描述符fd为fd_set集中的一个元素,则返回非零值,可以用于调用select()后测试文件描述符集中的哪个文件描述符是否有变化

    一般来说,在每次使用select()函数之前,首先使用FD_ZERO()和FD_SET()来初始化文件描述符集(在需要重复调用select()函数时,先把一次初始化好的文件描述符集备份下来,每次读取它即可)。在select()函数返回后,可循环使用FD_ISSET()来测试描述符集,在执行完对相关文件描述符的操作后,使用FD_CLR()来清除描述符集。

    另外,select()函数中的timeout是一个struct timeval类型的指针,该结构体如下所示:

    struct timeval     {         long tv_sec; /* 秒 */         long tv_unsec; /* 微秒 */     }

    可以看到,这个时间结构体的精确度可以设置到微秒级,这对于大多数的应用而言已经足够了。     poll()函数的语法要点如表2.10所示。

表2.10 poll()函数语法要点

所需头文件     #include     #include 函数原型 int poll(struct pollfd *fds, int numfds, int timeout) 函数传入值 fds:struct pollfd结构的指针,用于描述需要对哪些文件的哪种类型的操作进行监控 struct pollfd {   int fd; /* 需要监听的文件描述符 */   short events; /* 需要监听的事件 */   short revents; /* 已发生的事件 */ } events成员描述需要监听哪些类型的事件,可以用以下几种标志来描述。 POLLIN:文件中有数据可读,下面实例中使用到了这个标志 POLLPRI::文件中有紧急数据可读 POLLOUT:可以向文件写入数据 POLLERR:文件中出现错误,只限于输出 POLLHUP:与文件的连接被断开,只限于输出 POLLNVAL:文件描述符是不合法的,即它并没有指向一个成功打开的文件 numfds:需要监听的文件个数,即第一个参数所指向的数组中的元素数目 timeout:表示poll阻塞的超时时间(毫秒)。如果该值小于等于0,则表示无限等待 函数返回值 成功:返回大于0的值,表示事件发生的pollfd结构的个数 0:超时; 1:出错

    3.使用实例

    当使用select()函数时,存在一系列的问题,例如,内核必须检查多余的文件描述符,每次调用select()之后必须重置被监听的文件描述符集,而且可监听的文件个数受限制(使用FD_SETSIZE宏来表示fd_set结构能够容纳的文件描述符的大数目)等。实际上,poll机制与select机制相比效率更高,使用范围更广。下面以poll()函数为例实现某种功能。

    本实例中主要实现通过调用poll()函数来监听三个终端的输入(分别重定向到两个管道文件的虚拟终端及主程序所运行的虚拟终端)并分别进行相应的处理。在这里我们建立了一个poll()函数监视的读文件描述符集,其中包含三个文件描述符,分别为标准输入文件描述符和两个管道文件描述符。通过监视主程序的虚拟终端标准输入来实现程序的控制(如程序结束);以两个管道作为数据输入,主程序将从两个管道读取的输入字符串写入到标准输出文件(屏幕)。

    为了充分表现poll()函数的功能,在运行主程序时,需要打开3个虚拟终端:首先用mknod命令创建两个管道in1和in2。接下来,在两个虚拟终端上分别运行cat>in1和cat>in2。同时在第三个虚拟终端上运行主程序。

    在程序运行后,如果在两个管道终端上输入字符串,则可以观察到同样的内容将在主程序的虚拟终端上逐行显示。

    如果想结束主程序,只要在主程序的虚拟终端下输入以“q”或“Q”字符开头的字符串即可。如果三个文件一直在无输入状态中,则主程序一直处于阻塞状态。为了防止无限期的阻塞,在程序中设置超时值(本实例中设置为60s),当无输入状态持续到超时值时,主程序主动结束运行并退出。该程序的流程图如图2.3所示。

图2.3 多路复用实例流程图

    /* multiplex_poll.c */     #include     #include     #include     #include     #include     #include     #include     #include     #define MAX_BUFFER_SIZE 1024 /* 缓冲区大小 */     #define IN_FILES 3 /* 多路复用输入文件数目 */     #define TIME_DELAY 60000 /* 超时时间秒数:60s */     #define MAX(a, b) ((a > b)?(a):(b))

    int main(void)     {         struct pollfd fds[IN_FILES];         char buf[MAX_BUFFER_SIZE];         int i, res, real_read, maxfd;

        /* 首先按一定的权限打开两个源文件 */         fds[0].fd = 0;         if((fds[1].fd = open ("in1", O_RDONLY|O_NONBLOCK))         {             printf("Open in1 error\n");             return 1;         }         if((fds[2].fd = open ("in2", O_RDONLY|O_NONBLOCK))         {             printf("Open in2 error\n");             return 1;         }         /* 取出两个文件描述符中的较大者 */         for (i = 0; i         {             fds[i].events = POLLIN;         }

        /* 循环测试是否存在正在监听的文件描述符 */         while(fds[0].events || fds[1].events || fds[2].events)         {             if (poll(fds, IN_FILES, 0)             {                 printf("Poll error or Time out\n");                 return 1;             }             for (i = 0; i             {                 if (fds[i].revents) /* 判断在哪个文件上发生了事件 */                 {                     memset(buf, 0, MAX_BUFFER_SIZE);                     real_read = read(fds[i].fd, buf, MAX_BUFFER_SIZE);                     if (real_read                     {                         if (errno != EAGAIN)                         {                             return 1; /* 系统错误,结束运行 */                         }                     }                     else if (!real_read)                     {                         close(fds[i].fd);                         fds[i].events = 0; /* 取消对该文件的监听 */                     }                     else                     {                         if (i == 0) /* 如果在标准输入上有数据输入时 */                         {                             if ((buf[0] == 'q') || (buf[0] == 'Q'))                             {                                 return 1; /* 输入“q”或“Q”则会退出 */                             }                         }                         else                         { /* 将读取的数据先传送到终端上 */                             buf[real_read] = '\0';                             printf("%s", buf);                         }                     } /* end of if real_read*/                 } /* end of if revents */             } /* end of for */         } /*end of while */         exit(0);     }

    读者可以将以上程序交叉编译,并下载到开发板上运行,以下是运行结果:

    $ mknod in1 p     $ mknod in2 p     $ cat > in1            /* 在第一个虚拟终端 */     SELECT CALL     TEST PROGRAMME     END     $ cat > in2            /* 在第二个虚拟终端 */     select call     test programme     end     $ ./multiplex_select   /* 在第三个虚拟终端 */     SELECT CALL            /* 管道1的输入数据 */     select call            /* 管道2的输入数据 */     TEST PROGRAMME         /* 管道1的输入数据 */     test programme         /* 管道2的输入数据 */     END                    /* 管道1的输入数据 */     end                    /* 管道2的输入数据 */     q                      /* 在第三个终端上输入“q”或“Q”则立刻结束程序运行 */

    程序的超时结束结果如下:

    $ ./multiplex_select     …(在60s之内没有任何监听文件的输入)     Poll error or Time out

    本文选自华清远见嵌入式培训教材《从实践中学嵌入式Linux应用程序开发》

   热点链接:

   1、linux 文件锁的实现及其应用    2、底层文件I/O操作的系统调用    3、Linux中的文件及文件描述符    4、Linux文件系统之虚拟文件系统(VFS)    5、Linux系统调用及用户编程接口(API)

更多新闻>> 



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3