趣文网 > 作文大全

读取文件时 程序经历了什么?

2020-12-31 11:00:01
相关推荐

你有没有想过当我们执行I/O操作时计算机底层都发生了些什么?

在回答这个问题之前,我们先来看下为什么对于计算机来说I/O是极其重要的。

不能执行I/O的计算机是什么?

相信对于程序员来说I/O操作是最为熟悉不过的了:

当我们使用C语言中的printf、C++中的"<<",Python中的print,Java中的System.out.println等时,这是I/O;当我们使用各种语言读写文件时,这也是I/O;当我们通过TCP/IP进行网络通信时,这同样是I/O;当我们使用鼠标龙飞凤舞时,当我们扛起键盘在评论区里指点江山亦或是埋头苦干努力制造bug时、当我们能看到屏幕上的漂亮的图形界面时等等,这一切都是I/O。

想一想,如果没有I/O计算机该是一种多么枯燥的设备,不能看电影、不能玩游戏,也不能上网,这样的计算机最多就是一个大号的计算器。

既然I/O这么重要,那么到底什么才是I/O呢?

什么是I/O

I/O就是简单的数据Copy,仅此而已。

这一点很重要,为了加深大家的印象,来,Everybody,Follow me,那边树上的朋友,还有那边墙上的朋友们,举起你们的双手,跟我唱,苍茫的天涯是。。。Sorry,I/O仅仅就是数据copy、I/O仅仅就是数据copy。

让我们先把演唱会的事情放在一边,既然是copy数据,又是从哪里copy到哪里呢?

如果数据是从外部设备copy到内存中,这就是Input。

如果数据是从内存copy到外部设备,这就是Output。

内存与外部设备之间不嫌麻烦的来回copy数据就是Input and Output,简称I/O(Input/Output),仅此而已。

I/O与CPU

现在我们知道了什么是I/O,接下来就是重点部分了,大家注意,坐稳了。

我们知道现在的CPU其主频都是数GHz起步,这是什么意思呢?简单说就是CPU执行机器指令的速度是纳秒级别的,而通常的I/O比如磁盘操作,一次磁盘seek大概在毫秒级别,因此如果我们把CPU的速度比作战斗机的话,那么I/O操作的速度就是肯德鸡。

也就是说当我们的程序跑起来时(CPU执行机器指令),其速度是要远远快于I/O速度的,那么接下来的问题就是二者速度相差这么大,那么我们该如何设计、该如何更加合理的高效利用系统资源呢?

既然有速度差异,而且进程在执行完I/O操作前不能继续向前推进,那么显然只有一个办法,那就是等待,wait。

同样是等待,有聪明的等待,也有傻傻的等待,简称傻等,那么是选择聪明的等待呢还是选择傻等呢?

假设你是一个急性子(CPU),需要等待一个重要的文件,不巧的是这个文件只能快递过来(I/O),那么这时你是选择什么事情都不干了,深情的注视着门口就像盼望着你的哈尼一样专心等待这个快递呢?还是暂时先不要管快递了,玩个游戏看个电影刷会儿短视频等快递来了再说呢?

很显然,更好的方法就是先去干其它事情,快递来了再说。

因此这里的关键点就是快递没到前手头上的事情可以先暂停,切换到其它任务,等快递过来了再切换回来。

理解了这一点你就能明白执行I/O操作时底层都发生了什么。

接下来让我们以读取磁盘文件为例来讲解这一过程。

执行I/O时底层都发生了什么

在上一篇《一文彻底理解高并发高性能中的线程与线程池》中,我们引入了进程和线程的概念,在支持线程的操作系统中,实际上被调度的是线程而不是进程,为了更加清晰的理解I/O过程,我们暂时假设操作系统只有进程这样的概念,先不去考虑线程,这并不会影响我们的讨论。

现在内存中有两个进程,进程A和进程B,当前进程A正在运行,如图所示:

进程A中有一段读取文件的代码,不管在什么语言中通常我们定义一个用来装数据的buff,然后调用read之类的函数,像这样:

read(buff);

这就是一种典型的I/O操作,当CPU执行到这段代码的时候会向磁盘发送读取请求,注意与CPU执行指令的速度相比,I/O操作操作是非常慢的,因此操作系统是不可能把宝贵的CPU计算资源浪费在无谓的等待上的,这时重点来了,注意接下来是重点哦。

