1、系列文章引言

1.1 文章目标

作为即时通讯技术的开发者来说,高性能、高并发相干的技术概念早就了然与胸,什么线程池、零拷贝、多路复用、事件驱动、epoll等等名词信手拈来,又或者你对具备这些技术特色的技术框架比方:Java的Netty、Php的workman、Go的nget等熟练掌握。但真正到了面视或者技术实际过程中遇到无奈释怀的纳闷时,方知自已所把握的不过是皮毛。

返璞归真、回归实质,这些技术特色背地的底层原理到底是什么?如何能通俗易懂、毫不费力真正透彻了解这些技术背地的原理,正是《从根上了解高性能、高并发》系列文章所要分享的。

1.2 文章源起

我整顿了相当多无关IM、音讯推送等即时通讯技术相干的资源和文章,从最开始的开源IM框架MobileIMSDK,到网络编程经典巨著《TCP/IP详解》的在线版本,再到IM开发纲领性文章《新手入门一篇就够:从零开发挪动端IM》,以及网络编程由浅到深的《网络编程懒人入门》、《脑残式网络编程入门》、《高性能网络编程》、《鲜为人知的网络编程》系列文章。

越往常识的深处走,越感觉对即时通讯技术理解的太少。于是起初,为了让开发者门更好地从根底电信技术的角度了解网络(尤其挪动网络)个性,我跨专业收集整理了《IM开发者的零根底通信技术入门》系列高阶文章。这系列文章未然是一般即时通讯开发者的网络通信技术常识边界,加上之前这些网络编程材料,解决网络通信方面的常识盲点根本够用了。

对于即时通讯IM这种零碎的开发来说,网络通信常识的确十分重要,但回归到技术实质,实现网络通信自身的这些技术特色:包含下面提到的线程池、零拷贝、多路复用、事件驱动等等,它们的实质是什么?底层原理又是怎么?这就是整顿本系列文章的目标,心愿对你有用。

1.3 文章目录

  • 1)《从根上了解高性能、高并发(一):深刻计算机底层,了解线程与线程池》
  • 2)《从根上了解高性能、高并发(二):深刻操作系统,了解I/O与零拷贝技术》(* 本文)
  • 3)《从根上了解高性能、高并发(三):深刻操作系统,彻底了解I/O多路复用 (稍后公布..)》
  • 4)《从根上了解高性能、高并发(四):深刻操作系统,彻底了解同步与异步 (稍后公布..)》
  • 5)《从根上了解高性能、高并发(五):高并发高性能服务器到底是如何实现的 (稍后公布..)》

1.4 本篇概述

接上篇《深刻计算机底层,了解线程与线程池》,本篇是高性能、高并发系列的第2篇文章,在这里咱们来到了I/O这一话题。你有没有想过,当咱们执行文件I/O、网络I/O操作时计算机底层到底产生了些什么?对于计算机来说I/O是极其重要的,本篇将带给你这个问的答案。

2、本文作者

应作者要求,不提供真名,也不提供集体照片。

本文作者次要技术方向为互联网后端、高并发高性能服务器、检索引擎技术,网名是“码农的荒岛求生”,公众号“码农的荒岛求生”。感激作者的自私分享。

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

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

  • 1)当咱们应用C语言中的printf、C++中的"<<",Python中的print,Java中的System.out.println等时;
  • 2)当咱们应用各种语言读写文件时;
  • 3)当咱们通过TCP/IP进行网络通信时;
  • 4)当咱们应用鼠标龙飞凤舞时;
  • 5)当咱们拿起键盘在评论区指点江山亦或是埋头苦干致力制作bug时;
  • 6)当咱们能看到屏幕上的丑陋的图形界面时等等。

以上这所有,都是I/O!

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

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

4、什么是I/O?

I/O就是简略的数据Copy,仅此而已!

这一点很重要!

既然是copy数据,那么又是从哪里copy到哪里呢?

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

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

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

5、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操作时底层都产生了什么。

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

6、执行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,在这里咱们临时先不思考这种形式。

在本节结尾咱们说过临时只思考过程而不思考线程,当初咱们放宽这个条件,实际上也非常简单,只须要把前图中调度的过程改为线程就能够了,这里的探讨对于线程一样成立。

7、零拷贝(Zero-copy)

最初须要留神的一点就是:下面的解说中咱们间接把磁盘数据copy到了过程空间中,但实际上个别状况下I/O数据是要首先copy到操作系统外部,而后操作系统再copy到过程空间中。

因而咱们能够看到这里其实还有一层通过操作系统的copy,对于性能要求很高的场景其实也是能够绕过操作系统间接进行数据copy的,这也是本文形容的场景,这种绕过操作系统间接进行数据copy的技术被称为Zero-copy,也就零拷贝,高并发、高性能场景下罕用的一种技术,原理上很简略吧。

PS:对于搞即时通讯开发的Java程序员来说,驰名的高性能网络框架Netty就应用了零拷贝技术,具体能够读《NIO框架详解:Netty的高性能之道》一文的第12节。如果对于Netty框架很好奇但不理解的话,能够因着这两篇文章入门:《新手入门:目前为止最透彻的的Netty高性能原理和框架架构解析》、《史上最艰深Netty入门长文:根本介绍、环境搭建、入手实战》。

8、本文小结

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

接下来的一篇《从根上了解高性能、高并发(三):深刻操作系统,彻底了解I/O多路复用》将要分享的是I/O技术的一大冲破,正是因为它,才彻底解决了高并发网络通信中的C10K问题(见《高性能网络编程(二):上一个10年,驰名的C10K并发连贯问题》),敬请期待!

附录:更多高性能、高并发文章精选

《高性能网络编程(一):单台服务器并发TCP连接数到底能够有多少》
《高性能网络编程(二):上一个10年,驰名的C10K并发连贯问题》
《高性能网络编程(三):下一个10年,是时候思考C10M并发问题了》
《高性能网络编程(四):从C10K到C10M高性能网络应用的实践摸索》
《高性能网络编程(五):一文读懂高性能网络编程中的I/O模型》
《高性能网络编程(六):一文读懂高性能网络编程中的线程模型》
《高性能网络编程(七):到底什么是高并发?一文即懂!》
《以网游服务端的网络接入层设计为例,了解实时通信的技术挑战》
《知乎技术分享:知乎千万级并发的高性能长连贯网关技术实际》
《淘宝技术分享:手淘亿级挪动端接入层网关的技术演进之路》
《一套海量在线用户的挪动端IM架构设计实际分享(含具体图文)》
《一套原创分布式即时通讯(IM)零碎实践架构计划》
《微信后盾基于工夫序的海量数据冷热分级架构设计实际》
《微信技术总监谈架构:微信之道——大道至简(演讲全文)》
《如何解读《微信技术总监谈架构:微信之道——大道至简》》
《疾速裂变:见证微信弱小后盾架构从0到1的演进历程(一)》
《17年的实际:腾讯海量产品的技术方法论》
《腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面》
《以微博类利用场景为例,总结海量社交零碎的架构设计步骤》
《新手入门:零根底了解大型分布式架构的演进历史、技术原理、最佳实际》
《从老手到架构师,一篇就够:从100到1000万高并发的架构演进之路》

本文已同步公布于“即时通讯技术圈”公众号。
本文在公众号上的链接是:点此进入。同步公布链接是:http://www.52im.net/thread-3280-1-1.html