前置知识:
计算机系统中,应用是没办法直接操纵内存的,应用属于用户态,操纵内存(硬件)需要内核态,也是防止用户态权限过大
所有IO
- 阻塞IO(Blocking IO)
- 非阻塞IO(Nonblocking IO)
- IO多路复用(IO Multiplexing)
- 信号驱动IO(Signal Driven IO)
- 异步IO(Asynchronous IO)
阻塞IO(Blocking IO)
应用是无法直接从磁盘上读取数据的,分为两步:
这是一个阻塞的过程,两个阶段都要等待
非阻塞IO(Nonblocking IO)
IO多路复用(IO Multiplexing)
文件描述符(File Descriptor):简称FD,是一个从0 开始的无符号整数,用来关联Linux中的一个文件。在Linux中,一切皆文件,例如常规文件、视频、硬件设备等,当然也包括网络套接字(Socket)。
通过FD,我们的网络模型可以利用一个线程监听多个FD,并在某个FD可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。
监听的多种方式: select、poll、 epoll
其中select和pool相当于是当被监听的数据准备好之后,他会把你监听的FD整个数据都发给你,你需要到整个FD中去找,哪些是处理好了的,需要通过遍历的方式,所以性能也并不是那么好
而epoll,则相当于内核准备好了之后,他会把准备好的数据,直接发给你,咱们就省去了遍历的动作。
epoll模式中如何解决这些问题的?
- 基于epoll实例中的红黑树保存要监听的FD,理论上无上限,而且增删改查效率都非常高
- 每个FD只需要执行一次epoll_ctl添加到红黑树,以后每次epol_wait无需传递任何参数,无需重复拷贝FD到内核空间
- 利用ep_poll_callback机制来监听FD状态,无需遍历所有FD,因此性能不会随监听的FD数量增多而下降
信号驱动IO
这种相当于 用户态告诉内核态:我要这个的数据,我看你这里没有,搞好了告诉我,我先去搞别的东西。等东西搞好了,内核态通知用户态可以来拿数据了,用户态调用recvfrom(阻塞)并返回数据
异步IO
这一步就是最后数据是由内核态直接传给用户态的
零拷贝
比如我现在要做一个文件传输系统,系统的文件流程大概是这样的:
(DMA是Direct Memory Access 直接访问内存的一个技术)
可以看到文件反复复制,拷贝了4次
进程状态切换了四次
零拷贝:
优化的几个点:
mmap()
系统调用函数会直接把内核缓冲区里的数据映射到用户空间,这样,操作系统内核与用户空间就不需要再进行任何的数据拷贝操作。linux内核提供了一个sendfile函数,又可以优化一些:
所谓零拷贝,我认为就是这一层“用户态和内核态之间”零拷贝吧
详细请看:www.xiaolincoding.com/os/8_networ…
图源:黑马Redis & 小林coding