你了解IO相关知识点吗?

你了解IO相关知识点吗?

IO

IO是什么?

我们都知道unix(like)世界里,一切皆文件,而文件是什么呢?文件就是一串二进制流而已,不管socket,还是FIFO、管道、终端,对我们来说,一切都是文件,一切都是流。在信息 交换的过程中,我们都是对这些流进行数据的收发操作,简称为I/O操作(input and output),往流中读出数据,系统调用read,写入数据,系统调用write。

IO源有哪些

磁盘IO

发送一条磁盘IO的指令, 指令一般是通知磁盘开始扇区位置,然后给出需要从这个初始扇区往后读取的连续扇区个数,同时给出动作是读,还是写。磁盘收到这条指令,就会按照指令的要求,读或者写数据。控制器发出的这种指令+数据,就是一次IO,读或者写。

磁盘IO的并发

一个磁盘同一时刻只能执行一条指令, 因此单磁盘并发度为0

内存IO

就是从内存中读写数据, 速度非常快, 通常不会成为性能瓶颈, 一般不考虑

设备IO

从一个外接设备写入或者读取数据, 设备IO需要考虑设备是否是个互斥资源. 互斥资源的IO某一时刻只能被一个线程占用.

网络IO

网络IO其实也属于设备IO的一种, 但是通常单独讨论. 网络IO也就是对网卡的读写, 也就是发送请求和接受请求在网卡上数据读写的IO. 主要就是利用socket套接字发生和接受数据.

数据拷贝

不同的IO源, 所遵循的数据拷贝都是一致的.

DMA控制器

DMA(Direct Memory Access,直接存储器访问) : 它是所有现代电脑的重要特色,它允许不同速度的硬件装置来沟通,而不需要依赖于 CPU 的大量中断负载。

你了解IO相关知识点吗?

传统IO操作

读操作

拷贝两次, 上下文切换两次

写操作

拷贝两次 上下文切换两次

零拷贝

于这种数据传输方式来说,应用程序可以直接访问硬件存储,操作系统内核只是辅助数据传输.也就是用户进程可以直接对磁盘或者内存进行读写. 这样数据就不需要拷贝了, 但是当然传统方式的一些好处必然也就需要舍弃

Linux 中提供类似零拷贝的系统调用主要有 mmap(),sendfile() 以及 splice()。

mmap() (读一次拷贝, 写不变)

一次拷贝发生在DMA会从磁盘或者内存将数据读入共享缓存区. 用户进程就可以直接使用共享缓存区中的数据.

和传统的区别就是read操作变成了mmap,之后用户空间会和内核态共享同一块内核缓存区,读入的数据都在这个内核缓存区里面。写入的话还是和原来一样。

sendfile() (读一次拷贝, 写两次次拷贝)

该方法适用于读取的数据可以直接写入到别的IO源里, 拷贝操纵直接在内核空间中完成, 用户进程不需要参与, 减少了上下文切换

读的一次拷贝发生在DMA会从磁盘或者内存将数据读入内核缓存区

写的两次拷贝

splice() (读一次拷贝, 写一次拷贝)

该方法适用于读取的数据可以直接写入到别的IO源里, 内存缓存和socket缓存间直接建立通道, 无需复制操作, 直接就可以互相访问.

读写就绪状态

(1) 读就绪状态

内核缓冲区中数据字节数大于等于用户进程请求读的字节数,此时系统可以将内核缓冲区的数据搬到用户缓冲区.

(2) 写就绪状态

内核缓冲区中剩余字节空间数(空闲空间)大于等于用户进程请求写的字节数,此时系统可以将用户缓冲区的数据搬往内核缓冲区.

也就是说一个读或写的过程,首先要经历一个读/写的就绪状态,读/写就绪后,才进行"真正"的IO,读就绪后,系统才能将内核缓冲区的数据搬到用户缓冲区;

写就绪后,系统才能将用户缓冲区的数据搬往内核缓冲区.

网络IO

网络IO作为java服务重点关注的情况, 无论是请求/相应, 还是数据库操作都通过网络IO完成

网络IO的特点

