概述
当进程中用到多个IO,希望内核一旦发现指定的IO条件就绪,就通知进程,这个能力称为IO复用
Unix下可用的5中IO模型
5种IO模型包括:阻塞式IO、非阻塞式IO、IO复用、信号驱动式IO、异步IO
1.阻塞式IO模型
以recvfrom为例,进程等待内核准备好数据后,并复制到用户进程的缓冲区或者发生错误才返回。进程在调用recvfrom直到返回这段期间,都是处于等待状态。
2.非阻塞式IO模型
如果把一个套接字设置成非阻塞式,会通知内核,当请求IO的操作不能及时完成时,不要置进程休眠,而是返回一个错误。
3.IO复用模型
调用select、poll或epoll,进程阻塞在这几个函数调用上,而不是阻塞在某个特定的IO上,当关注的任何一个或多个IO准备就绪,这几个函数返回,进程执行下一步操作。
和IO复用模型一个类似的模型,就是针对每一个描述符,创建一个线程,阻塞的等待描述符就绪。
4.信号驱动式IO
让内核在描述符准备就绪的时候,发送SIGIO信号给我们的进程,在此期间进程可以做任何事。
5.异步IO模型
异步IO的工作机制是,告诉内核启动某个操作,并在内核完成数据等待、操作完成之后通知到进程。它和信号驱动式IO的区别在于,前者是内核操作完指定函数之后通知我们,后者是内核在IO就绪的时候通知我们,随后需要进程自己去做具体操作。
POSIX中定义,真正的IO操作由进程来完成的,称为同步IO操作,反之称为异步IO操作。根据这个定义,前四种IO模型属于同步IO操作,最后一种模型属于异步IO模型
select函数
函数说明
我们调用select函数,告诉内核进程对哪些描述符的读、写、异常事件感兴趣,并指定愿意等待的时长,等待期间进程阻塞在select函数上,直到有描述符就绪,返回就绪的描述符数目。描述符不局限于套接字,可以是任何描述符。
#include <sys/select.h>
int select (int maxfdpl, fd_set * restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct timeval *restrict timeout);
返回值:若有描述符准备就绪,返回描述符数目,若超时返回0,若出错返回-1
我们感兴趣的可读、可写、发生异常的描述符,分别通过readfds、writefds、exceptfds参数传递给select。它们的数据结构是fd_set,本质是一个整数数组,感兴趣的fd集合记录在这个数组当中,可以设置为NULL。我们无需关心fd_set的具体实现,只需要调用相应的宏对fd_set进程增删改查:
void FD_ZERO(fd_set *fdset);//初始化fd_set
void FD_SET(int fd, fd_set *fdset);//将fd加入fdset
void FD_CLR(int fd, fd_set *fdset);//将fd从fdset中移除
int FD_ISSET(int fd, fd_set *fdset);//判断fd是否在fdset中
其中fdset会被select动态的修改,如select返回的时候,fdset中仅仅保留了已经准备就绪的fd,此时可以调用FD_ISSET来判断哪些fd准备就绪。
maxfdp1:传入的最大描述符内容+1,如我们传入描述符1、4、5,那么maxfdp1等于6
timeout:参数用于表示进程等待IO的超时时间
NULL:一直等待,直到有fd就绪
0:不等待
大于0的常数:等待指定时间
套接字描述符就绪
普通文件描述符可读、可写就绪的概念比较好理解,网络套接字描述符的就绪是什么概念?
如果网络套接字,接收缓存收到很少的数据,或只有很小的发送缓存,就给应用程序发送就绪信号,应用程序拿到这些数据可能没有办法执行业务,所以有必要对触发就绪设置一个最小值,如设置接收最小值和发送最小值。在接收数据大于接收最小值,触发select返回对应读fd,在发送缓存大于最小值,触发select返回对应写fd。接收最小值通过SO_RCVLOWAT设置,发送最小值通过SO_SNDLOWAT设置
(1)满足以下几个条件之一,网络套接字读就绪:
- 接收缓存中的数据大于等于接收最小值
- 监听套接字调用accept,且已完成连接队列中的连接数大于0
- 收到对侧发FIN包,sockfd触发可读,此时返回0
- 套接字有错误待处理,返回-1
(2)满足以下几个条件之一,网络套接字写就绪
- 发送缓存可用空间大于发送最小值
- TCP写方向关闭
- 套接字有错误待处理,返回-1
(3)exceptfds对应的连接有带外数据,select会返回
pselect函数
#include <sys/select.h>
int pselect(int maxfdpl, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, const struct timespec *restrict tsptr, const sigset_t *restrict sigmask)
pselect与select的区别在于:
- 使用timespec指定超时时间,时间颗粒度更加精细
- 指定信号屏蔽字,并通过原子操作设定屏蔽字,可对select期间的未决信号进行屏蔽,select结束后恢复屏蔽字。
poll函数
poll函数和select函数功能类似,函数原型如下所示:
#include <poll.h>
int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);
- 返回值:就绪的fd数目,超时返回0,出错返回-1
- fdarray:指向struct pollfd结构体数组的指针
- nfds:fdarray结构体数组的数目
- timeout:超时时间,INFTIM永远等待,0不阻塞,>0指定等待的毫秒数
其中struct pollfd结构体定义如下所示:
struct pollfd {
int fd;//感兴趣的fd
short events;//该fd感兴趣的事件
short revents;//fd实际发生的事件
}
events是对fd感兴趣的事件,revents是fd实际发生的事件,两者区别开来的原因是,一些错误事件不可作为events传入poll,却可以通过revents返回给调用者。
本文概述了Unix系统中的五种IO模型,包括阻塞IO、非阻塞IO、IO多路复用、信号驱动IO和异步IO,详细解释了每种模型的工作原理和应用场景,以及select、pselect和poll函数的作用和用法。
2121

被折叠的 条评论
为什么被折叠?



