01 IO多路复用
2020-11-26 本文已影响0人
格林哈
1 描述
- 背景 需要以非阻塞的方式检查文件描述符上是否可进行I/O操作。
同时检查多个文件描述符,看它们中的任何一个是否可以执行I/O操作- 部分满足的技术
- 非阻塞式I/O
- 多进程或多线程技术
- 方案
- I/O多路复用允许进程同时检查多个文件描述符以找出它们中的任何一个是否可执行I/O操作。系统调用select()和poll()用来执行I/O多路复用
- 信号驱动I/O是指当有输入或者数据可以写到指定的文件描述符上时,内核向请求数据的进程发送一个信号。进程可以处理其他的任务,当I/O操作可执行时通过接收信号来获得通知。当同时检查大量的文件描述符时,信号驱动I/O相比select()和poll()有显著的性能提升
- epoll API是Linux专有的特性,同I/O多路复用API一样,epoll API允许进程同时检查多个文件描述符,看其中任意一个是否能执行I/O操作
- 三个方案同一目标
- 同时检查多个文件描述符,看它们是否准备好了执行 I/O 操作(准确地说,是看 I/O系统调用是否可以非阻塞地执行)
- 这些技术都不会执行实际的I/O操作。它们只是告诉我们某个文件描述符已经处于就绪状态了。这时需要调用其他的系统调用来完成实际的I/O操作
- 命令对比
- select/poll
- 优点 可移植性
- 缺点 当同时检查大量的(数百或数千个)文件描述符时性能延展性不佳
- epoll
- 优点 它能让应用程序高效地检查大量的文件描述符
- 缺点 它是专属于Linux系统的API
- select/poll
- 部分满足的技术
2 水平触发和边缘触发
- 两种文件描述符准备就绪的通知模式
2.1 水平触发通知
- 如果文件描述符上可以非阻塞地执行 I/O 系统调用,此时认为它已经就绪。
- 在任意时刻检查文件描述符的就绪状态,没有必要每次当文件描述符就绪后需要尽可能多地执行 I/O
2.2 边缘触发通知
- 如果文件描述符自上次状态检查以来有了新的I/O活动(比如新的输入),此时需要触发通知。
- 当I/O事件发生时我们才会收到通知,尽可能多地执行I/O
1 select/poll/epoll
1.1 select
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);
struct timeval
{
long tv_sec; /*秒 */
long tv_usec; /*微秒 */
};
- 方法描述
- nfds 被监听的文件描述符的总数,它比所有文件描述符集合中的文件描述符的最大值大1,因为文件描述符是从0开始计数的
- readfds 是用来检测输入是否就绪的文件描述符集合。
- writefds 是用来检测输出是否就绪的文件描述符集合。
- exceptfds 是用来检测异常情况是否发生的文件描述符集合。
- timeout 等待时间上限, 指定为NULL,此时select()会一直阻塞
- 返回值
- −1表示有错误发生
- 正整数表示有1个或多个文件描述符已达到就绪
-
总结
image.png
- 过程
- 通过 fdset 位图先置为0,然后把fdset 从用户态拷贝到内核态
- 内核态 判断是否有数据
- 无 阻塞
- 有 就把对于fdset 位置标记有数据来, select 方法返回。
- 缺点
- fdset 默认大小 1024,虽然可以调整,但是还是有上限。
- fdset 不可重用
- 从内核态回来,调用FD_ZERO()将fdset所指向的集合初始化为空
- fdset 用户态内核态切换的开销
- 返回的fdset,判断文件描述符是否有数据 ,遍历时O(n)的复杂度。
- 过程
1.2 poll
- 跟select 相识,主要的区别在于我们要如何指定待检查的文件描述符
# include < sys/ poll. h>
int poll ( struct pollfd * fds, unsigned int nfds, int timeout);
struct pollfd
{
int fd; /* 文件描述符 */
short events; /* 等待的事件 */
short revents; /* 实际发生了的事件 */
} ;
typedef unsigned long nfds_t;
-
方法描述
- fds 存放需要检测其状态的socket描述符
- nfds 数组fds中元素的个数
- timeout 等待时间上限
- -1 一直阻塞
- 0 只是执行一次检查看看哪个文件描述符处于就绪态
- 大于0 poll()至多阻塞timeout毫秒
- 返回值
- −1 表示有错误发生
- 0 表示该调用在任意一个文件描述符成为就绪态之前就超时了
- 正整数 表示有1个或多个文件描述符处于就绪态了
-
总结
image.png
- 过程
- 首先 把pollfd 结构体 revents 置为0,有数据通过 revents 判断。
- 对select 改进
- 是个数组,解决了fdset 默认大小 1024 问题。
- 结构体pollfd,可以重用,解决 fdset 不可重用。 只需要把 revents 置为0.
- select 3,4 的缺点依然存在。
- 过程
1.2 epoll
- 当检查大量的文件描述符时,epoll的性能延展性比select()和poll()高很多
- epoll API既支持水平触发也支持边缘触发。与之相反,select()和poll()只支持水平触发
#include<sys/epoll.h>
// epoll instance数据结构并返回一个文件描述符
int epoll_create(int size);
typedef union epoll_data {
void *ptr; /* 指向用户自定义数据 */
int fd; /* 注册的文件描述符 */
uint32_t u32; /* 32-bit integer */
uint64_t u64; /* 64-bit integer */
} epoll_data_t;
struct epoll_event {
uint32_t events; /* 描述epoll事件 */
epoll_data_t data; /* 见第一个结构体 */
};
//epoll_ctl就是管理interest list的接口
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev);
// 事件等待
int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents, int timeout);
-
总结
image.png
- epoll_wait 执行
- 用户态内核态共享内存
- 返回值,判断文件描述符是否有数据, 复杂度 O(1)
- 返回值 返回数组evlist中的元素个数
- -1 出错
- 0 如果在timeout超时间隔内没有任何文件描述符处于就绪态
- 大于0 n, 前n个数据
- epoll_wait 执行
-
epoll与select poll 比较
image.png
- select用数组来存放socket, 因此受到数量限制. poll用链表存放, epoll用红黑树存放, 因此不受数量限制
- select poll是采用轮询方式, epoll采用回调的方式
- select、poll 返回就绪的文件描述符数量, 还要进行一次O(n) 遍历, epoll 不需要
-
Linux/UNIX系统编程手册