1-嵌入式开发总结之--CPU模型

   CPU这三个字母,大家都很熟悉,也都知道它是计算机的核心。CPU是中央处理器的简称,在大部分人的脑海里,它就是一小块芯片,但却异常的复杂,计算机少了它,什么都干不了。买电脑的时候,都想着内存可以加,磁盘可以扩,显示器可以换新的,唯独CPU,想着一步到位,要最新性能的,最快速度的。这从一个侧面,在一定程度上反映了CPU的重要性。CPU很复杂,这是不争的事实,那么我们该如何为其建模呢?这需要从它工作的原理出发。CPU的复杂,很多的来自于它与硬件相关的细节,如果从软件的角度,配合工作的原理出发,就可以抛掉硬件细节,这样以来,建模就成为了可能。在这部分,我们就从软件这另一个角度出发,为大家展现一个不一样的CPU。

   首先,还是需要简单介绍一下CPU是做什么的

   现代计算机系统的组成,包括了CPU、内存、磁盘、以及输入输出设备(键盘、显示器等)。对于CPU,大家都不陌生,其被称为计算机的大脑,控制中心,负责指令的执行。简单来讲,CPU从内存中取出指令,执行指令,如果是控制指令,则控制设备,如果是运算指令,则将结果保存回内存。这就像炒菜,人就是CPU,菜谱就是指令,按照菜谱,获取和处理食材,并加工为色香味俱全的美食。对于计算机来讲,其是运行程序的,这里面就是CPU来执行程序。程序由数据和逻辑组成,CPU把数据按照逻辑进行加工,最终产生结果。

   要完成这些任务,CPU的内部逻辑就简单不了。可以说,其内部逻辑十分复杂,即使就其中的某一特性,比如流水线指令等,就可以用一本书来讲解。当然,如前所述,在这里,不对其内部细节,特别是硬件细节,进行过多的描述,而是简化出其功能组成部分,便于后面进行程序执行分析即可。读者如果对内部实现有兴趣,可以查阅专门的书籍。

   简单了解了CPU做什么的以后,可能对CPU的概念还是很模糊的,很难跟我们见到的小芯片建立起来联系。其实我想,很多人都想过,CPU芯片里面到底是什么样的,为啥就能完成那么多复杂的工作?这里为了满足大家的好奇心,也包括我,特意从网上找了一张图片,来看看CPU里面到底是什么样子的。

                                                                                                    Intel X86 某型号CPU内部晶体管图

   当然,现代CPU的发展也不是一蹴而就的,中央处理器的发展历史也是经历了从简单到复杂的过程。从最早的4位到现在的64位,从最早的4004、经典的8086、286、486到现在的集各种先进技术特性于一身的酷睿7代、8代CPU。各种衡量指标不断翻倍,典型的总线宽度从4位到64位,主频从几MHz到几GHz,晶体管数量从几万、几百万到几亿几十亿,核心从1个到2个、4个、8个...64个甚至到一百多个,更大的吞吐量,更快的速度,更小的体积,更少的能耗,更广的应用领域等等。如果细说起来,光就各种飞跃,也都可以装几大箩筐。但是,不管它怎么跨越式的发展变化,其基础核心还是没有改变,也就是说它本质的东西没有大的变化,掌握了这些不变的,就可以以不变应万变。

   那么它核心的东西包括什么呢?那就是它的作用,功能。不论CPU的设计如何发展,其基本功能还是解析、执行指令。分步骤来讲,就是取指令,分析指令,执行指令,存储执行结果。再进一步,深入到内部,这核心中的核心就是计算和控制。基于这一点,我们抛开CPU内部复杂的逻辑,来看一个简化的模型图(图片来自网络)。

   上图可以说是一个最简单的CPU模型了。其中,寄存器保存临时变量,计数器控制取指令地址,运算器完成基础运算,控制器完成控制动作。这里不细介绍CPU的组成部分,包括寄存器、运算器、控制器等,而是换个角度介绍其如何工作。对于涉及执行细节的地方,尽量用形象的比喻或者类比来描述。

   一 CPU的核心:寄存器、运算器和控制器

   首先,我们的指令和数据都保存在内存中,CPU要执行的指令和处理的数据,都需要从内存中获取。这是冯洛伊曼架构计算机的核心。还有一种是哈弗结构,指令和数据分开存储,从指令中获取数据地址,然后到数据存储中获取数据,如下图所示:

   中央处理器做什么动作,是由存储在内存中的指令编码决定的。CPU通过控制器取指令,并对指令进行分析,译码,然后根据指令要求处理数据。有了控制器,CPU就知道从哪里取指令,并知道需要对数据做怎样的处理,下一步就是如何完成数据运算,这是通过其中的另一个核心器件来完成的,这就是运算器。CPU的运算嚣对数据的处理是最基础最简单的,只有类似加减乘除这些。那有人可能就问了,不会吧,就这样简单的处理,没错,就是这样简单的处理,因为复杂的处理是没必要的,也是难以完成的。说没必要,是因为复杂的处理可以通过简单的运算的组合来完成,说难以完成,是因为复杂运算千变万化,不可能把所有的都实现,而且硬件实现的逻辑一般都是固定不变的,也不可能将所有可能都预置好。其实,复杂的算法都是通过人的聪明才智编写出来的,这就是我们程序员的责任,找到问题的解决方法,然后编成程序。CPU只需要尽可能发挥它的长处,那就是速度,对,就是速度,通过速度取胜。通过每秒钟执行几百万几千万条指令,最后在宏观整体上达到我们实际可以感受到的丰富多彩效果。提到速度,就不得不提它的第三个核心器件,这就是寄存器。寄存器是个什么玩意,可能非电子和计算机专业的同学,就不是很清楚了,因为它不像其他计算机名词和概念那么流行。说白了,寄存器就是一个数据缓冲器,临时保存器,CPU通过它来匹配自己的高速和内存的慢速。因为CPU速度远远高于内存,所以总不能每次的数据存取都通过内存,这样每次花在内存读取上的时间就太不划算了。而且,运算器所运算的数据也是存在寄存器里的。临时结果也保存在寄存器中。如此一来,CPU每次从内存读取的数据基本上都是先存放在寄存器里,CPU内部逻辑单元再直接使用寄存器进行处理,包括运算器所计算的数据大都来自CPU里的寄存器,一些指令寻址也要用寄存器作为中介,搭起CPU和内存之间的数据传递桥梁。这块我们在后面讨论到虚拟地址实地址转换时还会再次提到。额外话题,可能有人就会问了,既然这样,为何不吧内存设计成跟寄存器一样的快速,这其中主要涉及到成本的问题,就像内存和硬盘一样,单位容量字节,内存要远远贵于硬盘。

   简单介绍了CPU的三个核心器件运算器、控制器及寄存器的作用后,我们再通过一个极其简单的例子来看看这三个器件是如何密切配合工作的。说极其简单,是因为这个例子是介绍如何进行加法运算的,加法似乎是再简单不过的数学运算了,不同之处是这里的加法是由CPU完成的。我们通过下面一幅图来(图片来自网络)完成这个加法流程的介绍:

   上一个指令执行完成后,程序计数器将加一,然后送给地址寄存器。地址寄存器将下一条指令的地址送到地址总线上,存储器中对应地址的数据就会送到数据总线上。这里的数据就是一条加法指令,包含指令名称和两个待相加的寄存器(假定为直接寻址)。指令数据将送到指令寄存器中。指令寄存器将获取的指令送给指令译码器进行译码。指令译码后,产生控制信号,送给控制器,控制器送出时序信号。该加法指令的时序信号将控制运算器完成两个寄存器中数据的相加,并根据结果设置状态寄存器。完成后,PC寄存器将继续加一,指导获取下一个指令进行处理。

   这里我们使用加法来举例子也是有另外一层含义的,就是虽然表面上看,CPU能够完成巨无比复杂的功能,但是抽丝剥茧后,CPU执行的基本操作却是非常简单的,正真复杂的功能其实都是人类大脑聪明智慧的构想设计。这些构想通过程序表达,由CPU来执行实现。最终来看,CPU只是做了机器该做的事,那就是高速的执行指令,而这些指令本质上就是一些电子动作,类比机器的机械动作。现在大火的人工智能似乎也有这种感觉在内。

   额外补充一点,从数学角度来看,加法有一种线性代数中基础基的意味,属于基础集合中的一个元素。基于基础基,也就是基础集合中的元素,通过一些操作、变换和组合,可以构建或者产生该集合所属领域中的其他所有元素或者内容。也就是说基础基中的元素是必须的,不能多也不能少,多了就是冗余,少了就无法构建整个体系。这也有一种数学中公理的感觉,每一个领域的数学大厦都是构建在公理基础之上的,其他的定理、推论都可以通过公理加上人类聪慧的大脑运算推导出来。

   CPU的设计也遵从了数学上的这种特征。CPU内部实际只完成一些基础的操作和运算,这些基础操作和运算就好比电子动作,而复杂的功能都是由人设计的程序来实现的。这些基础的操作和运算有另外一个更恰当的名称,就是指令集。指令集并不是严格意义上的基础基,为了方便实际需求,指令集是有一定的冗余的。关于这些内容不再展开叙述,感兴趣的读者可以查阅相关资料进一步深入了解。

   这样,最终,如下图所示,CPU就像是一个永动机一样,不断从内存中取指令,分析指令,处理数据,完成程序交给它的任务,宏观上实现程序要表达的各种功能。

   上图是CPU的写实图,也就是依样画葫芦,按照原样展示CPU内部逻辑。下面,我们换个角度,来个写意图。此时,可将CPU想像成你自己的大脑,内存更加逻辑化,用来执行完成各种操作。而这种转换,就相当于开篇关于矩阵那篇文章中所介绍的,在另一个角度描述同一个东西,在另一个角度给CPU照像,这样描述更加形象一些。

   内部中心取出指令逻辑,取出指令数据,执行逻辑,对数据处理,然后将结果保存起来,这就是整个过程的极简描述。

   了解了CPU的核心,也就了解了CPU内部的工作原理。不过这些都是偏理论的论述。如果目的只是了解原理,这就足够了,但是如果想基于此设计CPU,那还远远不够,因为这里忽略了太多太多的重要技术细节。有多少呢?也许这些技术细节本身的介绍就需要好几本大部头,也许这些细节就只隐藏于这个世界的某个角落,只为个别人所掌握。这一点上,CPU也不特殊和例外。简单一个比方,了解原子弹的原理跟造出原子弹完全是两回事,了解空气动力学的原理跟造火箭也完全是两回事。说二者相差十万八千里一点也不过分。

   二 基于Cache的优化

   第一部分介绍了CPU的核心器件,了解了CPU的工作原理。在这部分及后面几部分将进一步介绍CPU内部的优化技术。这些优化措施有助于我们进一步深入理解CPU的工作原理,所以还是值得了解的。

   前面我们讲了,CPU的速度远高于内存,为了便于数据的处理,也为了优化处理,提升速度,使用了寄存器作为平衡的桥梁。甚至我们现在有些设计语言支持申请寄存器变量,来优化程序,原理就在于此。但是仅仅通过寄存器这种优化还远远不够,人们研究发现,我们编写的程序是有一些特点的,比如大部分指令是顺序执行的,跳转的只是少数,最近使用过的数据还有可能再次被使用,甚至是反复使用。这就说明许多程序都具有时间上和空间上的局部性特点。有了这一点启发,聪明的智慧的人类就想出了一招,是不是可以把这些局部的数据和指令先缓存到CPU中,这样就会进一步减少读取内存的次数,甚至因为反复使用,会更大程度的优化执行时间!实际上这种优化带来的效率提升和效果改进,远远超出了我们的预期。这对CPU来讲是一个很重要的优化。为了完成这一使命,就需要对CPU的设计作修改。CPU的设计者们在内部增加了指令cache和数据cache,也就是指令缓存和数据缓存,优化后的模型如下图所示。cache在计算机中是一个非常重要的概念,其应用非常普遍,而且这种思想在计算机的各个领域也都随处可见。当然,后面我们也还会多次提到它,读者要引起重视。有了这两个cache,CPU就会根据算法将最近最可能或者肯定会使用到的指令和数据预先读到cache中,这样在执行的过程中就不用到内存里去读了。其实,道理讲起来简单,但是就如之前说的,细节可不简单,这其中涉及的选择算法【如何选择读取到cache中的指令和数据才能达到最优的效果】,匹配失效也就是非命中情况的处理,等等这些细节,就可以用一本书来细细讲述。计算机体系结构对这块的内容有深入的探讨,感兴趣的读者可以阅读相关的书籍了解更详细的内容。CPU的这种处理对程序员来讲是基本透明的,也就是软件层并不能感知到这种cache的存在,是由硬件自动处理的。虽然我们不能直接的控制我们的程序何时、何部分会放到cache中,但是也不是说就完全无法作为。相反,我们可以努力故意让我们程序的局部性特点更加突出,这样就可以减少失效的情况,增加重复使用的概率,这也就是为什么说是基本透明。比如,现在的编译优化技术就已经利用了CPU的这一特点。【一种优化程序的方法,原理就在CPU的预读取技术,参见本人博文 嵌入式Linux开发调优之二:应用程序_龙赤子的博客-CSDN博客 】

  三 基于MMU的优化

  了解了CPU利用cache的预读取优化技术后,现在让我们来了解CPU的另一项关键优化技术,这就是虚拟内存管理单元,简称MMU。之前有提到,CPU不仅是速度越来越快,还有一个发展趋势是处理的基本数据宽度也越来越大,从最初的4位机、8位机、16位机到32位机,甚至现在已经开始普遍的64位机。那么,CPU为何要扩展它的数据宽度呢?这要从扩展之后能够带来的好处说起。首先,是可访问内存空间的扩大,可以写更大型的程序。计算机功能的强大和流行,带来了大型程序的需求。其次,效率更高。宽位数据的运算可以直接执行完成,不再需要组合处理,浮点数的计算也会更加高效。

   回到讨论点,CPU的这些变化与MMU也就是内存管理单元有什么关系呢?最主要的因素是,随着计算机的流行,各种功能软件的不断涌现,同时运行多个软件逐渐变成一项基本需求了。试想一下,如果你的电脑开机后,同时只能有一个软件运行,那你使用它将如何的别扭。浏览网页时不能编辑文档,编辑文档时,不能收发邮件,收发邮件时,不能编写程序......电脑似乎一下子变得不可用了。所以,MMU保证了计算机可以同时运行多个程序。

   那么问题就来了,同时运行多个软件,为什么就需要MMU的支持呢?这是因为,内存总是不够用。虽然我们的内存越来越大,但是我们的软件也越来越复杂庞大了,而且各种功能软件涌现的速度远远超过了内存的扩展速度,并且差距在不断的拉大。软件似乎有无穷多,内存总是有限的那么大。根据冯洛伊曼系统结构原理,程序是要加载到内存中才可以运行的。这样如果要同时运行多个程序,且同时要运行几个,并且都是那几个程序还不固定(每个用户的需求都不一样),内存必然会处于不够的状态。是否可以通过增加内存解决这个问题呢?显然,由于差距巨大,这个矛盾不是简单扩充内存就可以搞定的。如此一来,要解决问题,那就得有一种方案,既不用增加内存,同时又要能满足多程序同时运行。有没有这样的解决方案存在呢?确实有这样一种方案,这就是虚拟内存技术,而虚拟内存技术在CPU层级,就需要MMU的支持。关于何为虚拟内存,这里不再展开,在操作系统内存部分会针对该主题进行详细的介绍。这里只需要知道,通过虚拟内存技术,CPU在内存不够用的时候,可以暂时将当前没有运行的程序或者不那么重要的程序存储到磁盘上,然后空出内存来运行急需要使用内存的程序。通过这种不断的调入调出操作,利用时间差(CPU角度经过的很长时间在人看来还是很短的,有点天上一天,地上一年的感觉,这里CPU就好比在地上度量时间),达到人为感知上的程序在同时运行,内存有无限大的错觉。

   最终,CPU利用MMU的协助,实现对虚拟内存技术的支持。不过,对于我们编写程序来讲,MMU是透明的。程序员只管编写代码就好了,不用管内存是不是不够用,这些都由操作系统来处理。但是对于操作系统来讲,MMU就不透明了,相反,操作系统的设计者要十分清楚如何使用CPU的MMU功能。因为最后的最后,是操作系统借助MMU实现了虚拟内存管理。

   这里我们将加了内存管理单元的CPU模型化为下图:

   可能到这里,读者对虚拟内存的感受还是比较模糊的。没关系,先有一个框架性的认识,在后面的操作系统基础部分的介绍中,我们会配合操作系统的实现,详细介绍其中的细节。到时,读写将会对虚拟内存有完整清晰的认识。

   上面我们只是简单粗略的介绍了MMU。实际CPU架构中,还会进一步利用TLB技术加快物理地址到虚拟地址的转换速度,原理本质上也是一种缓存技术。不过这些对程序员大多不可见,这里就不再展开了。

   下面我们再简单介绍几种CPU的优化技术。

   四 基于流水线指令的优化

   CPU为了更高效的运作,通常都会采用多级流水线技术。这一方面利用了程序的局部性原理,另一方面利用了指令执行过程的统一性(比如,都包括取指令,分析指令,执行指令,数据写回等几个基本步骤)。因为局部性原理的存在,CPU处理的下一条指令很可能就是下一个地址的指令。当然,如果当前指令是跳转指令,则该规则失效,不过这种情况可以认为是小概率事件。又因为CPU工作过程可以分为上述取指令、分析指令、执行指令等几个标准步骤,所以,可以让CPU的一些部件在处理完当前指令的工作后,根据局部性原理,继续处理下一地址的指令(就认为是下一条指令),而非原来的等待所有当前指令的几个步骤都执行完成后再处理下一条指令。采用流水线技术,可以充分调动CPU内部各个部件的“积极性和主观能动性”,避免部件之间的低效“观望等待”状态。

   五 基于多核心的优化

   由于摩尔定律在不断的逼近物理极限,单纯提升频率提高CPU性能的做法难以为继。在这种大背景下,各个厂商普遍都将目光投向了多核心方案。前面已经提到,虽然CPU同一时间只能干一件事,但是CPU的速度很快,可以通过不断的切换运行程序,宏观上给人并行执行的感觉。将这个操作进一步的精细化,我们可以让CPU内部集成多个核心,每个核心可以承载一部分程序的运行,甚至某个程序独占一个核,这样程序实际获取的CPU运行时间变多,性能得到提升。进一步的,我们也可以人为的将程序的工作“分而治之”,拆分为多个子模块,每个子模块占用一个核心的方式,达到原来无法实现的性能提升。

   六 基于指令集的优化

   除了上面介绍的优化技术外,还有一种优化技术就是软件硬件化。我们知道,硬件执行速度总是快于软件,人们通过将一些特定的处理逻辑固化为硬件方式,达到原来通用指令难以企及的高性能。这是整体性的硬件化。进一步的,我们可以分析软件的逻辑特点,特别是计算特点,设计特殊的指令集应对特殊的应用。DSP就是这类方案的典型代表,通过单独设计许多特殊指令,比如设计浮点数乘法指令,实现通用指令(比如,加法模拟实现)难以达到的性能指标。这是片区、面的硬件化。再进一步,即使是通用指令,也存在优化的可能。比如复杂指令集和精简指令集,就是典型的代表。指令集的设计也是在不断优化的,一方面通过统计数据,分析程序对指令的使用频率,达到整体的优化;另一方面,在设计允许的情况下,扩展指令,满足指令不断进化的要求。

   指令集的优化,不但性能上要提升,而且整体上也要取优,包括功耗等其他因素也是要考虑的,否则,单单某一个方面的提升,可能无法激起市场的涟漪。

   指令集的优化与前几项有一个很大的不同在于需要编译器深度参与。单单依靠CPU自身可能无法达到满意的效果。

   总的来讲,上述这些对CPU优化技术的了解还是很必要的,因为一些编译优化技术就是根据CPU的改进而配套提出的,从而也对我们的代码优化具有直接或间接的指导作用。

   这篇文章写得有点早了,最近因为工作原因,再次了解CPU时,发现了很多之前学校中不曾了解的概念,最直接的两个是超标量和乱序执行。这里再补充补充。看来,学习不能停,否则很容易跟快速的技术发展脱节,而且容易形成固有的认知,影响对新问题的分析判断。

   七 超标量优化

   前面提到了现代CPU都采用流水线执行方式来优化性能。但是,仔细思考一下,我们会发现,流水线的优化上限跟CPU的执行过程有关。 如果CPU执行一条指令需要5个过程,那么流水线最理性的效果也只是将这5个过程并行化。每个过程本身并不可再分。因此,最优结果是一个时钟周期完成一条指令的处理。这个时候,每个功能部件都在并行工作。看似已经优化的很好了,或者说到极限了,那么实际上有没有可能再更进一步呢?

   乍看,似乎无解,但是,仔细想想还是有空间的。这个时候,你要站到CPU设计者的角度来思考。既然物理规律导致频率增长的空间有限,那么我们能不能空间换时间呢?一个部件的执行时间很难再缩短,那么我们可不可以每个阶段多个部件,比如,如果有两个执行部件,那么一个时钟周期就可以完成两条指令的处理。这个跟多核思想是一致的,但是细节上不一样。多个核心可以并行执行不同的线程,而这种方式,是单个核心以二倍速执行一个线程。这就是超标量技术。硬件的并行跟软件的并行不一样,硬件层面,可以轻松做到微观层面真正的并行。当然,这种并行并不是无限制的,如果指令之间存在前后依赖,那么并行就会退化成串行。解决这个问题,就需要另一种技术,乱序执行。

   八 乱序执行优化

   乱序执行(Out of Order Execution)和超标量可以说是相辅相成的。为了更好的超标量,那么就需要乱序执行来支持。这个时候,指令的执行不再按照设计顺序执行,而是根据依赖关系,相互之间不依赖的指令就可以并行执行。理想情况下,一个时钟周期就可以有多条指令被执行。这等价于时钟频率的翻倍,代价则是复杂度和晶体管的增加。显然,要是能够解决好这个问题,那么付出点晶元面积还是很值得的。

   乱序执行看似很美好,但是乱序也仅仅只限于执行,指令的逻辑顺序效果,不能被打破,也就是乱序执行乱序的是无关联的指令。如果一条指令依赖上一条指令的结果,那么这两条指令是不能并行运行的,同样,乱序执行的结果再写回内存时,也是按照有序执行的结果来完成的。所以,从软件层面看到的,仍然是有序执行的结果。

   后记:

   关于CPU的优化,在讨论CPU的发热和CPU的负载两篇博文中,都有所触及,其本质就是优化平均指令周期。这一方面依赖时钟频率的提升,一方面依赖更好的架构设计。时钟频率的提升,目前来看,在综合发热等方面的因素后,基本上不再是主要的优化手段。而架构的设计,则是近十年CPU设计领域的主要优化方向。最终目标就是实现单位能量的效能最大化。无论是通过增加各种模块还是采用更好的生产工艺来完成。另外,话说分久必合、合久必分;除了CPU、GPU,现在也在不断涌现NPU、DPU,所以未来如何,咱们这一代人也可以拭目以待。

   补充一个比较好的了解CPU架构的网站。WikiChip

   对CPU内部架构感兴趣的,可以研究一下这个网站的内容。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙赤子

你的小小鼓励助我翻山越岭

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值