IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> IO 杂记~绿叶 -> 正文阅读

[系统运维]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注册即可.

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-03-13 22:13:16  更:2022-03-13 22:16:46 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/16 2:23:20-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码