前言
IO应该是每一个程序员都熟知的名词,啥?没听过?请把自己关小黑屋!但IO到底是个啥呢?操作系统到底有哪些IO操作呢?今天我来给大家唠唠操作系统的IO模型,如有错误之处,请不吝指正,欢迎拍砖。
先请大家看看下图:
哈哈,没想到吧,连鼠标、键盘都算是IO操作。
那广大程序员口中的IO指的是哪些呢?
我们通常所说的IO指的是磁盘IO和网络IO。举个简单的栗子:上传和下载,这里同时涉及到了网络IO和磁盘IO,我们从磁盘上读取文件(磁盘IO),然后通过网卡来传输数据(网络IO)!
IO分类
操作系统和IO设备的交互
轮询
轮询的原理不用解释了吧,就是一个for循环,操作系统不断的去询问IO设备的状态,有数据就执行IO操作。
栗子:
大家应该都有去医院检查的经历,检查完了,会给大家一个条形码,让大家自己去报告机上自己打印,然后大家就每过一段时间就拿着条形码去报告机上扫一下,看下好没好,好了就把报告打印出来;没好就继续等一会再来看下。
中断
当IO设备有数据时主动向操作系统发出一个中断信号,操作系统可以自己决定是否要响应该中断信号,如果响应,操作系统会中断已经在执行的操作转而去执行该设备的IO操作。
操作系统不需要主动去轮询IO设备的状态,只需要接受设备发送的中断信号即可。
栗子:
在一个风高夜黑的晚上,小张疲惫的离开办公室走在回家的路上,肚子饿的咕咕叫,在路边拐角有一个炒面摊,虽是深夜,生意竟还挺好。小张点了一份肉丝炒面、两个荷包蛋;点好后小张便找了张桌子坐下来一边打王者农药一边等,当小张正玩的兴起时,老板高喊:好了!小张只好放下手机去拿,结果只好了一个荷包蛋;小张不爽的拿着一个荷包蛋回来,继续打农药。刚刚进入状态,老板又喊好了,小张跑去一看,又是只好了一个荷包蛋;小张怒了:能不能都做好了再叫我!
DMA
在中断模式下,如果1个字节的数据中断一次,传1KB的数据得中断1024次,太浪费CPU时间,于是有了DMA方式。
DMA的出现就是为了解决批量数据的输入/输出问题。DMA是指外部设备不通过CPU而直接与系统内存交换数据的接口技术。这样数据的传送速度就取决于存储器和外设的工作速度。
栗子:
今天发了工资,小张决定奢侈一把去吃汉堡王,点了一份霸王鸡套餐,点好餐后小张便找了个座位坐下来并且告诉服务员自己坐哪,掏出手机一边玩吃鸡一边等,不一会服务员便端着餐盘过来了,里面放着所有的餐点,一样都不少
通道
DMA方式只能控制一个设备的一块数据,多块数据还是要CPU干预多次。于是有了通道来控制IO,它比DMA更强大,能控制多块数据,多个设备的IO,更加解放了CPU参与IO过程。
栗子:
有一天小张和同乡土豪小王聊天,说有没有这样一家店,既有肯德基的吮指原味鸡又有汉堡王的汉堡,还有麦当劳的甜点。小王一听,顿时感觉商机来了,于是在某商场租下了一大块店铺,
然后迅速和肯德基、汉堡王、麦当劳达成了合作协议,三家店铺同时入驻,然后小王又请了一批服务员。客户进店后,可以在点餐台同时点三家的餐点(既可以点肯德基的吮指原味鸡又可以点汉堡王的汉堡,还可以点麦当劳的甜点),点好餐后,服务员会根据客户的需求(某一家好了就送餐还是要三家都好了再送餐)在餐点准备好了后将餐点送到客户的桌位上。
操作系统和用户进程间的IO
同步阻塞
同步阻塞就是我们常说的BIO,在java中,传统的 java.io 包就是BIO的实现。
同步阻塞就是当用户线程发起io请求后,一直阻塞(阻塞io),直到数据就绪后,用户线程将数据写入socket空间,或从socket空间读取数据(同步)
栗子:
早上我们去十足便利店(一个便利店一般只有一个售货员,既是收银员也是取货员)买早餐,我们买了一个包子加一根烤肠,售货员一般都是先拿包子,然后放在收银台,然后再转身去拿烤肠,然后递给你,然后收钱,当售货员去取货时我们需要同步等待付款(哈哈,被阻塞住了吧),直到售货员取货回来。
同步非阻塞
同步非阻塞是我们常说的NIO。
说起NIO大家应该都很熟悉,但实际上它是在JDK1.4中才开始支持的;才JDK1.4之前,只能使用BIO。
NIO是线程发起io请求后,立即返回(非阻塞io)。用户线程不阻塞等待,但是,用户线程要定时轮询检查数据是否就绪,当数据就绪后,用户线程将数据从用户空间写入socket空间,或从socket空间读取数据到用户空间(同步)。
栗子:
年关将至,思乡心切的小张还能买到一张回家的车票,看着12306官网上显示的冷冰冰的已售罄三个字,内心仿佛有一万匹神兽在奔跑;好在热心的同乡小王给小张出了一个主意,你可以候补别人退票的啊,小张想了想好像很有道理,可是怎么知道别人什么时候退票呢?哎呀,你写个脚本去轮询查一下不就可以了吗?每秒去请求一下查询接口,不管有没有票都立即返回;如果有票,就调用下单接口!小张恍然大悟,然后熟练的打开了IDEA...
多路复用
IO多路复用模型是建立在内核提供的多路分离函数select基础之上的,使用select函数可以避免同步非阻塞IO模型中轮询等待的问题。现在大都数的IO场景都是基于NIO的多路复用实现的,例如Redis、netty等。
在NIO的实现中,需要用户线程轮询的去检查IO数据是否就绪,需要占用一定的CPU资源。而多路复用指的是一个(也可以使用线程池)线程管理多路IO,线程还是阻塞调用,其中一路或几路IO有数据了就返回。需要线程遍历全部IO,需要判断是哪个IO有数据(select的实现)。
在常见的程序多路复用设计中,会将检查IO数据是否就绪的任务,交给系统级别的select或poll、epoll(linux默认)模型,由系统进行监控,减轻用户线程负担。
栗子:
大家应该都去过肯德基,肯德基的就餐流程是什么样的呢?一般是的有多个收银员专门负责收钱和下单,顾客下单后,收银台会将订单传递给后厨,后厨将餐点准备好后通过叫号牌提醒顾客前来取餐。`
肯德基的就餐流程是不是和我们的IO多路复用的流程很像;将汉堡等食物类比为IO数据,将后厨类比为IO设备。一个(多个)收银员管理所有前来就餐的顾客,将顾客的需求转发给后厨,后厨准备好食物后通过叫号牌通知顾客前来取餐。
多路复用的流程也是一样,一个(多个)线程管理所有的连接请求(同时可注册一个回调函数),然后将IO交给另一个(多个)线程来处理,当IO数据准备就绪后,调用回调函数(epoll实现)。
异步
“真正”的异步IO需要操作系统更强的支持。在IO多路复用模型中,事件循环将文件句柄的状态事件通知给用户线程,由用户线程自行读取数据、处理数据。而在异步IO模型中,当用户线程收到通知时,数据已经被内核读取完毕,并放在了用户线程指定的缓冲区内,内核在IO完成后通知用户线程直接使用即可。
栗子:
我们接着来唠唠肯德基的就餐流程,在上面的栗子中,我们还需要自己去取餐,恩,这个服务还有很大的进步空间嘛,让你们的服务员给我端过来!
我们的应用程序在对数据进行处理时会涉及到数据在用户态和内核态之间的拷贝,大都数情况下,内核态只是通知用户态,诺,我数据准备好了,你拷贝走吧,而在异步模式下,内核态已经把数据拷贝到了用户态的线程缓冲区,然后再通知用户线程,是不是服务的很贴心。
总结
今天,我们唠了一下IO有哪些,以及我们常见的IO操作有哪些分类和区别,我想大家已经对IO有了一个生动的理解和记忆。但肯定也有一些疑惑,比如select、poll、epoll到底是什么呢?为什么会有用户态和内核态的拷贝呢?IO多路复用怎么实现呢?零拷贝是如何实现的?
哈哈,别急,唠嗑君后续会给大家带来IO多路复用详情的分享。