linux io模式

184 阅读4分钟

1.概念

io

io:文件流的输入输出。网络IO,磁盘IO,都是io,一般只有内核才能访问。用户软件需要调用接口才能访问。

网络IO

  1. 等待网络数据到达网卡->读取到内核缓冲区
  2. 从内核缓冲区复制数据 –> 用户空间

磁盘IO

  1. 当进程IO系统调用后,内核会先看缓冲区中有没有相应的缓存数据。
  2. 如果没有的话,再到设备中读取,因为设备IO一般速度较慢、需要等待。
  3. 如果内核缓冲区有数据,则直接复制到用户空间

缓存IO

数据会先复制到内核,最后才从内核复制到用户进程。

  • 缺点,重复了一次复制,
  • 优点:如果有一样的数据重新操作,直接利用缓存

内核空间和用户空间

  • 内核空间:即可以访问用户空间也可以访问内核空间,主要用户直接访问io的读写
  • 用户空间:只能访问用户空间的内容,访问io只能调用内核api

同步与异步

同步:当调用方法时,只要没有返回会一直等待,直到返回才结束。(当等待过久操作系统有权切换cpu去执行其他进程) 异步:当调用方法时,立即返回,不用等待返回的结果,但是调用者会主动定期的轮询访问是否完成。(消耗cpu资源,cpu也不能切换其他进程)

2. 5种IO的模式

  • 同步: 阻塞/非阻塞/多路复用/信号驱动 IO
  • 异步: 异步IO

blocking 阻塞

用户进程 -> 调用内核方法   -> 内核无数据 
                          -> 内核开始准备数据
                          -> 准备好数据
                          -> 内核复制数据到用户态
                          -> 内核复制完成
用户进程 <-  返回成功

用户进程需要等待内核"准备数据"和"复制数据到用户态"这两步。

  • 优点:如果时间过去cpu可以切换到其他进程处理任务。
  • 缺点:是完全阻塞,期间需要等待很久。

Non Blocking 非阻塞

用户进程 -> 调用内核方法/返回失败   -> 内核无数据 
用户进程 <->  返回失败             -> 内核开始准备数据
用户进程 <->  返回失败             -> 准备好数据
用户进程 <->  返回失败             -> 内核复制数据到用户态
用户进程 <->  返回失败             -> 内核复制完成
用户进程 <->  返回成功

用户进程发起read,然后直接返回失败,后续不断轮询请求是否生成

  • 优点:用户请求完可以立马去操作别的事情
  • 缺点:由于是轮询,完成的时候会比实际慢,cpu频繁轮询,导致不能切换进程

multiplexing 多路复用/事件驱动

非阻塞是当前用户进程不断轮询检查。多路复用是专门交给一个进程的统一处理状态检查。一次可以处理多个IO,一次性返回。

常见的有 select、poll、epoll函数。

  1. 用户进程先依赖select,等内核数据准备好,select触发可读条件,
  2. 用户进程再把内核态复制到用户态(这里是阻塞)
select  -> 调用内核方法   -> 内核无数据 
                         -> 内核开始准备数据
用户进程 <-   返回可读条件  -> 准备好数据
用户进程 ->  系统调用     -> 内核复制数据到用户态
                          -> 内核复制完成
用户进程 <->  返回成功
  • 优点:当连接数大的时候,性能就很好
  • 缺点:当连接数小的时候,由于另外多调用了select函数效果比非阻塞还要差。

signal dirver 信号驱动

  1. 用户进程先注册SIGIO和回调方法,等待内核处理好数据发送SIGIO信号,触发注册回调的方法。
  2. 用户进程再把内核态复制到用户态(这里是阻塞)
用户进程       <-> 注册SIGIO/同时立即返回   -> 内核无数据 
                                          -> 内核开始准备数据
信号处理的函数  <-   发送SIGIO信号          -> 准备好数据
用户进程        ->  系统调用               -> 内核复制数据到用户态
                                          -> 内核复制完成
用户进程 <->  返回成功

asyn 异步

aio_read -> 调用内核方法/立即返回    -> 内核无数据 
                                    -> 内核开始准备数据
                                    -> 准备好数据
                                    -> 内核复制数据到用户态
                                    -> 内核复制完成
信号处理 <-  调用aio_read的指定的信号
返回成功&用户进程处理

用户进程发起aio_read,立即返回,继续做其他事情 内核会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,内核最后通过aio_read信号 触发方法,返回成功,用户进程完成调用。