其它IO源在读取的时候, 基本不会有数据不存在的情况. 但是网络IO对于操作系统来说, 读写的都是网卡的内容, 网卡的内容能否被读取, 取决于是否有新的数据通过网络写入. 因此用户进程并不知道何时才能从网络IO获取数据, 因此就需要使用IO模型.

网络IO收取网络包的过程

www.easemob.com/news/5544

www.yuque.com/henyoumo/ik…

IO模型

IO模型适用于所有和IO源交互的情况, 但是对于网络IO来说, IO交互的等待时间可能无限长.

阻塞IO(BIO)

阻塞IO一个线程只能用来获取一个socket套接字的数据.

非阻塞IO(BIO)

非阻塞IO, 如果你在线程中维护多个socket连接的信息, 是可以实现和select()差不多的效果.

IO多路复用

IO多路复用是一种同步IO模型,一个线程监听多个IO事件,当有IO事件就绪时,就会通知线程去执行相应的读写操作,没有就绪事件时,就会阻塞交出cpu。

多路是指网络链接,复用指的是复用同一线程。

因为IO多路复用不止适用于套接字, 适用于所有文件描述符fd, 因此介绍时以fd来介绍

select()

用户线程维护一个数组, 记录所有感兴趣的fd(在socket中就是已经建立连接的所有套接字). 数组的大小有限制, 在32位系统中,最大值为1024个,而在64位系统中,最大值为2048个

(1) 线程不断调用select()方法, 将数组从用户空间拷贝到内核空间, 内核空间会按照数组检查一遍fd是否发生了IO事件(就是socket读队列有没有数据), 如果有, 就使该fd为就绪状态(此时不拷贝数据)

(2) select()方法返回, 进程遍历一遍该数组, 看看哪些fd是就绪状态, 如果就绪了, 就调用fd的对应方法, 将数据从内核空间拷贝到进程空间中.

poll()

进程维护一个链表, 因为是链表, 所以没有长度的限制.

其它的操作过程和select()方法一样. 性能没啥提升

epoll()

epoll就是对select和poll的改进了。它的核心思想是基于事件驱动来实现的,相当于提前建立好相应的数据结构 + 回调函数的使用, 使得不需要轮询, 而是只返回就绪的fd.

epoll操作实际上对应着有三个函数:epoll_create,epoll_ctr,epoll_wait.

epoll_create

epoll_create相当于在内核中创建一个存放fd的数据结构。在select和poll方法中,内核都没有为fd准备存放其的数据结构,只是简单粗暴地把数组或者链表复制进来;而epoll则不一样,epoll_create会在内核建立一颗专门用来存放fd结点的红黑树,后续如果有新增的fd结点,都会注册到这个epoll红黑树上。

epoll_ctr

select和poll会一次性将监听的所有fd都复制到内核中,而epoll不一样,当需要添加一个新的fd时,会调用epoll_ctr,给这个fd注册一个回调函数,然后将该fd结点注册到内核中的红黑树中。当该fd对应的设备活跃时,会调用该fd上的回调函数,将该结点存放在一个就绪链表中。这也解决了在内核空间和用户空间之间进行来回复制的问题。

epoll_wait

epoll_wait方法就是进程获取就绪fd的时候调用, 其实直接就是从就绪链表中取结点

epoll的工作流程

就算是epoll模型, 也需要线程去主动去获取数据, 即调用epoll_wait()方法, 此时就绪链表如果有数据, 那就直接返回, 如果没有数据, 线程就会进入阻塞状态, 然后当有数据后, 就会唤醒该线程, 获得数据的线程就会从epoll_wait()方法继续向后执行

何时选择select(), poll() 或者epoll()

并不是所有的情况中epoll都是最好的,比如当fd数量比较小的时候,epoll不见得就一定比select和poll好

AIO 异步IO

异步IO肯定不是阻塞的了, 异步乍一看和epoll回调类似, 但是epoll其实是等数据就绪了之后, 唤醒之前尝试获取数据的线程, 之前的线程在被唤醒前是一直阻塞的

jdk的IO演变历程

你了解IO相关知识点吗?


作者:用自己的话说
链接:https://juejin.cn/post/6954732511268175886

展开阅读全文

页面更新:2024-02-26

标签:上下文   缓冲区   知识点   线程   缓存   内核   磁盘   函数   进程   内存   操作   方法   数据   用户   科技   网络   空间

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top