IO 杂记~绿叶

IO 杂记

1.用户空间 和 内核空间

Kernel space 是 Linux 内核的运行空间, User space 是用户程序的运行空间. 为了安全, 它们是隔离的, 即使用户的程序崩溃了, 内核也不受影响.

ni: niceness 的缩写, CPU 消耗在 nice 进程 (低优先级) 的时间百分比.

id: idle 的缩写, CPU 消耗在闲置进程的时间百分比, 这个值越低, 表示 CPU 越忙.

wa: wait 的缩写, CPU 等待外部 I/O 的时间百分比, 这段时间 CPU 不能干其他事, 但是也没有执行运算, 这个值太高就说明外部设备有问题.

hi: hardware interrupt 的缩写, CPU 响应硬件中断请求的时间百分比.

si: software interrupt 的缩写, CPU 响应软件中断请求的时间百分比.

st: stole time 的缩写, 该项指标只对虚拟机有效, 表示分配给当前虚拟机的 CPU 时间之中, 被同一台物理机上的其他虚拟机偷走的时间百分比.

2. PIO与DMA

​ PIO 我们拿磁盘来说, 很早以前, 磁盘和内存之间的数据传输是需要CPU控制的, 也就是说如果我们读取磁盘文件到内存中, 数据要经过CPU存储转发, 这种方式称为PIO.

​ DMA 后来, DMA(直接内存访问, Direct Memory Access) 取代了PIO, 它可以不经过CPU而直接进行磁盘和内存 (内核空间) 的数据交换. 在DMA模式下, CPU只需要向DMA控制器下达指令, 让DMA控制器来处理数据的传送即可, DMA控制器通过系统总线来传输数据, 传送完毕再通知CPU, 这样就在很大程度上降低了CPU占有率, 大大节省了系统资源.

3. 缓存IO 和 直接IO

3.1 缓存IO

​ 在Linux的缓存I/O机制中, 数据先从磁盘复制到内核空间的缓冲区, 然后从内核空间缓冲区复制到应用程序的地址空间.

1. 读操作

​ 操作系统检查内核的缓冲区有没有需要的数据, 如果已经缓存了, 那么就直接从缓存中返回;否则从磁盘中读取, 然后缓存在操作系统的缓存中.

2. 写操作

​ 将数据从用户空间复制到内核空间的缓存中. 这时对用户程序来说写操作就已经完成, 至于什么时候再写到磁盘中由操作系统决定, 除非显示地调用了sync同步命令.

3. 优点

​ 在一定程度上分离了内核空间和用户空间, 保护系统本身的运行安全; 可以减少读盘的次数, 从而提高性能.

4. 缺点

​ 在缓存 I/O 机制中, DMA 方式可以将数据直接从磁盘读到页缓存中, 或者将数据从页缓存直接写回到磁盘上, 而不能直接在应用程序地址空间和磁盘之间进行数据传输. 数据在传输过程中需要在应用程序地址空间 (用户空间) 和缓存 (内核空间) 之间进行多次数据拷贝操作, 带来的CPU以及内存开销是非常大的.

3.2 直接IO

​ 直接IO就是应用程序直接访问磁盘数据, 而不经过内核缓冲区, 也就是绕过内核缓冲区,自己管理I/O缓存区, 这样做的目的是减少一次从内核缓冲区到用户程序缓存的数据复制.

​ 引入内核缓冲区的目的在于提高磁盘文件的访问性能, 因为当进程需要读取磁盘文件时, 如果文件内容已经在内核缓冲区中, 那么就不需要再次访问磁盘;而当进程需要向文件中写入数据时, 实际上只是写到了内核缓冲区便告诉进程已经写成功, 而真正写入磁盘是通过一定的策略进行延迟的.

​ 数据库服务器, 它们为了充分提高性能, 希望绕过内核缓冲区, 由自己在用户态空间实现并管理I/O缓冲区, 包括缓存机制和写延迟机制等, 以支持独特的查询机制, 比如数据库可以根据更加合理的策略来提高查询缓存命中率. 另一方面, 绕过内核缓冲区也可以减少系统内存的开销, 因为内核缓冲区本身就在使用系统内存.

​ Linux提供了对这种需求的支持, 即在open()系统调用中增加参数选项O_DIRECT, 用它打开的文件便可以绕过内核缓冲区的直接访问, 这样便有效避免了CPU和内存的多余时间开销.