由于外部设备执行I/O操作是相当慢的,因此在I/O操作完成之前进程是无法继续向前推进的,这就是所谓的阻塞,即通常所说的block。操作系统检测到进程向I/O设备发起请求后就暂停进程的运行,怎么暂停运行呢?很简单,只需要记录下当前进程的运行状态并把CPU的PC寄存器指向其它进程的指令就可以了。

进程有暂停就会有继续执行,因此操作系统必须保存被暂停的进程以备后续继续执行,显然我们可以用队列来保存被暂停执行的进程,如图所示,进程A被暂停执行并被放到阻塞队列中(注意,不同的操作系统会有不同的实现,可能每个I/O设备都有一个对应的阻塞队列,但这种实现细节上的差异不影响我们的讨论)。

这时操作系统已经向磁盘发送了I/O请求,因此磁盘driver开始将磁盘中的数据copy到进程A的buff中,虽然这时进程A已经被暂停执行了,但这并不妨碍磁盘向内存中copy数据。注意,现代磁盘向内存copy数据时无需借助CPU的帮助,这就是所谓的DMA(Direct Memory Access),这个过程如图所示:

让磁盘先copy着数据,我们接着聊。

实际上操作系统中除了有阻塞队列之外也有就绪队列,所谓就绪队列是指队列里的进程准备就绪可以被CPU执行了,你可能会问为什么不直接执行非要有个就绪队列呢?答案很简单,那就是僧多粥少,在即使只有1个核的机器上也可以创建出成千上万个进程,CPU不可能同时执行这么多的进程,因此必然存在这样的进程,即使其一切准备就绪也不能被分配到计算资源,这样的进程就被放到了就绪队列。

现在进程B就位于就绪队列,万事俱备只欠CPU,如图所示:

当进程A被暂停执行后CPU是不可以闲下来的,因为就绪队列中还有嗷嗷待哺的进程B,这时操作系统开始在就绪队列中找下一个可以执行的进程,也就是这里的进程B。

此时操作系统将进程B从就绪队列中取出,找出进程B被暂停时执行到的机器指令的位置,然后将CPU的PC寄存器指向该位置,这样进程B就开始运行啦,如图所示:

注意,注意,接下来的这段是重点中的重点。

注意观察上图,此时进程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是一个怎样的过程了吧。

这种进程执行I/O操作被阻塞暂停执行的方式被称为阻塞式I/O,blocking I/O,这也是最常见最容易理解的I/O方式,有阻塞式I/O就有非阻塞式I/O,在这里我们暂时先不考虑这种方式。

在本节开头我们说过暂时只考虑进程而不考虑线程,现在我们放宽这个条件,实际上也非常简单,只需要把前图中调度的进程改为线程就可以了,这里的讨论对于线程一样成立。

零拷贝,Zero-copy

最后需要注意的一点就是上面的讲解中我们直接把磁盘数据copy到了进程空间中,但实际上一般情况下I/O数据是要首先copy到操作系统内部,然后操作系统再copy到进程空间中。因此我们可以看到这里其实还有一层经过操作系统的copy,对于性能要求很高的场景其实也是可以绕过操作系统直接进行数据copy的,这也是本文描述的场景,这种绕过操作系统直接进行数据copy的技术被称为Zero-copy,也就零拷贝,高并发、高性能场景下常用的一种技术,原理上很简单吧。

总结

本文讲解的是程序员常用的I/O,一般来说作为程序员我们无需关心,但是理解I/O背后的底层原理对于设计高性能、高并发系统是极为有益的,希望这篇能对大家加深对I/O的认识有所帮助。

阅读剩余内容
网友评论
相关内容
延伸阅读
小编推荐

大家都在看

秋天的作文300字 为什么要学英语作文 劳动最光荣作文结尾 春节里的一件事作文 参加俱乐部的英语作文 勿以恶小而为之 作文 小学生作文批语 秋天的家园作文 一次小实验作文300字 玩摩天轮作文 作文竞赛简报 作文运动会题目 挑山工精神作文 我喜欢的一天英语作文 网球比赛作文 师生之间作文600字 好暖作文 以夏天为主题的作文 生日聚会英文作文 那个人改变了我作文 暑假活动英语作文 梅花作文450字 我以书本为知己作文 关于介绍自己的英语作文 与读书有关的作文 精彩半命题作文 遇见你真好作文600字 荡秋千作文 付出与分享作文 与什么交朋友作文