用户态和内核态
我们无论是从磁盘中读数据还是从网络(网卡)中读数据,都是用户态发起系统调用进入内核态,内核态中等待数据从磁盘/网卡中拷贝过来,然后再将数据拷贝回用户态,完成数据读取
其中影响到数据读取性能主要是:①内核态数据等待 ②用户态和内核态之间数据的拷贝
为什么有IO多路复用?
现在有以下5种IO模式:
阻塞IO(BIO):用户进程发起系统调用,要去阻塞等待数据就绪,数据就绪之后还要阻塞等待数据拷贝读取数据
非阻塞IO:用户进程发起系统调用,如果内核中没数据,就不会等待然后会直接返回一个错误信息。然后他会一直去调用一直去看数据是否就绪(会导致CPU空转)。但是如果有数据之后,进行数据拷贝返回数据时还是会阻塞。
因为阻塞IO的两次阻塞等待,以及非阻塞IO不断发起系统调用导致的CPU空转,使得IO操作效率很慢,所以IO多路复用横空出世
IO多路复用
IO多路复用:核心思想是通过一个线程去监视多个连接(FD),一旦某个连接就绪(触发读写事件),就去通知对应的应用程序去处理该连接。减少用户态和内核态之间的切换,先去判断哪个FD就绪再recvfrom去调用获得数据
IO多路复用有多种实现方式:select poll epoll。差异在于select和poll只会通知用户进程有FD就绪,但是具体是哪个就需要用户进程遍历来确认。而epoll在通知用于进程FD就绪的同时,把已就绪的FD写入用户空间。
Select模式
Poll模式
Epoll模式
每一个FD都有Callback回调,每当就绪就直接其添加到list列表中。Epoll_wait阻塞等待检查就绪列表,有就绪的列表元素就返回结果拷贝到空数组
epoll模式下的EL模式和LT模式:
三种模式比较
①在select模式中和poll模式中每次调用都需要把要监听的FD全都拷贝到内核,然后内核处理完还要全部拷贝到用户空间。而在epoll模式中,只需要通过epoll_ctl方法把要监听的FD拷贝到内核放到红黑树中,不需要每次调用再把重复的FD再放进来,只放一次就可以了。减少了拷贝的次数,数量。
②在select模式中和poll模式中每次都要把所有的FD都拷贝回用户空间,然后让其去遍历寻找就绪的FD。而在epoll中就只需要通过epoll_wait监听链表,如果有数据就拷贝已经就绪的FD即可。
③select模式中因为使用的是数组最多监听1024个FD,poll虽然采用了链表,理论上无上限,但是当链表长度非常长,你遍历的时候则会花费大量时间。而在epoll模式中,FD是放在红黑树中,数量肯定是可以存很多,但是遍历(增删改查)红黑树的时间复杂度不会随着数量的增多而增加,都是O(log n)。