4. IO 访问方式

4.1 磁盘IO

​ 当应用程序调用read接口时, 操作系统检查在内核的高速缓存有没有需要的数据, 如果已经缓存了, 那么就直接从缓存中返回, 如果没有, 则从磁盘中读取, 然后缓存在操作系统的缓存中.

​ 应用程序调用write接口时, 将数据从用户地址空间复制到内核地址空间的缓存中, 这时对用户程序来说, 写操作已经完成, 至于什么时候再写到磁盘中, 由操作系统决定, 除非显示调用了sync同步命令.

4.2 网络IO

普通的网络传输步骤如下:

​ 1)操作系统将数据从磁盘复制到操作系统内核的页缓存中.

​ 2)应用将数据从内核缓存复制到应用的缓存中.

​ 3)应用将数据写回内核的Socket缓存中.

​ 4)操作系统将数据从Socket缓存区复制到网卡缓存, 然后将其通过网络发出.


​ 1.当调用read系统调用时, 通过DMA(Direct Memory Access) 将数据copy到内核模式.

​ 2.然后由CPU控制将内核模式数据copy到用户模式下的 buffer中.

​ 3.read调用完成后, write调用首先将用户模式下 buffer中的数据copy到内核模式下的socket buffer中

​ 4.最后通过DMA copy将内核模式下的socket buffer中的数据copy到网卡设备中传送. 从上面的过程可以看出, 数据白白从内核模式到用户模式走了一圈, 浪费了两次copy, 而这两次copy都是CPU copy, 即占用CPU资源.

4.3 磁盘 IO vs 网络IO

​ 磁盘IO主要的延时是由(以15000rpm硬盘为例) :

​ 机械转动延时机械磁盘的主要性能瓶颈, 平均为2ms) + 寻址延时(2~3ms) + 块传输延时(一般4k每块, 40m/s的传输速度, 延时一般为0.1ms) 决定. (平均为5ms)

​ 而网络IO主要延是由: 服务器响应延时 + 带宽限制 + 网络延时 + 跳转路由延时 + 本地接收延时 决定. (一般为几十到几千毫秒, 受环境干扰极大)

5. 同步IO 和 异步IO

​ 同步和异步是针对 应用程序和内核的交互 而言的

​ 同步指 用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪, 而异步指 用户进程触发IO操作以后便开始做自己的事情

​ 用户空间要的数据, 必须等到内核空间给它才做其他事情; 用户空间要的数据, 不需要等到内核空间给它, 才做其他事情

6. 阻塞IO 和 非阻塞IO

​ 阻塞方式下读取或者写入函数将一直等待, 而非阻塞方式下, 读取或者写入函数会立即返回一个状态值

用户和内核空间IO操作的方式

​ 堵塞: 用户空间通过系统调用(systemcall)和内核空间发送IO操作时, 该调用是堵塞的

​ 非堵塞: 用户空间通过系统调用 (systemcall) 和 内核空间发送IO操作时, 该调用是不堵塞的, 直接返回的, 只是返回时, 可能没有数据而已

7. IO 模型

IO模式BIONIOAIO
同步阻塞同步非阻塞异步非阻塞
实现难度easyhardhard
可靠性
吞吐量
  • 同步阻塞IO (Blocking IO): 即传统的IO模型

  • 同步非阻塞IO (Non-blocking IO): 默认创建的socket都是阻塞的, 非阻塞IO要求socket被设置为NONBLOCK.

  • IO多路复用(IO Multiplexing) : 即经典的Reactor设计模式, 有时也称为异步阻塞IO

  • 异步IO(Asynchronous IO) : 即经典的Proactor设计模式, 也称为异步非阻塞IO

7.1 同步阻塞IO BIO

​ 同步阻塞IO模型是最简单的IO模型, 用户线程在内核进行IO操作时被阻塞.

​ 用户线程通过系统调用read发起IO读操作, 由用户空间转到内核空间. 内核等到数据包到达后, 然后将接收的数据拷贝到用户空间, 完成read操作

​ 即用户需要等待read将socket中的数据读取到buffer后, 才继续处理接收的数据. 整个IO请求的过程中, 用户线程是被阻塞的, 这导致用户在发起IO请求时, 不能做任何事情, 对CPU的资源利用率不够

