Linux I/O多路复用

IO多路复用(multiplexing)本质是通过系统内核缓冲IO数据,让单个进程可以监视多个文件描述符,一旦有某个描述符就绪(读就绪或写就绪),进程就可以进行相应的操作。

select、epoll、poll是Linux提供的IO多路复用API。

文件描述符和IO缓存

文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。

Linux把I/O视为文件,所以利用文件描述符来管理I/O

IO缓存:缓存I/O又称为标准I/O,大多数文件系统的默认I/O操作都是缓存I/O。在Linux的缓存I/O机制中,操作系统会将I/O的数据缓存在文件系统的页缓存中,即数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。

Select函数

int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timbal *timeout);

int maxfdp1 指定待测试的文件描述符个数,它的值是待测试的最大描述符加1。
fd_set理解为一个集合,存放的是文件描述符(file descriptor)。参数readset,writeset和exceptset指定要让内核测试的读、写和异常条件的文件描述符集合。如果某个不需要,则把它设成空指针即可。
*const struct timval timeout timeout告知内核等待所指定文件描述符集合中的任何一个就绪可花多少时间。其timeval结构用于指定这段时间的秒数和微妙数。

返回值 若有就绪描述符,则返回其数目,若超时则为0,若出错则为-1。

select运行机制:
select机制中提供一种fd_set的数据结构,实际上是一个long类型的数组,每一个数组元素都能与一打开的文件句柄(无论是Socket句柄,还是其他文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成。当调用select时,由内核根据I/O状态修改fd_set的内容,由此来通知执行了select的进程哪一Socket或文件是可读的。

从流程上看,使用select函数进行I/O请求和同步阻塞模型没有太大的区别,因为调用select函数后进程也会阻塞。甚至因为多添加了socket以及调用select函数的额外操作而效率更差。但使用select的最大优势在于用户可以仅凭借一个线程同时处理多个socket的I/O请求。用户可以注册多个socket,然后不断调用select读取被激活的socket。在同步阻塞模型中必须使用多线程的方式,为每个socket创建一个线程去监控I/O请求。

select的缺点

  1. 每次调用select,都需要把fd_set集合从用户态拷贝到内核态,如果fd_set集合很大时,这个开销也会很大。
  2. select底层采用轮询的方式监控,每次调用select都需要在内核遍历传递进来的所有fd_set,如果fd_set集合很大时,这个开销也会很大。
  3. 为了减少数据拷贝造成的性能影响,内核对被监控的fd_set集合大小做了限制,并且这个是通过宏控制的,大小不可改变(Linux中通常限制为1024)。

Poll函数

Poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。只解决了select的第三个缺点,没有解决select的缺点1和缺点2。

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

typedef struct pollfd {
	int fd; // 需要被检测或选择的文件描述符
	short events; // 对文件描述符fd上感兴趣的事件
	short revents; // 文件描述符fd上当前实际发生的事件
} pollfd_t;

poll改变了文件描述符集合的描述方式,使用了pollfd结构而不是select的fd_set结构,使得poll支持的文件描述符集合限制远大于select的1024.

*struct polled fds: fds是一个struct pollfd类型的数组,用于存放需要检测其状态的socket描述符,并且调用poll函数之后fds数组不会被清空;一个pollfd结构体表示一个被监视的文件描述符,通过传递fds指示poll()监视多个文件描述符。其中,结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。结构体的revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域。

nfds_t nfds 记录数组fds中描述符的总数量。

返回值:返回值int表示fds集合中就绪的读、写或出错的描述符数量,返回0表示超时,返回-1表示出错。

Epoll函数

epoll是基于事件驱动的I/O方式,相对于select来说,epoll没有描述符个数的限制,使用一个文件描述符管理多个描述符,将用户关心的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间只需copy一次。

Linux中的epoll相关函数:

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
  1. epoll_create函数创建一个epoll句柄,参数size表明内核要监听的描述符数量。调用成功时返回一个epoll句柄描述符,失败时返回-1。
  2. epoll_ctl函数注册要监听的事件类型。四个参数的解释如下:
    • epfd 表示epoll句柄;
    • op表示fd操作类型,有三种:
      • EPOLL_CTL_ADD 注册新的fd到epfd中
      • EPOLL_CTL_MOD 修改已注册的fd监听事件
      • EPOLL_CTL_DEL 从epfd中删除一个fd
    • fd是要监听的描述符
    • event表示要监听的事件
      epoll_event结构体定义如下:
strcut epoll_event {
	__uint32_t events; // Epoll events
	epoll_data_t data; // User data variable
};

typedef union epoll_data {
	void *ptr;
	int fd;
	__uint32_t u32;
	__uint64_t u64;
} epoll_data_t;
  1. epoll_wait函数等待事件的就绪,成功时返回就绪的事件数目,调用失败时返回-1,等待超时返回0。
    • epfd时epoll句柄
    • events表示从内核得到的就绪事件集合
    • maxevents告诉内核events的大小
    • timeout表示等待的超时事件

epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的改进。能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。因为获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核I/O事件异步唤醒而加入Ready队列的描述符集合就行了。

epoll除了提供select/poll那种I/O事件的水平触发(Level Triggered)外,还提供了边缘触发(Edged Triggered),这就使得用户空间程序有可能缓存I/O状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

  • 水平触发(LT):默认工作模式,当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序可以不立即处理该事件,下次调用epoll_wait时,会再次通知此事件;
  • 边缘触发(ET):当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次通知此事件。(边缘触发只在状态由未就绪变为就绪时只通知一次)。

ET模式很大程度上减少了epoll事件的触发次数,因此效率比LT模式下高。

https://www.jianshu.com/p/397449cadc9a
epoll是目前在Linux下进行大规模网络并发程序开发的首选模型。在绝大多数情况下性能远超select和poll。但是在并发连接不高的情况下,多线程+阻塞I/O方式的性能可能更好。

参考博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值