操作系统与网络 (13. 典型IO/多路转接模型)

准确表达自己的诉求并且懂得拒绝,会在很大程度上减少生活中的烦恼

13. 典型IO/多路转接模型

13.1 典型IO

13.1.1 分类

(1) 阻塞IO
在内核将数据准备好之前, 系统调用会一直等待. 所有的套接字, 默认都是阻塞方式.
(2)非阻塞IO
如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULDBLOCK错误码.非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询.
(3) 信号驱动IO
内核将数据准备好的时候, 使用SIGIO信号通知应用程序进行IO操作.
(4) 异步IO
由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据).
(5) 多路转接IO
对大量的的IO事件是否就绪的监控, 并且可以告诉用户哪一个IO当前就绪,这个时候用户可以针对就绪的ID进行操作.

  • IO流程
    等待IO就绪+IO数据拷贝
    几种典型IO的操作对比
    这几种IO是一个IO的发展过程, 他们的使用效率以及对资源的利用率越来越高, 但是占用的资源越来越多, 并且流程控制越来越复杂.
  • 概念
    阻塞
    非阻塞
  • 同步
    <1> 为了完成一个功能发起调用, 若当前不具备完成条件, 则进行进程等待, 直到完成功能;
    <2> 同步功能由进程自身完成, 通常都是阻塞的;
  • 异步
    为了完成一个功能发起调用, 进程自身并不完成, 功能由操作系统完成后通知进程, 同步与异步强调的是发起调用后,功能是否是由进程自身完成的,异步功能由别人完成, 通常有阻塞也有非阻塞.
  • 异步阻塞
    等待别人完成功能;
  • 异步非阻塞
    不等待别人完成 调用发起后,直接返回.
  • 同步与异步优缺点
    同步流程更加简单, 异步对资源的利用更加充分, 但是流程控制比较复杂;发起调用之后, 是否能够立即完成功能, 并调用返回.

13.2 多路转接模型(多路复用)

select

  • 接口使用
    int select(int nfds, fd_set *readfds, fd_set *writefds,
    fd_set *exceptfds, struct timeval *timeout);
  • 参数解析:
    nfds: maxfd+1 最大的描述符+1; 避免空遍历判断, 提高性能;
    readfds/writefds/exceptfds: 可读/可写/异常事件集合
    timeout: struct{tv_sec; tv_usec}为select的阻塞监控,做一个超时返回
    NULL: 永久阻塞 内部时间为0则立即返回
    fdset: 描述符集合–位图存储–大小默认为1024, 取决于_FD_SETSIZE这个宏
    返回值: >0 返回就绪的描述符个数 ==0 等待超时 <0 监控出错
  • 原理流程
    <1> 用户自己定义一个描述符集合fd_set, 然后将需要监控的描述符添加到集合中;
    <2> 将描述符集合拷贝到内核中;
    <3> 对集合中的的描述符进行轮询就绪判断:
    若没有描述符就绪, 则挂起等待, 在此进行轮询判断, 若超时, 则直接返回;
    若有描述符就绪, 则select调用返回, 并且返回就绪的描述符个数;
    <4> 调用返回之前, 将描述符集合中的非就绪描述符移除(集合中遗留的都是就绪描述符);
    <5> 进程判断哪个描述符在集合中, 这个描述符就是就是就绪的, 就而对其进行IO操作
  • 优缺点分析
    优点
    <1> select遵循posix标准, 可以跨平台;
    <2> select监控超时等待时间, 可以精确到微秒;
    缺点
    <1> select所能监控的描述符数量有最大上限–1024(_FD_SETSIZE);
    <2> select每次都需要将描述符集合拷贝到内核进行监控(用户态与内核之间的数据拷贝);
    <3> select在内核中对所有描述符进行轮询遍历判断是否就绪(性能随着描述符增多而降低);
    <4> select就绪后移除集合中非就绪描述符, 修改集合;每次监控都需要新添加描述符(编码复杂);
    <5> select返回给用户就绪的描述符集合, 但是不会直接告诉用户哪些描述符就绪(需要用户进行遍历判断哪些描述符在集合中,才能找出就绪的描述符进而对其进行操作),性能随着描述符增多而降低;

