BIO,NIO,AIO的底层(select/poll以及epoll)

分享这篇受益匪浅的好文章

fd_set文章

回顾用户态和内核态的交互

  1. 用户态应用程序调用系统调用(系统调用相当于内核态的一组接口,满足用户态应用程序调用内核的使用)
  2. 用户态的应用程序也可以调用系统函数库的函数,函数库是在系统调用的基础上做了封装

IO底层的实现

  1. IO通信是在内核态中实现的,需要用户态调用系统调用实现
  2. 这里的系统调用接口其实就是select()方法,select返回需要的IO数据

select方法参数以及返回值

select的作用:使用socket套接字时,socket的读写需要调用系统调用select方法实现,select实现多路复用,每次socket调用select只需要注册,socket在fd_set集合中,一个select可以负责多个IO操作
  1. select原型
int select (int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
  1. fd_set是C语言的一个结构体,结构体内包含一个数组,底层实现是一个bitMap也就是位图结构,数组大小是1024,所以socket的最大读写文件数是1024,也就是socket发送缓冲区的大小。
  2. readfds表示socket感兴趣的可读状态的fd文件集合
  3. writefds表示socket感兴趣的可写状态的fd文件集合
  4. exceptfds表示特殊事件的文件集合,比如出现错误等
  5. timeout表示超时时间返回select
  6. 用户态应用程序循环系统调用select查看是否有感兴趣的事件发生,每一个bit都对应一个fd,往集合了添加n时只需将第n个bit位置1,移除n时只需将第n个比特置为0,移除所有数据时,只需将所有bit置为0,并且在fd_set数组的事件的对应位置填写1,否则填写0。返回select。
返回值负数,0,正数
  1. 当返回为-1时,所有描述符集清0。
  2. 当返回为0时,表示超时。
  3. 当返回为正数时,表示已经准备好的描述符数。
  4. 返回正数时,开发者可以根据传入的socket循环查询是否有文件可读或者可写,时间复杂度是O(n),流太多的话会很消耗cpu

fd_set的方法

FD_CLR( s, *set)    //从set中删除句柄s;
FD_ISSET( s, *set)  //检查句柄s是否存在与set中;
FD_SET( s, *set )   //把句柄s添加到set中;
FD_ZERO( *set )     //把set队列初始为空

特点:

1.select/poll每次都需要重复传递全部的监听fd进来,涉及用户空间和内核直接的数据拷贝。

2.fd事件回调函数是pollwake,只是将本进程唤醒,本进程需要重新遍历全部的fd检查事件,然后保存事件,拷贝到用户空间,函数返回。

3.每次循环都是对全部的监测的fd进行轮询检测,可能发生事件的fd很少,这样效率很低。

4.当有事件发生,需要返回时,也需要将全部fd的事件进行返回,而其中可能只有很少的fd有事件发生。

5.select/poll返回时,会将该进程从全部监听的fd的等待队列里移除掉,这样就需要select/poll每次都要重新传入全部监听的fd,然后重新将本进程挂载到全部的监测fd的等待队列,大量重复劳动,效率很低。

select与epoll的不同点详解

epoll的实现原理

epoll的四大三大重要函数

epoll_create()    //创建epoll对象,同时创建epoll使用到的fd_set集合,底层使用的是红黑树(方便查询,增删fd)
epoll_ctl()    //操作fd_set,有EPPOLL_ADD和EPOLL_DEL操作,执行fd_set的增删
epoll_wait()  //查看就绪队列是否有准备就绪的fd时间,唤醒等待进程,返回就绪队列

特点:

1.每次累加添加,不需要每次传入全部的监测fd。

2.每个fd只将本进程挂载到自己的等待队列一次,直到该fd被从epoll移除,不需要重复挂载。

3.fd事件回调函数是ep_epoll_callback,该函数将发生事件的fd加入到epoll专门的就绪队列rdllist中,同时唤醒本进程。

4.本进程不需要遍历每一个fd去监测事件是否发生,而只需要判断epoll中的就绪队列rdllist是否为空即可。

5.epoll返回时,只返回就绪队列rdllist中的项,避免了无关项的操作,应用层也就不需要再次重复遍历。

6.epoll内部使用红黑树存储监测fd,支持大量fd的快速查询、修改和删除操作。

总结

一. epoll与select/poll机制的相同点:

  1. 主要监测流程是一样的,都需要将当前进程挂载到对应fd的队列中去。如果fd有事件发生,调用挂载的回调函数,该回调函数基本的作用是唤醒本进程。

  2. 主事件检测循环是一样的,循环检测是否有事件发生,有则处理事件后返回;没有则调用schedule_timeout睡眠一会。不同的是,select/poll直接检测每个fd,而epoll只需检测就绪队列rdllist是否有数据即可。

二. epoll针对select/poll的痛点进行的修改,也就是高效之处总结:

  1. select/poll把fd的监听列表放在用户空间,由用户空间管理,导致在用户空间和内核空间之间频繁重复拷贝大量fd;epoll在内核建立fd监听列表(实际是红黑树),每次通过epoll_ctl增删改即可。

  2. select/poll每当有fd内核事件时,都唤醒当前进程,然后遍历监听列表全部fd,检查所有就绪fd并返回;epoll在有fd内核事件时,通过回调把该fd放到就绪队列中,只需返回该就绪队列即可,不需要每次遍历全部监听fd。

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