缺点

​ IO代码里read操作是阻塞操作, 如果连接不做数据读写操作会导致线程阻塞, 浪费资源

​ 如果线程很多, 会导致服务器线程太多, 压力太大, 比如C10K问题

7.2 同步非阻塞IO NIO

​ 将socket设置为NONBLOCK. 这样做用户线程可以在发起IO请求后可以立即返回

​ 由于socket是非阻塞的方式, 因此用户线程发起IO请求时立即返回. 但并未读取到任何数据, 用户线程需要不断地发起IO请求, 直到数据到达后, 才真正读取到数据, 继续执行

​ 整个IO请求的过程中, 虽然用户线程每次发起IO请求后可以立即返回, 但是为了等到数据, 仍需要不断地轮询、重复请求, 消耗了大量的CPU的资源

8.3 IO多路复用

​ 建立在内核提供的多路分离函数select基础之上的, 使用select函数可以避免同步非阻塞IO模型中轮询等待的问题

​ 用户首先将需要进行IO操作的socket添加到select中, 然后阻塞等待select系统调用返回. 当数据到达时, socket被激活, select函数返回. 用户线程正式发起read请求, 读取数据并继续执行

​ 使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求. 用户可以注册多个socket, 然后不断地调用select读取被激活的socket, 即可达到在同一个线程内同时处理多个IO请求的目的

​ 其中while循环前将socket添加到select监视中, 然后在while内一直调用select获取被激活的socket, 一旦socket可读, 便调用read函数将socket中的数据读取出来

​ 使用select函数的优点并不仅限于此. 虽然上述方式允许单线程内处理多个IO请求, 但是每个IO请求的过程还是阻塞的(在select函数上阻塞) 
优化: 如果用户线程只注册自己感兴趣的socket或者IO请求, 然后去做自己的事情, 等到数据到来时再进行处理, 则可以提高CPU的利用率


​ 通过Reactor的方式, 可以将用户线程轮询IO操作状态的工作统一交给handle_events事件循环进行处理. 用户线程注册事件处理器之后可以继续执行做其他的工作(异步), 而Reactor线程负责调用内核的select函数检查socket状态. 当有socket被激活时, 则通知相应的用户线程(或执行用户线程的回调函数), 执行handle_event进行数据读取、处理的工作

​ 由于select函数是阻塞的, 因此多路IO复用模型也被称为异步阻塞IO模型. 注意, 这里的所说的阻塞是指select函数执行时线程被阻塞, 而不是指socket.

void UserEventHandler::handle_event() { 
    if(can_read(socket)) {
        read(socket, buffer);
        process(buffer);
    }
}

{
    Reactor.register(new UserEventHandler(socket));
}

​ 用户需要重写EventHandler的handle_event函数进行读取数据、处理数据的工作, 用户线程只需要将自己的EventHandler注册到Reactor即可.

Reactor::handle_events() {
    while(1) {
        sockets = select();
        for(socket in sockets) {
            get_event_handler(socket).handle_event(); 
        }
    }
}
8.4 异步IO

​ 异步IO模型中, 当用户线程收到通知时, 数据已经被内核读取完毕, 并放在了用户线程指定的缓冲区内, 内核在IO完成后通知用户线程直接使用即可


​ 用户线程直接使用内核提供的异步IO API发起read请求, 且发起后立即返回, 继续执行用户线程代码. 不过此时用户线程已经将调用的AsynchronousOperation和CompletionHandler注册到内核, 然后操作系统开启独立的内核线程去处理IO操作. 当read请求的数据到达时, 由内核负责读取socket中的数据, 并写入用户指定的缓冲区中. 最后内核将read的数据和用户线程注册的CompletionHandler分发给内部Proactor, Proactor将IO完成的信息通知给用户线程(一般通过调用用户线程注册的完成事件处理函数), 完成异步IO
void UserCompletionHandler::handle_event(buffer) {
    process(buffer);
}
{ 
    aio_read(socket, new UserCompletionHandler);
}

​ 用户需要重写CompletionHandler的handle_event函数进行处理数据的工作, 参数buffer表示Proactor已经准备好的数据, 用户线程直接调用内核提供的异步IO API, 并将重写的CompletionHandler注册即可.

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值