乐趣区

关于后端:读取文件时程序经历了什么

你有没有想过当咱们执行 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 就开始运行啦,如图所示:

留神,留神,接下来的这段是重点中的重点。

留神察看上图,你能看出这种设计的精妙之处吗,这对于了解操作系统至关重要,关注公众号“码农的荒岛求生”回复“过程”二字你就能失去答案以及该过程的最初两个步骤啦。

零拷贝,Zero-copy

最初须要留神的一点就是下面的解说中咱们间接把磁盘数据 copy 到了过程空间中,但实际上个别状况下 I / O 数据是要首先 copy 到操作系统外部,而后操作系统再 copy 到过程空间中。因而咱们能够看到这里其实还有一层通过操作系统的 copy,对于性能要求很高的场景其实也是能够绕过操作系统间接进行数据 copy 的,这也是本文形容的场景,这种绕过操作系统间接进行数据 copy 的技术被称为Zero-copy,也就零拷贝,高并发、高性能场景下罕用的一种技术,原理上很简略吧。

总结

本文解说的是程序员罕用的 I /O,一般来说作为程序员咱们无需关怀,然而了解 I / O 背地的底层原理对于设计高性能、高并发零碎是极为无益的,心愿这篇能对大家加深对 I / O 的意识有所帮忙。

退出移动版