一句话概括什么是I/O:简单的数据Copy。内存与外部设备之间的copy就是I/O(input/output)
I/O与CPU
通常CPU速度:纳秒级别(指令执行速度)
通常I/O速度:毫秒级别(磁盘的一次seek)
速度差异上千倍,需要解决如何匹配I/O和CPU速度、调度CPU等。
假设你是一个急性子(CPU),需要等待一个重要的文件,不巧的是这个文件只能快递过来(I/O),那么这时你是选择什么事情都不干了,深情的的注视着门口就像盼望着你的哈尼一样专心等待这个快递呢?还是暂时先不要管快递了,玩个游戏看个电影刷会儿短视频等快递来了再说呢?很显然,更好的方法就是先去干其它事情,快递来了再说。
因此这里的关键点就是手头上的事情可以先暂停,切换到其它任务,等快递过来了再切换回来。
上图内存中有两个进程,进程A和进程B,当前进程A正在运行,进程A中有一段读取文件的代码,不管在什么语言中通常都会自定义一个用来装数据的buffer,然后调用read之类的函数,像这样:
read(buff);
这就是一种典型的I/O操作,当CPU执行到这段代码的时候会向磁盘发送读取请求,注意与CPU执行指令的速度相比,I/O操作操作是非常慢的,因此操作系统是不可能把宝贵的计算资料浪费在无谓的等待上的,这时由于外部设备执行I/O操作是相当慢的,因此在I/O操作完成之前进程是无法继续向前推进的,这就是所谓的阻塞,即通常所说的block。操作系统检测到进程向I/O设备发起请求后就暂停进程的运行,需要记录下当前进程的运行状态并把CPU的PC寄存器指向其它进程的指令。
进程有暂停就会有继续执行,因此操作系统必须保存被暂停的进程以备后续继续执行,显然我们可以用队列来保存被暂停执行的进程,如图所示,进程A被暂停执行并被放到阻塞队列中(注意,不同的操作系统会有不同的实现,可能每个I/O设备都有一个对应的阻塞队列,但这种实现细节上的差异不影响我们的讨论)。
磁盘相当于在异步读取,而次数将读取结束回调放入I/O队列,CPU又去干其他事情了。
实际上操作系统中除了有阻塞队列之外也有就绪队列,所谓就绪队列是指队列里的进程准备就绪可以被CPU执行了,你可能会问为什么不直接执行非要有个就绪队列呢?答案很简单,那就是僧多粥少,在只有2个核的机器上可以创建出成千上万个进程,CPU不可能同时执行这么多的进程,因此必然存在这样的进程,即使其一切准备就绪也不能被分配到计算资源,这样的进程就被放到了就绪队列。现在进程B就位于就绪队列,万事俱备只欠CPU,如图所示:
注意观察上图,此时进程B在被CPU执行,磁盘在向进程A的内存空间中copy数据,数据copy和指令执行在同时进行,在操作系统的调度下CPU、磁盘都得到了充分的利用,这就是程序员的智慧所在。
此后磁盘终于将全部数据都copy到了进程A的内存中,这时磁盘通知操作系统任务完成,这就是中断。
操作系统接收到磁盘中断后发现数据copy完毕,进程A重新获得继续运行的资格,这时操作系统小心翼翼的把进程A从阻塞队列放到了就绪队列当中,如图所示:
注意,操作系统是不会直接运行进程A的,进程A必须被放到就绪队列中等待,这样对大家都公平。此后进程B继续执行,进程A继续等待,进程B执行了一会儿后操作系统认为进程B执行的时间够长了,因此把进程B放到就绪队列,并把进程A取出继续执行。注意操作系统把进程B放到的是就绪队列,因此进程B被暂停运行仅仅是因为时间片到了而不是因为发起I/O请求被阻塞,如图所示:
进程A继续执行,此时buff中已经装满了想要的数据,进程A就这样愉快的运行下去了,就好像从来没有被暂停过一样,进程对于自己被暂停一事一无所知,这就是操作系统的魔法。
零拷贝:
绕过操作系统,直接进行数据拷贝叫做零拷贝。
实际上一般情况下I/O数据是要首先copy到操作系统内部,然后操作系统再copy到进程空间中
摘自计算机底层的秘密——小风,公众号:码农的荒岛求生
这本书写的很通俗易懂,类似于大话设计模式的文风,幽默风趣,图文并茂,浅显易懂,比课本上文绉绉的文字描述和干瘪的代码好太多。