用户空间和内核空间
文件资源 设备资源 CPU资源 磁盘资源都是属于系统资源,IO需要内核态来完成
IO效率受什么影响?
频繁的用户态和内核态切换
用户空间缓冲区和内核空间缓冲区的资源复制
阻塞IO
对这两个步骤的优化就是Linux的5中IO模型
阻塞IO就是两个阶段都需要用户应用阻塞等待
非阻塞IO
非阻塞IO的recvfrom操作会立即返回结果而不阻塞用户进程
一阶段不阻塞,内核缓冲区无数据直接返回失败(用户态接受失败后会再次请求系统资源,属于是忙轮询,会导致CPU空转,CPU使用率暴增);二阶段阻塞从内核缓冲区拷贝数据到用户缓冲区(一阶段不阻塞 、 二阶段阻塞)
IO多路复用
文件描述符(File Descriptor):简称FD,是一个从0开始递增的无符号整数,用来关联Linux中的一个文件。在Linux中,一切皆文件,例如常规文件、视频、硬件设备等,当然也包括网络套接字(Socket)。
IO多路复用:是利用单个线程来同时监听多个FD,并在某个FD可读、可写时得到通知,从而避免无效的等待,充分利用CU资源。
IO多路复用一二阶段都是阻塞性操作,相比于阻塞IO、非阻塞IO,IO多路复用避免了无效的等待(阻塞IO、非阻塞IO都是等待一个资源数据,当其他数据准备完成时候,等待的没有完成,还是会陷入等待,浪费CPU资源)
IO多路复用有三种基本的实现方式:
select
select模式存在的问题:
1.fd_set需要从用户空间拷贝到内核空间,还要从内核空间拷贝到用户空间
2.select方法返回值为有几个就绪的数据,需要遍历fd_set来获取具体是哪几个就绪了
3.fd_set的长度受限,导致监听数量受限
poll
实现步骤:
创建一个pollfd数组,添加要监听的fd信息到数组中
调用poll函数,pollfd数组会从用户空间传入到内核空间,转为链表存储,无上限
内核遍历fd数组,判断是否就绪,就绪了就修改数组元素的revents状态
遍历结束后,拷贝数组到用户空间,返回就绪fd的数量
用户进程判断返回值是否>0
大于0则遍历数据获取就绪的fd
与select相比:
增加了能够监听的fd的数量
epoll
epoll解决了哪些问题:
1.不用让监听列表在用户空间和内核空间来回拷贝;epoll拆分为两部分:往内核空间中添加监听的fd(一个fd只需要监听一次就永久添加到了监听队列中,而select和poll每次监听都要拷贝所有的监听列表),从内核空间中获取就绪的fd(拷贝的数量少了,select、poll都是所有的都拷贝);
2.select和poll返回的都是就绪的数量;而epoll即返回就绪的数量,也返回就绪的fd
3.监听fd使用红黑树结构来替代链表,既做到了无限的添加数量,也做到了增删改查的高性能
IO多路复用-事件通知机制
当fd中有数据可读,我们调用epoll_wait就可以得到通知,通知模式分为两种
1.LT,当就绪链表中有数据可读,每次调用epoll_wait都会报告未处理的FD,直到应用程序处理了这个FD;
2.ET,当就绪链表中有数据可读,epoll_wait()仅在首次调用时报告此状态。如果应用程序没有及时处理这个FD,那么即使该FD仍然处于就绪状态,也不会在后续的epoll_wait()调用中再次被报告,(因为在第一次调用epollwait时候,就绪链表就会断开链表,如果链表中的节点没有处理完,ET模式下不会自动将未处理玩的节点加入到就绪队列(可以手动添加回去epoll_ctl 或者是 控制每次都处理完成所有数据,例如通过循环非阻塞IO来读取fd就绪链表中的数据),而LT模式下会自动将未处理完成的节点添加回就绪队列)直到该FD的状态发生变化
epollwait可以获取就绪fd的数量,以及就绪的fd,但是从内核空间向用户空间每次的拷贝大小都是有限的,这就代表每次能读取的就绪fd是有限的,当时ET模式时候,就可能会丢事件,而LT模式只要fd没出被处理完成,下次调用epollwait依旧会报告这个fd,但是重复通知会影响效率
信号驱动IO
异步IO
异步IO的整个过程都是非阻塞的,用户进程调用完异步API后就可以去做其它事情,内核等待数据就绪并拷贝到用户空间后才会递交信号,通知用户进程。
高并发情况下,内核空间容易内存溢出