poll(已被淘汰)

  • 接口使用
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
// pollfd结构
struct pollfd {
	int fd; /* file descriptor */
	short events; /* requested events */
	short revents; /* returned events */
};
  • 参数解析:
    fds是一个poll函数监听的结构列表. 每一个元素中, 包含了三部分内容: 文件描述符,
    监听的事件集合, 返回的事件集合.
    nfds表示fds数组的长度.
    timeout表示poll函数的超时时间, 单位是毫秒(ms).
    返回值: <0 表示出错; ==0表示poll函数等待超时; >0返回就绪的描述符个数;
  • 原理流程
  1. 用户Wie每一个关心的描述符定义事件结构(文件描述符/用户所关心的事件);
  2. 将描述事件结构数组拷贝到内核中进行监控;
  3. 轮询遍历事件数组中的描述符, 判断描述符是否就绪了某个事件:
    若没有描述符就绪用户所关心的事件, 则每隔一会遍历判断一次;
    若有描述符就绪用户所关心的事件, 则会将就绪的事件放到事件结构的revents中,并调用返回(性能随着描述符增多而降低);
  4. 当调用返回后, 用户遍历事件结构数组, 判断结构中的revents事件中是否包含用户所关心的事件, 通过这种方式判断哪一个描述符就绪了, 进而对描述符进行相应操作;
  • 优缺点分析
    优点
    <1> poll采用事件结构的方式对描述符进行监控, 简化了多个描述符集合的监控编码流程;
    <2> poll没有描述符数量上的限制;
    缺点
    <1> poll每次监控都需要将所有的事件结构信息拷贝到内核;
    <2> 在内核中poll进行就绪判断同样适用轮询遍历的判断(性能随着描述符增多而降低);
    <3> 描述符事件就绪后, poll修改描述符事件结构中的revents信息为当前就绪的事件;但是poll不会直接告诉用户哪一个描述符就绪, 需要用户对事件结构数组进行遍历, 判断哪一个结构中的revents是用户关心的事件, 则这个描述符是就绪的进入而对其进行操作;
    <4> poll不能跨平台操作;
    <5> poll相较于select实际上对大量描述符进行监控的原理并没有发生改变, 因此性能并没有多大提高;

epoll

  • 接口使用
    int epoll_create(int size);
    创建epoll, 在内核中创建eventpoll结构体struct eventpoll{…红黑树rbr, …双向
    链表rdllist,…}
    返回一个文件描述符, 可以通过没舒服找到内核中的eventpoll结构体;
  • 参数解析
    size: 限制epoll所能监控的描述符数量上限, 自从Linux2.68之后被忽略,需要>0;
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    它不同于select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型.
    epfd: 是epoll_create()的返回值(epoll的句柄).
    op: 表示动作,用三个宏来表示,EPOLL_CTL_ADD,EPOLL_CTL_MOD,EPOLL_CTL_DEL;
    fd: 是需要监听的fd.
    *event: 告诉内核需要监听什么事.
struct epoll_event{
	uint32_t events;
	epoll_data_t data;
	_EPOLL_PACKED;
}
  • int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
    epfd: epoll_create返回的epoll操作句柄, 描述符;
    events: epoll_event结构体数组首地址, 用于接收就绪的描述符事件信息;
    maxevents: epoll_event结构体数组结点个数;
    timeout: epoll的超时等待时间(毫秒);
    返回值: >0就绪的描述符个数 ==0 等待超时 <0 监控出错
  • 原理流程
  1. 通过epoll_create创建eventpoll结构, 返回操作句柄;
  2. 通过操作句柄向内核中添加描述符的监控事件epoll_ctl;
  3. 通过epoll_wait开始监控,异步阻塞操作, 描述符的监控由操作系统完成;
    操作系统: 当描述符就绪了用户所关心的事件, 则将事件信息添加到eventpoll的双向链表中,每隔一段时间则会看eventpoll中双向链表是否为空, 判断是否有描述符就绪;
    若没有就绪, 则挂起等待, 隔一会继续判断, 若超时则返回;
    若有就绪返回时, epoll_wait将就绪的事件信息放置到事件数组events中,返回给用户;
  4. 用户通过epoll_wait传入的events获取到就绪的事件,直接遍历操作;
  • 优缺点分析
    优点
    <1> epoll不能跨平台;
    <2> 监控超时等待时间只能精细到毫秒;
    缺点
    <1> 监控的描述符数量无最大上限;
    <2> 采用事件结构对描述符进行监控, 简化了多个描述符集合的监控操作流程;
    <3> epoll的事件信息, 每条信息只需要向内核拷贝一次;
    <4> epoll是一个异步阻塞操作, 操作系统对描述符事件进行监控, 而这个操作系统实现的监控采用事件回调的方式, 不需要进行轮询遍历描述符进行判断, 性能不会随着描述符增多而降低;
    <5> 当描述符就绪后将描述符事件信息添加到双向链表中,epoll_wait只需要隔会判断双向链表是否为空, 就可以判断出是否有就绪描述符事件就绪;
    <6> 当epoll_wait返回时, 直接将就绪的事件信息拷贝到用户给与的事件结构数组中,相当于直接告诉了哪些描述符就绪了(事件结构中包含描述符), 用户可以直接对描述符进行操作(避免非就绪描述事件遍历);
  • 说明
    IO多路转接模型, 可以实现对大量描述符就绪事件监控, 可以让进进程针对就绪描述符进行操作, 可以让进程,线程避免因为对非就绪描述符进行操作而阻塞, 可以让一个进程轮询对大量的描述符进行操作, 实现一个服务器与多个客户端进行数据通信, 因此IO多路转接模型是一种高并发模型;

你可能感兴趣的:(操作系统和网络)