Linux - IO 模型

1、简介

  本文重点描述了 IO 模型的 阻塞、同步非阻塞、多路复用、epoll共享空间 这一路的过程,简略介绍了sendfile、 及sendfile和共享空间之间的区别 以下正片,全文阅读完可能需要5 ~ 8分钟时间 ;

2、谈epoll 之前的铺垫

 突然一下子还不知道怎么开始,想了想还是从 linux 的 IO模型 演化过程来简单聊聊;看之前不妨大家带着几个问题:
 最初 IO模型 是怎样处理的?
 IO模型 有哪些演变过程?
 每次演化都解决了什么问题?
 select 是做什么的?
 select和epoll有什么区别?
 linux有 AIO吗?
 epoll 中的共享空间是零拷贝吗?
 共享空间和sendfile有什么不同

3、谈谈 IO 吧

  3.1 最初是什么样?

 每个计算机是有内核;内核向下有很多连接,这些连接就是客户端,通过文件描述符(fd)连接到内核(一个连接一个文件描述符);
 早先 程序的线程/进程 通过 read 命令 读 指定的fd 到内核 ,这个时期socket 就是 blocking (BIO) ------也就是,抛出一个线程,读网卡连接,有数据就会返回数据,如果没有就一直阻塞着,下面的也就执行不了,只能抛出更多的线程,如果只有一颗CPU的话,某一时间片上某一时间点上就只能有一个线程处理,例如: fd1 的数据到了 ,但fd2 的数据还没到 这样就CPU的利用率就不会很高,如果描述的比较饶口,试试下面的图:
在这里插入图片描述

  3.2 同步非阻塞 (NIO) 时期

 这一时期,socket (fd)升级为 NIO,不阻塞只跑一个 线程,通过在线程,尽量不切换,同时内部写个while死循环,先遍历 fd1 当没有程序时在遍历 fd2 ,当有返回时开始处理,处理完后在read fd1,fd1有就返回结果,开始处理,…
  以上轮循发生在用户空间,以上就是非阻塞IO的实现,当前由于只有自己去遍历取出来处理,所以还只是同步阶段,所以也是同步非阻塞时期;如图:
在这里插入图片描述

  3.3 多路复用 NIO

  上面提高了 使 CPU 更加繁忙起来了,但是这时就会有个问题: 如果有 1000个 fd 那就代表:
自己的用户空间,查询一回文件表述符,就要进行一次系统调用,得内核,用户态内核态切换,cpu保护现场,恢复现场…,这样就需要1000 次 ,就会带出成本问题,怎么解决? 这种情况下如果不去改变内核,可能也没有办法继续优化;
  基于此内核中就增加了一个系统调用,这个系统调用早先就叫做 select,这样用户空间直接调用新的进程调用(select):统一将所有 文件表述符 传给select,然后 内核 统一监控,返回 需要操作的文件表述符,用户空间在拿着返回的文件描述符,找到里面能用的fd(哪些数据到达了,哪些是缓冲区有的) 去调用read,这样就保证 read不会调用 没有数据 的 fd ,看图:
在这里插入图片描述

  3.4 多路复用 NIO - 2 (伪AIO)

 老规矩先说上面的可以优化的地方,首先,线程进程都是在 用户态中,比如里面放着1000个文件表述符 二进制 ,而调内核的时候传参,需要将数据从 用户态拷贝到 内核态,PS:这里的数据是指 内核和用户进程沟通 “IO里是否有数据” 的数据 ; 面对fd 相关数据 需要考来考去有什么解决方案呢,众所周知,目前linux 内核还没实现AIO,只有windows实现了AIO ,但是可以通过伪AIO 实现;
 之前,用户态有用户态的内存地址空间,内核有内核的内存地址空间,其实这些都是虚拟地址空间,站在物理内存上无非就两个区域,只不过内核的区域,用户进程没办法访问,所以只能通过传参将数据传过去, 就不可避免的需要考来考去,这时, 内核开辟一个单独的空间,并且空间也属于用户态空间,当用户进程在自己8号位置写了数据相当于间接写在共同空间,在调用 进程调用时,不需要将数据传递拷贝,只需要将对应指针传递,进程调用通过 CUP 解释即可找到自己这边对应的位置如 6号位置,附一点: 例如 公共空间的数据和 用户态,内核态是双向绑定,同一个数据,可能用户态是绑定 7号位置,而内核态绑定的是3号位置;
 这样就通过共享空间,免除了数据多次拷贝的问题,而共享空间是通过mmap调用,同时用户程序则在共享空间中 放了一些数据结构; 首先 epoll 是一个大的概念,里面有三个调用,create, read ,用户空间 通过create 给一个 epoll 的文件标识符,epoll会准备一个共享空间,是mmap ,现将所有的fp都注册到共享空间,注意:共享空间的增删改是内核来完成的,查询是用户进程完成,用户进程 可能会 ctl add del 追加或删除文件描述符,和wait 方法,用来等待事件,总结下就是:用户空间先create, epoll 就把 文件描述符 注册到红黑树,当某个文件描述符 数据到了之后会把 它从红黑树放到链表中并维护数据是可写还是可读,只要链表中有了,wait()就可以从阻塞变成不阻塞 取那个链表把实际到达的返回,因为是共享的所以不用拷贝到用户进程,用户进程还需要单独去调wait() read() ,方法所以 epoll也是NIO 而不是 AIO ; 图片如下:
在这里插入图片描述

  3.5 epoll 怎么样才能算真AIO

  如果是AIO: 用户进程 不是直接去调的 read 而是先把 fd 先注册到 read,也不管 数据什么时候到的,整个读写过程都是在内核中完成,最后返回了一个消息或者一个通知,或者用户态有个线程池,回调用户态的方法把 返回 压到线程栈中,关键是这段时间,主线程在做自己的事,两边的事也没有关系;

  3.6 谈谈 sendfile

  好啦,最后一个 sendfile,首先sendfile 是零拷贝的,而epoll 是共享内存,两个是不一样的,首先sendfile 需要两个参数,一个 输入 fd 一个输出 fd 类似于 阿帕奇中的 out in 一个概念 ,举个实际例子: 有个文件和一个网卡,文件是 文件标识符 fd1,网卡是socket 标识符 fd2,之前 内核先是 把 fd1 数据read 到用户态,用户态在 把数据 write到 内核,在通过内核发出去,中间需要拷贝的过程, 但是有 sendfile则是 用户进程直接调用 sendfile, 内核直接拿着缓冲区 直接读 fd1 ,放到缓冲区,然后直接发出去;就不需要程序拷来拷去了;图片如下:
在这里插入图片描述

4、结尾

  如有不准确的地方,希望可以留下您宝贵的意见 ,拜谢!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值