《奔跑吧 Linux内核》之处理器体系结构

本文摘自人民邮电出版社异步社区《奔跑吧Linux内核》
html

第1章 处理器体系结构


京东购书: item.jd.com/12152745.ht…
试读地址: www.epubit.com.cn/book/detail…
本章思考题
1.请简述精简指令集RISC和复杂指令集CISC的区别。
2.请简述数值0x12345678在大小端字节序处理器的存储器中的存储方式。
3.请简述在你所熟悉的处理器(好比双核Cortex-A9)中一条存储读写指令的执行全过程。
4.请简述内存屏障(memory barrier)产生的缘由。
5.ARM有几条memory barrier的指令?分别有什么区别?
6.请简述cache的工做方式。
7.cache的映射方式有full-associative(全关联)、direct-mapping(直接映射)和set-associative(组相联)3种方式,请简述它们之间的区别。为何现代的处理器都使用组相联的cache映射方式?
8.在一个32KB的4路组相联的cache中,其中cache line为32Byte,请画出这个cache的cache line、way和set的示意图。
9.ARM9处理器的Data Cache组织方式使用的VIVT,即虚拟Index虚拟Tag,而在Cortex-A7处理器中使用PIPT,即物理Index物理Tag,请简述PIPT比VIVT有什么优点?
10.请画出在二级页表架构中虚拟地址到物理地址查询页表的过程。
11.在多核处理器中,cache的一致性是如何实现的?请简述MESI协议的含义。
12.cache在Linux内核中有哪些应用?
13.请简述ARM big.LITTLE架构,包括总线链接和cache管理等。
14.cache coherency和memory consistency有什么区别?
15.请简述cache的write back有哪些策略。
16.请简述cache line的替换策略。
17.多进程间频繁切换对TLB有什么影响?现代的处理器是如何面对这个问题的?
18.请简述NUMA架构的特色。
19.ARM从Cortex系列开始性能有了质的飞越,好比Cortex-A8/A15/A53/A72,请说说Cortex系列在芯片设计方面作了哪些重大改进?

Linux 4.x内核已经支持几十种的处理器体系结构,目前市面上最流行的两种体系结构是x86和ARM。x86体系结构以Intel公司的PC和服务器市场为主导,ARM体系结构则是以ARM公司为主导的芯片公司占领了移动手持设备等市场。本书重点讲述Linux内核的设计与实现,可是离开了处理器体系结构,就犹如空中楼阁,毕竟操做系统只是为处理器服务的一种软件而已。目前大部分的Linux内核书籍都是基于x86架构的,可是国内仍是有至关多的开发者采用ARM处理器来进行开发产品,好比手机、IoT设备、嵌入式设备等。所以本书基于ARM体系结构来说述Linux内核的设计与实现。
关于ARM体系结构,ARM公司的官方文档已经有不少详细资料,其中描述ARMv7-A和ARMv8-A架构的手册包括:
前端

  • <ARM Architecture Reference Manual, ARMv7-A and ARMv7-R edition>

  • <ARM Architecture Reference Manual, ARMv8, for ARMv8-A architecture profile>

  • 另外还有一本很是棒的官方资料,讲述ARM Coxtex系统处理器编程技巧:node

  • <ARM Coxtex-A Series Programmer’s Guide, version 4.0>
  • <ARM Coxtex-A Series Programmer’s Guide for ARMv8-A, version 1.0>
  • 读者能够从ARM官方网站中下载到上述4本资料[1]。本书的重点集中在Linux内核自己,不会用过多的篇幅来介绍ARM体系结构的细节,所以本章以快问快答的方式来介绍一些ARM体系结构相关的问题。linux


    可能有些读者对ARM处理器的命名感到疑惑。ARM公司除了提供处理器IP和配套工具之外,主要仍是定义了一系列的ARM兼容指令集来构建整个ARM的软件生态系统。从ARMv4指令集开始为国人所熟悉,兼容ARMv4指令集的处理器架构有ARM7-TDMI,典型处理器是三星的S3C44B0X。兼容ARMv5指令集的处理器架构有ARM920T,典型处理器是三星的S3C2440,有些读者还买过基于S3C2440的开发板。兼容ARMv6指令集的处理器架构有ARM11 MPCore。到了ARMv7指令集,处理器系列以Cortex命名,又分红A、R和M系列,一般A系列针对大型嵌入式系统(例如手机),R系列针对实时性系统,M系列针对单片机市场。Cortex-A7和Coxtex-A9处理器是前几年手机的主流配置。Coxtex-A系列处理器面市后,因为处理性能的大幅提升以及杰出功耗控制,使得手机和平板电脑市场迅猛发展。另一些新的应用需求正在酝酿,好比大内存、虚拟化、安全特性(Trustzone[2]),以及更好的能效比(大小核)等。虚拟化和安全特性在ARMv7上已经实现,可是大内存的支持显得有点捉襟见肘,虽然能够经过LPAE(Large Physical Address Extensions)技术支持40位的物理地址空间,可是因为32位的处理器最高支持4GB的虚拟地址空间,所以不适合虚拟内存需求巨大的应用。因而ARM公司设计了一个全新的指令集,即ARMv8-A指令集,支持64位指令集,而且保持向前兼容ARMv7-A指令集。所以定义AArch64和AArch32两套运行环境分别来运行64位和32位指令集,软件能够动态切换运行环境。为了行文方便,在本书中AArch64也称为ARM64,AArch32也称为ARM32。程序员


    1.请简述精简指令集RISC和复杂指令集CISC的区别。

    20世纪70年代,IBM的John Cocke研究发现,处理器提供的大量指令集和复杂寻址方式并不会被编译器生成的代码用到:20%的简单指令常常被用到,占程序总指令数的80%,而指令集里其他80%的复杂指令不多被用到,只占程序总指令数的20%。基于这种思想,将指令集和处理器进行从新设计,在新的设计中只保留了经常使用的简单指令,这样处理器不须要浪费太多的晶体管去作那些很复杂又不多使用的复杂指令。一般,简单指令大部分时间都能在一个cycle内完成,基于这种思想的指令集叫做RISC(Reduced Instruction Set Computer)指令集,之前的指令集叫做CISC(Complex Instruction Set Computer)指令集。
    IBM和加州大学伯克利分校的David Patterson以及斯坦福大学的John Hennessy是RISC研究的先驱。Power处理器来自IBM,ARM/SPARC处理器受到伯克利RISC的影响,MIPS来自斯坦福。当下还在使用的最出名的CISC指令集是Intel/AMD的x86指令集。

    RISC处理器经过更合理的微架构在性能上超越了当时传统的CISC处理器,在最初的较量中,Intel处理器败下阵来,服务器市场的处理器大部分被RISC阵营占据。Intel的David Papworth和他的同事一块儿设计了Pentium Pro处理器,x86指令集被解码成相似RISC指令的微操做指令(micro-operations,简称uops),之后执行的过程采用RISC内核的方式。CISC这个古老的架构经过巧妙的设计,又一次焕发生机,Intel的x86处理器的性能逐渐超过同期的RISC处理器,抢占了服务器市场,致使其余的处理器厂商只能向低功耗或者嵌入式方向发展。算法


    RISC和CISC都是时代的产物,RISC在不少思想上更为先进。Intel的CSIC指令集也凭借向前兼容这一利器,战胜全部的RISC厂商,包括DEC、SUN、Motorola和IBM,一统PC和服务器领域。不过最近在手机移动业务方面,以ARM为首的厂商占得先机。编程


    2.请简述数值0x12345678在大小端字节序处理器的存储器中的存储方式。

    在计算机系统中是以字节为单位的,每一个地址单元都对应着一个字节,一个字节为8个比特位。但在32位处理器中,C语言中除了8比特的char类型以外,还有16比特的short型,32bit的int型。另外,对于位数大于8位的处理器,例如16位或者32位的处理器,因为寄存器宽度大于一个字节,那么必然存在着如何安排多个字节的问题,所以致使了大端存储模式(Big-endian)和小端存储模式(Little-endian)。例如一个16比特的short型变量X,在内存中的地址为0x0010,X的值为0x1122,那么0x11为高字节,0x22为低字节。对于大端模式,就将0x11放在低地址中;0x22放在高地址中。小端模式则恰好相反。不少的ARM处理器默认使用小端模式,有些ARM处理器还能够由硬件来选择是大端模式仍是小端模式。Cortex-A系列的处理器能够经过软件来配置大小端模式。大小端模式是在处理器Load/Store 访问内存时用于描述寄存器的字节顺序和内存中的字节顺序之间的关系。缓存


    大端模式:指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中。例如:安全


    内存视图:

    0000430: 1234 5678 0100 1800 53ef 0100 0100 0000
    0000440: c7b6 1100 0000 3400 0000 0000 0100 ffff复制代码

    在大端模式下,前32位应该这样读:12 34 56 78。性能优化


    所以,大端模式下地址的增加顺序与值的增加顺序相同。


    小端模式:指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中。例如:


    内存视图:

    0000430: 7856 3412 0100 1800 53ef 0100 0100 0000
    0000440: c7b6 1100 0000 3400 0000 0000 0100 ffff复制代码

    在小端模式下,前32位应该这样读:12 34 56 78。


    所以,小端模式下地址的增加顺序与值的增加顺序相反。


    如何检查处理器是大端模式仍是小端模式?联合体Union的存放顺序是全部成员都从低地址开始存放的,利用该特性能够轻松获取CPU对内存采用大端模式仍是小端模式读写。


    int checkCPU(void)
    {
    union w
    {
    int a;
    char b;
    } c;
    c.a = 1;
    return (c.b == 1);
    }复制代码

    若是输出结果是true,则是小端模式,不然是大端模式。


    3.请简述在你所熟悉的处理器(好比双核Cortex-A9)中一条存储读写指令的执行全过程。

    经典处理器架构的流水线是五级流水线:取指、译码、发射、执行和写回。


    现代处理器在设计上都采用了超标量体系结构(Superscalar Architecture)和乱序执行(out-of-order)技术,极大地提升了处理器计算能力。超标量技术可以在一个时钟周期内执行多个指令,实现指令级的并行,有效提升了ILP(Instruction Level Parallelism)指令级的并行效率,同时也增长了整个cache和memory层次结构的实现难度。


    一条存储读写指令的执行全过程很难用一句话来回答。在一个支持超标量和乱序执行技术的处理器当中,一条存储读写指令的执行过程被分解为若干步骤。指令首先进入流水线(pipeline)的前端(Front-End),包括预取(fetch)和译码(decode),通过分发(dispatch)和调度(scheduler)后进入执行单元,最后提交执行结果。全部的指令采用顺序方式(In-Order)经过前端,并采用乱序的方式(Out-of-Order,OOO)进行发射,而后乱序执行,最后用顺序方式提交结果,并将最终结果更新到LSQ(Load-Store Queue)部件。LSQ部件是指令流水线的一个执行部件,能够理解为存储子系统的最高层,其上接收来自CPU的存储器指令,其下链接着存储器子系统。其主要功能是未来自CPU的存储器请求发送到存储器子系统,并处理其下存储器子系统的应答数据和消息。


    不少程序员对乱序执行的理解有偏差。对于一串给定的指令序列,为了提升效率,处理器会找出非真正数据依赖和地址依赖的指令,让它们并行执行。可是在提交执行结果时,是按照指令次序的。总的来讲,顺序提交指令,乱序执行,最后顺序提交结果。例若有两条没有数据依赖的数据指令,后面那条指令的读数据先被返回,它的结果也不能先写回到最终寄存器,而是必须等到前一条指令完成以后才能够。


    对于读指令,当处理器在等待数据从缓存或者内存返回时,它处于什么状态呢?是等在那不动,仍是继续执行别的指令?对于乱序执行的处理器,能够执行后面的指令;对于顺序执行的处理器,会使流水线停顿,直到读取的数据返回。


    如图1.1所示,在x86微处理器经典架构中,存储指令从L1指令cache中读取指令,L1指令cache会作指令加载、指令预取、指令预解码,以及分支预测。而后进入Fetch & Decode单元,会把指令解码成macro-ops微操做指令,而后由Dispatch部件分发到Integer Unit或者FloatPoint Unit。Integer Unit由Integer Scheduler和Execution Unit组成,Execution Unit包含算术逻辑单元(arithmetic-logic unit,ALU)和地址生成单元(address generation unit,AGU),在ALU计算完成以后进入AGU,计算有效地址完毕后,将结果发送到LSQ部件。LSQ部件首先根据处理器系统要求的内存一致性(memory consistency)模型肯定访问时序,另外LSQ还须要处理存储器指令间的依赖关系,最后LSQ须要准备L1 cache使用的地址,包括有效地址的计算和虚实地址转换,将地址发送到L1 Data Cache中。



    图1.1 x86微处理器经典架构图


    如图1.2所示,在ARM Cortex-A9处理器中,存储指令首先经过主存储器或者L2 cache加载到L1指令cache中。在指令预取阶段(instruction prefetch stage),主要是作指令预取和分支预测,而后指令经过Instruction Queue队列被送到解码器进行指令的解码工做。解码器(decode)支持两路解码,能够同时解码两条指令。在寄存器重名阶段(Register rename stage)会作寄存器重命名,避免机器指令没必要要的顺序化操做,提升处理器的指令级并行能力。在指令分发阶段(Dispatch stage),这里支持4路猜想发射和乱序执行(Out-of-Order Multi-Issue with Speculation),而后在执行单元(ALU/MUL/FPU/NEON)中乱序执行。存储指令会计算有效地址并发射到内存系统中的LSU部件(Load Store Unit),最终LSU部件会去访问L1数据cache。在ARM中,只有cacheable的内存地址才须要访问cache。



    图1.2 Cortex-A9结构框图[3]


    在多处理器环境下,还须要考虑Cache的一致性问题。L1和L2 Cache控制器须要保证cache的一致性,在Cortex-A9中cache的一致性是由MESI协议来实现的。Cortex-A9处理器内置了L1 Cache模块,由SCU(Snoop Control Unit)单元来实现Cache的一致性管理。L2 Cache须要外接芯片(例如PL310)。在最糟糕状况下须要访问主存储器,并将数据从新传递给LSQ,完成一次存储器读写的全过程。


    这里涉及计算机体系结构中的众多术语,比较晦涩难懂,如今对部分术语作简单解释。



    • 超标量体系结构(Superscalar Architecture):早期的单发射结构微处理器的流水线设计目标是作到每一个周期能平均执行一条指令,但这一目标不能知足处理器性能增加的要求,为了提升处理器的性能,要求处理器具备每一个周期能发射执行多条指令的能力。所以超标量体系结构是描述一种微处理器设计理念,它可以在一个时钟周期执行多个指令。

    • 乱序执行(Out-of-order Execution):指CPU采用了容许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理的技术,避免处理器在计算对象不可获取时的等待,从而致使流水线停顿。

    • 寄存器重命名(Register Rename):现代处理器的一种技术,用来避免机器指令或者微操做的没必要要的顺序化执行,从而提升处理器的指令级并行的能力。它在乱序执行的流水线中有两个做用,一是消除指令之间的寄存器读后写相关(Write-after-Read,WAR)和写后写相关(Write-after-Write,WAW);二是当指令执行发生例外或者转移指令猜想错误而取消后面的指令时,可用来保证现场的精确。其思路为当一条指令写一个结果寄存器时不直接写到这个结果寄存器,而是先写到一个中间寄存器过渡,当这条指令提交时再写到结果寄存器中。

    • 分支预测(Branch Predictor):当处理一个分支指令时,有可能会产生跳转,从而打断流水线指令的处理,由于处理器没法肯定该指令的下一条指令,直到分支指令执行完毕。流水线越长,处理器等待时间便越长,分支预测技术就是为了解决这一问题而出现的。所以,分支预测是处理器在程序分支指令执行前预测其结果的一种机制。在ARM中,使用全局分支预测器,该预测器由转移目标缓冲器(Branch Target Buffer,BTB)、全局历史缓冲器(Global History Buffer,GHB)、MicroBTB,以及Return Stack组成。

    • 指令译码器(Instruction Decode):指令由操做码和地址码组成。操做码表示要执行的操做性质,即执行什么操做;地址码是操做码执行时的操做对象的地址。计算机执行一条指定的指令时,必须首先分析这条指令的操做码是什么,以决定操做的性质和方法,而后才能控制计算机其余各部件协同完成指令表达的功能,这个分析工做由译码器来完成。例如,Cortex-A57能够支持3路译码器,即同时执行3条指令译码,而Cortex-A9处理器只能同时译码2条指令。

    • 调度单元(Dispatch):调度器负责把指令或微操做指令派发到相应的执行单元去执行,例如,Cortex-A9处理器的调度器单元有4个接口和执行单元链接,所以每一个周期能够同时派发4条指令。

    • ALU算术逻辑单元:ALU是处理器的执行单元,主要是进行算术运算,逻辑运算和关系运算的部件。

    • LSQ/LSU部件(Load Store Queue/Unit):LSQ部件是指令流水线的一个执行部件,其主要功能是未来自CPU的存储器请求发送到存储器子系统,并处理其下存储器子系统的应答数据和消息。


    4.请简述内存屏障(memory barrier)产生的缘由。

    程序在运行时的实际内存访问顺序和程序代码编写的访问顺序不一致,会致使内存乱序访问。内存乱序访问的出现是为了提升程序运行时的性能。内存乱序访问主要发生在以下两个阶段。


    (1)编译时,编译器优化致使内存乱序访问。


    (2)运行时,多CPU间交互引发的内存乱序访问。


    编译器会把符合人类思考的逻辑代码(例如C语言)翻译成CPU运算规则的汇编指令,编译器了解底层CPU的思惟逻辑,所以它会在翻译成汇编时进行优化。例如内存访问指令的从新排序,提升指令级并行效率。然而,这些优化可能会违背程序员原始的代码逻辑,致使发生一些错误。编译时的乱序访问能够经过volatile关键字来规避。


    #define barrier() asm volatile ("" ::: "memory")复制代码

    barrier()函数告诉编译器,不要为了性能优化而将这些代码重排。


    因为现代处理器广泛采用超标量技术、乱序发射以及乱序执行等技术来提升指令级并行的效率,所以指令的执行序列在处理器的流水线中有可能被打乱,与程序代码编写时序列的不一致。另外现代处理器采用多级存储结构,如何保证处理器对存储子系统访问的正确性也是一大挑战。


    例如,在一个系统中含有n个处理器P1~Pn,假设每一个处理器包含Si个存储器操做,那么从全局来看可能的存储器访问序列有多种组合。为了保证内存访问的一致性,须要按照某种规则来选出合适的组合,这个规则叫作内存一致性模型(Memory Consistency Model)。这个规则须要保证正确性的前提,同时也要保证多处理器访问较高的并行度。


    在一个单核处理器系统中,访问内存的正确性比较简单。每次存储器读操做所得到的结果是最近写入的结果,可是在多处理器并发访问存储器的状况下就很难保证其正确性了。咱们很容易想到使用一个全局时间比例部件(Global Time Scale)来决定存储器访问时序,从而判断最近访问的数据。这种内存一致性访问模型是严格一致性(Strict Consistency)内存模型,也称为Atomic Consistency。全局时间比例方法实现的代价比较大,那么退而求其次,采用每个处理器的本地时间比例部件(Local Time Scale)的方法来肯定最新数据的方法被称为顺序一致性内存模型(Sequential Consistency)。处理器一致性内存模型(Processor Consistency)是进一步弱化,仅要求来自同一个处理器的写操做具备一致性的访问便可。


    以上这些内存一致性模型是针对存储器读写指令展开的,还有一类目前普遍使用的模型,这些模型使用内存同步指令,也称为内存屏障指令。在这种模型下,存储器访问指令被分红数据指令和同步指令两大类,弱一致性内存模型(weak consistency)就是基于这种思想的。


    1986年,Dubois等发表的论文描述了弱一致性内存模型的定义。



    • 对同步变量的访问是顺序一致的。

    • 在全部以前的写操做完成以前,不能访问同步变量。

    • 在全部以前同步变量的访问完成以前,不能访问(读或者写)数据。


    弱一致性内存模型要求同步访问是顺序一致的,在一个同步访问能够被执行以前,全部以前的数据访问必须完成。在一个正常的数据访问能够被执行以前,全部以前的同步访问必须完成。这实质上把一致性问题留给了程序员来决定。


    ARM的Cortex-A系列处理器实现弱一致性内存模型,同时也提供了3条内存屏障指令。


    5.ARM有几条memory barrier的指令?分别有什么区别?

    从ARMv7指令集开始,ARM提供3条内存屏障指令。


    (1)数据存储屏障(Data Memory Barrier,DMB)


    数据存储器隔离。DMB指令保证:仅当全部在它前面的存储器访问操做都执行完毕后,才提交(commit)在它后面的存取访问操做指令。当位于此指令前的全部内存访问均完成时,DMB指令才会完成。


    (2)数据同步屏障(Data synchronization Barrier,DSB)


    数据同步隔离。比DMB要严格一些,仅当全部在它前面的存储访问操做指令都执行完毕后,才会执行在它后面的指令,即任何指令都要等待DSB前面的存储访问完成。位于此指令前的全部缓存,如分支预测和TLB(Translation Look-aside Buffer)维护操做所有完成。


    (3)指令同步屏障(Instruction synchronization Barrier,ISB)


    指令同步隔离。它最严格,冲洗流水线(Flush Pipeline)和预取buffers(pretcLbuffers)后,才会从cache或者内存中预取ISB指令以后的指令。ISB一般用来保证上下文切换的效果,例如更改ASID(Address Space Identifier)、TLB维护操做和C15寄存器的修改等。


    内存屏障指令的使用例子以下。


    例1:假设有两个CPU核A和B,同时访问Addr1和Addr2地址。


    Core A:
    STR R0, [Addr1]
    LDR R1, [Addr2]

    Core B:
    STR R2, [Addr2]
    LDR R3, [Addr1]复制代码

    对于上面代码片断,没有任何的同步措施。对于Core A、寄存器R一、Core B和寄存器R3,可能获得以下4种不一样的结果。



    • A获得旧的值,B也获得旧的值。

    • A获得旧的值,B获得新的值。

    • A获得新的值,B获得旧的值。

    • A获得新的值,B获得新的值。


    例2:假设Core A写入新数据到Msg地址,Core B须要判断flag标志后才读入新数据。


    Core A:
    STR R0, [Msg] @ 写新数据到Msg地址
    STR R1, [Flag] @ Flag标志新数据能够读

    Core B:
    Poll_loop:
    LDR R1, [Flag]
    CMP R1,#0 @ 判断flag有没有置位
    BEQ Poll_loop
    LDR R0, [Msg] @ 读取新数据复制代码

    在上面的代码片断中,Core B可能读不到最新的数据,由于Core B可能由于乱序执行的缘由先读入Msg,而后读取Flag。在弱一致性内存模型中,处理器不知道Msg和Flag存在数据依赖性,因此程序员必须使用内存屏障指令来显式地告诉处理器这两个变量有数据依赖关系。Core A须要在两个存储指令之间插入DMB指令来保证两个store存储指令的执行顺序。Core B须要在“LDR R0, [Msg]”以前插入DMB指令来保证直到Flag置位才读入Msg。


    例3:在一个设备驱动中,写入一个命令到一个外设寄存器中,而后等待状态的变化。


    STR R0, [Addr]        @ 写一个命令到外设寄存器
    DSB
    Poll_loop:
    LDR R1, [Flag]
    CMP R1,#0 @ 等待状态寄存器的变化
    BEQ Poll_loop复制代码

    在STR存储指令以后插入DSB指令,强制让写命令完成,而后执行读取Flag的判断循环。


    6.请简述cache的工做方式。

    处理器访问主存储器使用地址编码方式。cache也使用相似的地址编码方式,所以处理器使用这些编码地址能够访问各级cache。如图1.3所示,是一个经典的cache架构图。



    图1.3 经典cache架构


    处理器在访问存储器时,会把地址同时传递给TLB(Translation Lookaside Buffer)和cache。TLB是一个用于存储虚拟地址到物理地址转换的小缓存,处理器先使用EPN(effective page number)在TLB中进行查找最终的RPN(Real Page Number)。若是这期间发生TLB miss,将会带来一系列严重的系统惩罚,处理器须要查询页表。假设这里TLB Hit,此时很快得到合适的RPN,并获得相应的物理地址(Physical Address,PA)。


    同时,处理器经过cache编码地址的索引域(Cache Line Index)能够很快找到相应的cache line组。可是这里的cache block的数据不必定是处理器所须要的,所以有必要进行一些检查,将cache line中存放的地址和经过虚实地址转换获得的物理地址进行比较。若是相同而且状态位匹配,那么就会发生cache命中(Cache Hit),那么处理器通过字节选择和偏移(Byte Select and Align)部件,最终就能够获取所须要的数据。若是发生cache miss,处理器须要用物理地址进一步访问主存储器来得到最终数据,数据也会填充到相应的cache line中。上述描述的是VIPT(virtual Index phg sical Tag)的cache组织方式,将会在问题9中详细介绍。


    如图1.4所示,是cache的基本的结构图。


    图1.4 cache结构图



    • cache地址编码:处理器访问cache时的地址编码,分红3个部分,分别是偏移域(Offset)、索引域(Index)和标记域(Tag)。

    • Cache Line:cache中最小的访问单元,包含一小段主存储器中的数据,常见的cache line大小是32Byte或64Byte等。

    • 索引域(Index):cache地址编码的一部分,用于索引和查找是在cache中的哪一行。

    • 组(Set):相同索引域的cache line组成一个组。

    • 路(Way):在组相联的cache中,cache被分红大小相同的几个块。

    • 标记(Tag):cache地址编码的一部分,用于判断cache line存放的数据是否和处理器想要的一致。


    7.cache的映射方式有full-associative(全关联)、direct-mapping(直接映射)和set-associative(组相联)3种方式,请简述它们之间的区别。为何现代的处理器都使用组相联的cache映射方式?

    (1)直接映射(Direct-mapping)


    根据每一个组(set)的高速缓存行数,cache能够分红不一样的类。当每一个组只有一行cache line时,称为直接映射高速缓存。


    如图1.5所示,下面用一个简单小巧的cache来讲明,这个cache只有4行cache line,每行有4个字(word,一个字是4个Byte),共64 Byte。这个cache控制器可使用两个比特位(bits[3:2])来选择cache line中的字,以及使用另外两个比特位(bits[5:4])做为索引(Index),选择4个cache line中的一个,其他的比特位用于存储标记值(Tag)。


    在这个cache中查询,当索引域和标记域的值和查询的地址相等,而且有效位显示这个cache line包含有效数据时,则发生cache命中,那么可使用偏移域来寻址cache line中的数据。若是cache line包含有效数据,可是标记域是其余地址的值,那么这个cache line须要被替换。所以,在这个cache中,主存储器中全部bit [5:4]相同值的地址都会映射到同一个cache line中,而且同一时刻只有一个cache line,由于cache line被频繁换入换出,会致使严重的cache颠簸(cache thrashing)。




    图1.5 直接眏射的cache和cache地址


    假设在下面的代码片断中,result、data1和data2分别指向0x00、0x40和0x80地址,它们都会使用同一个cache line。


    void add_array(int data1, int data2, int *result, int size)
    {
    int i;
    for (i=0 ; i<size ; i++) {
    result[i] = data1[i] + data2[i];
    }
    }复制代码


    • 当第一次读data1即0x40地址时,由于不在cache里面,因此读取从0x40到0x4f地址的数据填充到cache line中。

    • 当读data2即0x80地址的数据时,数据不在cache line中,须要把从0x80到0x8f地址的数据填充到cache line中,由于地址0x80和0x40映射到同一个cache line,因此cache line发生替换操做。

    • result写入到0x00地址时,一样发生了cache line替换操做。

    • 因此这个代码片断发生严重的cache颠簸,性能会很糟糕。


    (2)组相联(set associative)


    为了解决直接映射高速缓存中的cache颠簸问题,组相联的cache结构在现代处理器中获得普遍应用。


    如图1.6所示,下面以一个2路组相联的cache为例,每一个路(way)包括4个cache line,那么每一个组(set)有两个cache line能够提供cache line替换。



    图1.6 2路组相联的映射关系


    地址0x00、0x40或者0x80的数据能够映射到同一个组中任意一个cache line。当cache line要发生替换操做时,就有50%的几率能够不被替换,从而减少了cache颠簸。


    8.在一个32KB的4路组相联的cache中,其中cache line为32Byte,请画出这个cache的cache line、way和set的示意图。

    在Cortex-A7和Cortex-A9的处理器上能够看到32KB 大小的4路组相联cache。下面来分析这个cache的结构图。


    cache的总大小为32KB,而且是4路(way),因此每一路的大小为8KB:


    way_size = 32 / 4 = 8(KB)


    cache Line的大小为32Byte,因此每一路包含的cache line数量为:


    num_cache_line = 8KB/32B = 256


    因此在cache编码地址Address中,bit[4:0]用于选择cache line中的数据,其中bit [4:2]能够用于寻址8个字,bit [1:0]能够用于寻址每一个字中的字节。bit [12:5]用于索引(Index)选择每一路上cache line,其他的bit [31:13]用做标记位(Tag),如图1.7所示。


    9.ARM9处理器的Data Cache组织方式使用的VIVT,即虚拟Index虚拟Tag,而在Cortex-A7处理器中使用PIPT,即物理Index物理Tag,请简述PIPT比VIVT有什么优点?

    处理器在进行存储器访问时,处理器访问地址是虚拟地址(virtual address,VA),通过TLB和MMU的映射,最终变成了物理地址(physical address,PA)。那么查询cache组是用虚拟地址,仍是物理地址的索引域(Index)呢?当找到cache组时,咱们是用虚拟地址,仍是物理地址的标记域(Tag)来匹配cache line呢?


    cache能够设计成经过虚拟地址或者物理地址来访问,这个在处理器设计时就肯定下来了,而且对cache的管理有很大的影响。cache能够分红以下3类。



    • VIVT(Virtual Index Virtual Tag):使用虚拟地址索引域和虚拟地址的标记域。

    • VIPT(Virtual Index Physical Tag):使用虚拟地址索引域和物理地址的标记域。



    图1.7 32KB 4路组相联cache结构图



    • PIPT(Physical Index Physical Tag):使用物理地址索引域和物理地址的标记域。


    在早期的ARM处理器中(好比ARM9处理器)采用VIVT的方式,不用通过MMU的翻译,直接使用虚拟地址的索引域和标记域来查找cache line,这种方式会致使高速缓存别名(cache alias)问题。例如一个物理地址的内容能够出如今多个cache line中,当系统改变了虚拟地址到物理地址映射时,须要清洗(clean)和无效(invalidate)这些cache,致使系统性能降低。


    ARM11系列处理器采用VIPT方式,即处理器输出的虚拟地址同时会发送到TLB/MMU单元进行地址翻译,以及在cache中进行索引和查询cache组。这样cache和TLB/MMU能够同时工做,当TLB/MMU完成地址翻译后,再用物理标记域来匹配cache line。采用VIPT方式的好处之一是在多任务操做系统中,修改了虚拟地址到物理地址映射关系,不须要把相应的cache进行无效(invalidate)操做。


    ARM Cortex-A系列处理器的数据cache开始采用PIPT的方式。对于PIPT方式,索引域和标记域都采用物理地址,cache中只有一个cache组与之对应,不会产生高速缓存别名的问题。PIPT的方式在芯片设计里的逻辑比VIPT要复杂得多。


    采用VIPT方式也有可能致使高速缓存别名的问题。在VIPT中,使用虚拟地址的索引域来查找cache组,这时有可能致使多个cache组映射到同一个物理地址上。以Linux kernel为例,它是以4KB大小为一个页面进行管理的,那么对于一个页来讲,虚拟地址和物理地址的低12bit(bit [11:0])是同样的。所以,不一样的虚拟地址映射到同一个物理地址,这些虚拟页面的低12位是同样的。若是索引域位于bit [11:0]范围内,那么就不会发生高速缓存别名。例如,cache line是32Byte,那么数据偏移域offset占5bit,有128个cache组,那么索引域占7bit,这种状况下恰好不会发生别名。另外,对于ARM Cortex-A系列处理器来讲,cache总大小是能够在芯片集成中配置的。如表1.1所示,列举出了Cortex-A系列处理器的cache配置状况。


    表1.1 ARM处理器的cache概况
























































     

    Cortex-A7

    Cortex-A9

    Cortex-A15

    Cortex-A53

    数据缓存实现方式

    PIPT

    PIPT

    PIPT

    PIPT

    指令缓存实现方式

    VIPT

    VIPT

    PIPT

    VIPT

    L1数据缓存大小

    8KB~64KB

    16KB/32KB/64KB

    32KB

    8KB~64KB

    L1数据缓存结构

    4路组相联

    4路组相联

    2路组相联

    4路组相联

    L2缓存大小

    128KB~1MB

    External

    512KB~4MB

    128KB~2MB

    L2缓存结构

    8路组相联

    External

    16路组相联

    16路组相联


    10.请画出在二级页表架构中虚拟地址到物理地址查询页表的过程。

    如图1.8所示,ARM处理器的内存管理单元(Memory Management Unit, MMU)包括TLB和Table Walk Unit两个部件。TLB是一块高速缓存,用于缓存页表转换的结果,从而减小内存访问的时间。一个完整的页表翻译和查找的过程叫做页表查询(Translation table walk),页表查询的过程由硬件自动完成,可是页表的维护须要软件来完成。页表查询是一个相对耗时的过程,理想的状态下是TLB里存有页表相关信息。当TLB Miss时,才会去查询页表,而且开始读入页表的内容。



    图1.8 ARM内存管理架构


    (1)ARMv7-A架构的页表


    ARMv7-A架构支持安全扩展(Security Extensions),其中Cortex-A15开始支持大物理地址扩展(Large Physical Address Extension,LPAE)和虚拟化扩展,使得MMU的实现比之前的ARM处理器要复杂得多。


    如图1.9所示,若是使能了安全扩展,ARMv7-A处理器分红安全世界(Secure World)和非安全世界(Non-secure World,也称为Normal World)。



    图1.9 ARMv7-A架构的运行模式和特权


    若是处理器使能了虚拟化扩展,那么处理器会在非安全世界中增长一个Hyp模式。


    在非安全世界中,运行特权被划分为PL0、PL1和PL2。



    • PL0等级:这个特权等级运行在用户模式(User Mode),用于运行用户程序,它是没有系统特权的,好比没有权限访问处理器内部的硬件资源。

    • PL1等级:这个等级包括ARMv6架构中的System模式、SVC模式、FIQ模式、IRQ模式、Undef模式,以及Abort模式。Linux内核运行在PL1等级,应用程序运行在PL0等级。若是使能了安全扩展,那么安全模式里有一个Monitor模式也是运行在secure PL1等级,管理安全世界和非安全世界的状态转换。

    • PL2等级:若是使能了虚拟化扩展,那么超级管理程序(Hypervisor)就运行这个等级,它运行在Hyp模式,管理GuestOS之间的切换。


    当处理器使能了虚拟化扩展,MMU的工做会变得更复杂。咱们这里只讨论处理器没有使能安全扩展和虚拟化扩展的状况。ARMv7处理器的二级页表根据最终页的大小能够分为以下4种状况。



    • 超级大段(SuperSection):支持16MB大小的超级大块。

    • 段(section):支持1MB大小的段。

    • 大页面(Large page):支持64KB大小的大页。

    • 页面(page):4KB的页,Linux内核默认使用4KB的页。


    若是只须要支持超级大段和段映射,那么只须要一级页表便可。若是要支持4KB页面或64KB大页映射,那么须要用到二级页表。不一样大小的映射,一级或二级页表中的页表项的内容也不同。如图1.10所示,以4KB页的映射为例。



    图1.10 ARMv7-A二级页表查询过程


    当TLB Miss时,处理器查询页表的过程以下。



    • 处理器根据页表基地址控制寄存器TTBCR和虚拟地址来判断使用哪一个页表基地址寄存器,是TTBR0仍是TTBR1。页表基地址寄存器中存放着一级页表的基地址。

    • 处理器根据虚拟地址的bit[31:20]做为索引值,在一级页表中找到页表项,一级页表一共有4096个页表项。

    • 第一级页表的表项中存放有二级页表的物理基地址。处理器根据虚拟地址的bit[19:12]做为索引值,在二级页表中找到相应的页表项,二级页表有256个页表项。

    • 二级页表的页表项里存放有4KB页的物理基地址,所以处理器就完成了页表的查询和翻译工做。


    如图 1.11 所示的4KB映射的一级页表的表项,bit[1:0]表示是一个页映射的表项,bit[31:10]指向二级页表的物理基地址。



    图1.11 4KB映射的一级页表的表项


    如图1.12所示的4KB映射的二级页表的表项,bit[31:12]指向4KB大小的页面的物理基地址。



    图1.12 4KB映射的二级页表的表项


    (2)ARMv8-A架构的页表


    ARMv8-A架构开始支持64bit操做系统。从ARMv8-A架构的处理器能够同时支持64bit和32bit应用程序,为了兼容ARMv7-A指令集,从架构上定义了AArch64架构和AArch32架构。


    AArch64架构和ARMv7-A架构同样支持安全扩展和虚拟化扩展。安全扩展把ARM的世界分红了安全世界和非安全世界。AArch64架构的异常等级(Exception Levels)肯定其运行特权级别,相似ARMv7架构中特权等级,如图1.13所示。



    • EL0:用户特权,用于运行普通用户程序。

    • EL1:系统特权,一般用于运行操做系统。

    • EL2:运行虚拟化扩展的Hypervisor。

    • EL3:运行安全世界中的Secure Monitor。


    在AArch64架构中的MMU支持单一阶段的地址页表转换,一样也支持虚拟化扩展中的两阶段的页表转换。



    • 单一阶段页表:虚拟地址(VA)翻译成物理地址(PA)。

    • 两阶段页表(虚拟化扩展):



    图1.13 AArch64架构的异常等级


    阶段1——虚拟地址翻译成中间物理地址(Intermediate Physical Address,IPA)。


    阶段2——中间物理地址IPA翻译成最终物理地址PA。


    在AArch64架构中,由于地址总线带宽最多48位,因此虚拟地址VA被划分为两个空间,每一个空间最大支持256TB。



    • 低位的虚拟地址空间位于0x0000_0000_0000_0000到0x0000_FFFF_FFFF_FFFF。若是虚拟地址最高位bit63等于0,那么就使用这个虚拟地址空间,而且使用TTBR0 (Translation Table Base Register)来存放页表的基地址。

    • 高位的虚拟地址空间位于0xFFFF_0000_0000_0000到0xFFFF_FFFF_FFFF_FFFF。 若是虚拟地址最高位bit63等于1,那么就使用这个虚拟地址空间,而且使用TTBR1来存放页表的基地址。


    如图1.14所示,AArch64架构处理地址映射图,其中页面是4KB的小页面。AArch64架构中的页表支持以下特性。



    图1.14 AArch64架构地址映射图(4KB页)



    • 最多能够支持4级页表。

    • 输入地址最大有效位宽48bit。

    • 输出地址最大有效位宽48bit。

    • 翻译的最小粒度能够是4KB、16KB或64KB。


    11.在多核处理器中,cache的一致性是如何实现的?请简述MESI协议的含义。

    高速缓存一致性(cache coherency)产生的缘由是在一个处理器系统中不一样CPU核上的数据cache和内存可能具备同一个数据的多个副本,在仅有一个CPU核的系统中不存在一致性问题。维护cache一致性的关键是跟踪每个cache line的状态,并根据处理器的读写操做和总线上的相应传输来更新cache line在不一样CPU核上的数据cache中的状态,从而维护cache一致性。cache一致性有软件和硬件两种方式,有的处理器架构提供显式操做cache的指令,例如PowerPC,不过如今大多数处理器架构采用硬件方式来维护。在处理器中经过cache一致性协议来实现,这些协议维护一个有限状态机(Finite State Machine,FSM),根据存储器读写指令或总线上的传输,进行状态迁移和相应的cache操做来保证cache一致性,不须要软件介入。


    cache一致性协议主要有两大类别,一类是监听协议(Snooping Protocol),每一个cache都要被监听或者监听其余cache的总线活动;另一类是目录协议(Directory Protocol),全局统一管理cache状态。


    1983年,James Goodman提出Write-Once总线监听协议,后来演变成目前最流行的MESI协议。总线监听协议依赖于这样的事实,即全部的总线传输事务对于系统内全部的其余单元是可见的,由于总线是一个基于广播通讯的介质,于是能够由每一个处理器的cache来进行监听。这些年来人们已经提出了数十种协议,这些协议基本上都是write-once协议的变种。不一样的协议须要不一样的通讯量,要求太多的通讯量会浪费总线带宽,使总线争用变多,留下来给其余部件使用的带宽就减小。所以,芯片设计人员尝试将保持一致性的协议所须要的总线通讯量减小到最小,或者尝试优化某些频繁执行的操做。


    目前,ARM或x86等处理器普遍使用相似MESI协议来维护cache一致性。MESI协议的得名源于该协议使用的修改态(Modified)、独占态(Exclusive)、共享态(Shared)和失效态(Invalid)这4个状态。cache line中的状态必须是上述4种状态中的一种。MESI协议还有一些变种,例如MOESI协议等,部分的ARMv7-A和ARMv8-A处理器使用该变种。


    cache line中有两个标志:dirty和valid。它们很好地描述了cache和内存之间的数据关系,例如数据是否有效、数据是否被修改过。在MESI协议中,每一个cache line有4个状态,可用2bit来表示。


    如表1.2和表1.3所示,分别是MESI协议4个状态的说明和MESI协议各个状态的转换关系。


    表1.2 MESI协议定义



























    状态

    描述

    M(修改态)

    这行数据有效,数据被修改,和内存中的数据不一致,数据只存在本cache中

    E(独占态)

    这行数据有效,数据和内存中数据一致,数据只存在于本cache中

    S(共享态)

    这行数据有效,数据和内存中数据一致,多个cache有这个数据副本

    I(无效态)

    这行数据无效


    表1.3 MESI状态说明





































































































    当前状态

    操做

    响应

    迁移状态

    修改态M

    总线读

    Flush该cache line到内存,以便其余CPU能够访问到最新的内容,状态变成S态

    S

    总线写

    Flush该cache line到内存,而后其余CPU修改cache line,所以本cache line执行清空数据操做,状态变成I态

    I

    处理器读

    本地处理器读该cache line,状态不变

    M

    处理器写

    本地处理器写该cache line,状态不变

    M

    独占态E

    总线读

    独占状态的cache line是干净的,所以状态变成S

    S

    总线写

    数据被修改,该cache line不能再使用了,状态变成I

    I

    本地读

    从该cache line中取数据,状态不变

    E

    本地写

    修改该cache line数据,状态变成M

    M

    共享态S

    总线读

    状态不变

    S

    总线写

    数据被修改,该cache line不能再使用了,状态变成I

    I

    本地读

    状态不变

    S

    本地写

    修改了该cache line数据,状态变成M;其余核上共享的cache line的状态变成I

    M

    无效态I

    总线读

    状态不变

    I

    总线写

    状态不变

    I

    本地读

    ● 若是cache miss,则从内存中取数据,cache line变成E;

    ● 若是其余cache有这份数据,且状态为M,则将数据更新到内存,本cache再从内存中取数据,两个cache line的状态都为S;

    ● 若是其余cache有这份数据,且状态是S或E,本cache从内存中取数据,这些cache line都变成S

    E/S

    本地写

    ● 若是cache miss,从内存中取数据,在cache中修改,状态变成M;

    ● 若是其余cache有这份数据,且状态为M,则要先将数据更新到内存,其余cache line状态变成I,而后修改本cache line的内容

    M

    • 修改和独占状态的cache line,数据都是独有的,不一样点在于修改状态的数据是脏的,和内存不一致,而独占态的数据是干净的和内存一致。拥有修改态的cache line会在某个合适的时候把该cache line写回内存中,其后的状态变成共享态。
    • 共享状态的cache line,数据和其余cache共享,只有干净的数据才能被多个cache共享。
    • I的状态表示这个cache line无效。

    • MOESI协议增长了一个O(Owned)状态,并在MESI协议的基础上从新定义了S状态,而E、M和I状态与MESI协议的对应状态相同。



      • O位。O位为1,表示在当前cache 行中包含的数据是当前处理器系统最新的数据复制,并且在其余CPU中可能具备该cache行的副本,状态为S。若是主存储器的数据在多个CPU的cache中都具备副本时,有且仅有一个CPU的Cache行状态为O,其余CPU的cache行状态只能为S。与MESI协议中的S状态不一样,状态为O的cache行中的数据与存储器中的数据并不一致。

      • S位。在MOESI协议中,S状态的定义发生了细微的变化。当一个cache行状态为S时,其包含的数据并不必定与存储器一致。若是在其余CPU的cache中不存在状态为O的副本时,该cache行中的数据与存储器一致;若是在其余CPU的cache中存在状态为O的副本时,cache行中的数据与存储器不一致。


      12.cache在Linux内核中有哪些应用?

      cache line的空间都很小,通常也就32 Byte。CPU的cache是线性排列的,也就是说一个32 Byte的cache line与32 Byte的地址对齐,另外相邻的地址会在不一样的cache line中错开,这里是指32n的相邻地址。


      cache在linux内核中有不少巧妙的应用,读者能够在阅读本书后面章节遇到相似的状况时细细体会,暂时先总结概括以下。


      (1)内核中经常使用的数据结构一般是和L1 cache对齐的。例如,mm_struct、fs_cache等数据结构使用“SLAB_HWCACHE_ALIGN”标志位来建立slab缓存描述符,见proc_caches_init()函数。


      (2)一些经常使用的数据结构在定义时就约定数据结构以L1 Cache对齐,使用“_cacheline_internodealigned_in_smp”和“_cacheline_aligned_in_smp”等宏来定义数据结构,例如struct zone、struct irqaction、softirq_vec[ ]、irq_stat[ ]、struct worker_pool等。


      cache和内存交换的最小单位是cache line,若结构体没有和cache line对齐,那么一个结构体有可能占用多个cache line。假设cache line的大小是32 Byte,一个自己小于32 Byte的结构体有可能横跨了两条cache line,在SMP中会对系统性能有不小的影响。举个例子,如今有结构体C1和结构体C2,缓存到L1 Cache时没有按照cache line对齐,所以它们有可能同时占用了一条cache line,即C1的后半部和C2的前半部在一条cache line中。根据cache 一致性协议,CPU0修改结构体C1的时会致使CPU1的cache line失效,同理,CPU1对结构体C2修改也会致使CPU0的cache line失效。若是CPU0和CPU1反复修改,那么会致使系统性能降低。这种现象叫作“cache line伪共享”,两个CPU本来没有共享访问,由于要共同访问同一个cache line,产生了事实上的共享。解决上述问题的一个方法是让结构体按照cache line对齐,典型地以空间换时间。include/linux/cache.h文件定义了有关cache相关的操做,其中cacheline_aligned_in_smp的定义也在这个文件中,它和L1_CACHE_BYTES对齐。


      [include/linux/cache.h]

      #define SMP_CACHE_BYTES L1_CACHE_BYTES

      #define 复制代码cacheline_aligned attribute ((aligned (SMP_CACHE_BYTES)))
      #define cacheline_aligned_in_smp cacheline_aligned

      #ifndef cacheline_aligned
      #define
      cacheline_aligned \
      attribute ((aligned (SMP_CACHE_BYTES), \
      section (".data..cacheline_aligned")))
      #endif /复制代码cacheline_aligned */

      #define cacheline_aligned_in_smp
      cacheline_aligned

      #define __
      cacheline_internodealigned_in_smp \
      attribute ((aligned (1 << (INTERNODE_CACHE_SHIFT))))复制代码



      (3)数据结构中频繁访问的成员能够单独占用一个cache line,或者相关的成员在cache line中彼此错开,以提升访问效率。例如,struct zone数据结构中zone->lock和zone-> lru_lock这两个频繁被访问的锁,可让它们各自使用不一样的cache line,以提升获取锁的效率。


      再好比struct worker_pool数据结构中的nr_running成员就独占了一个cache line,避免多CPU同时读写该成员时引起其余临近的成员“颠簸”现象,见第5.3节。


      (4)slab的着色区,见第2.5节。


      (5)自旋锁的实现。在多CPU系统中,自旋锁的激烈争用过程致使严重的CPU cacheline bouncing现象,见第4章关于自旋锁的部份内容。


      13.请简述ARM big.LITTLE架构,包括总线链接和cache管理等。

      ARM提出大小核概念,即big.LITTLE架构,针对性能优化过的处理器内核称为大核,针对低功耗待机优化过的处理器内核称为小核。


      如图1.15所示,在典型big.LITTLE架构中包含了一个由大核组成的集群(Cortex-A57)和小核(Cortex-A53)组成的集群,每一个集群都属于传统的同步频率架构,工做在相同的频率和电压下。大核为高性能核心,工做在较高的电压和频率下,消耗更多的能耗,适用于计算繁重的任务。常见的大核处理器有Cortex-A1五、Cortex-A5七、Cortex-A72和Cortex-A73。小核性能虽然较低,但功耗比较低,在一些计算负载不大的任务中,不用开启大核,直接用小核便可,常见的小核处理器有Cortex-A7和Cortex-A53。



      图1.15 典型的big.LITTLE架构


      如图1.16所示是4核Cortex-A15和4核Cortex-A7的系统总线框图。



      • Quad Cortex-A15:大核CPU簇。

      • Quad Cortex-A7:小核CPU簇。



      图1.16 4核A15和4核A7的系统总线框图



      • CCI-400模块[4]:用于管理大小核架构中缓存一致性的互连模块。CCI-400只能支持两个CPU簇(cluster),而最新款的CCI-550能够支持6个CPU簇。

      • DMC-400[5]:内存控制器。

      • NIC-400[6]:用于AMBA总线协议的链接,能够支持AXI、AHB和APB总线的链接。

      • MMU-400[7]:系统内存管理单元。

      • Mali-T604:图形加速控制器。

      • GIC-400:中断控制器。


      ARM CoreLink CCI-400模块用于维护大小核集群的数据互联和cache一致性。大小核集群做为主设备(Master),经过支持ACE协议的从设备接口(Slave)链接到CCI-400上,它能够管理大小核集群中的cache一致性和实现处理器间的数据共享。此外,它还支持3个ACE-Lite从设备接口(ACE-Lite Slave Interface),能够支持一些IO主设备,例如GPU Mali-T604。经过ACE-Lite协议,GPU能够监听处理器的cache。CCI-400还支持3个ACE-Lite主设备接口,例如经过DMC-400来链接LP-DDR2/3或DDR内存设备,以及经过NIC-400总线来链接一些外设,例如DMA设备和LCD等。


      ACE协议,全称为AMBA AXI Coherency Extension协议,是AXI4协议的扩展协议,增长了不少特性来支持系统级硬件一致性。模块之间共享内存不须要软件干预,硬件直接管理和维护各个cache之间的一致性,这能够大大减小软件的负载,最大效率地使用cache,减小对内存的访问,进而下降系统功耗。


      14.cache coherency和memory consistency有什么区别?

      cache coherency高速缓存一致性关注的是同一个数据在多个cache和内存中的一致性问题,解决高速缓存一致性的方法主要是总线监听协议,例如MESI协议等。而memory consistency关注的是处理器系统对多个地址进行存储器访问序列的正确性,学术上对内存访问模型提出了不少,例如严格一致性内存模型、处理器一致性内存模型,以及弱一致性内存模型等。弱内存访问模型在如今处理器中获得普遍应用,所以内存屏障指令也获得普遍应用。


      15.请简述cache的write back有哪些策略。

      在处理器内核中,一条存储器读写指令通过取指、译码、发射和执行等一系列操做以后,率先到达LSU部件。LSU部件包括Load Queue和Store Queue,是指令流水线的一个执行部件,是处理器存储子系统的最顶层,链接指令流水线和cache的一个支点。存储器读写指令经过LSU以后,会到达L1 cache控制器。L1 cache控制器首先发起探测(Probe)操做,对于读操做发起cache读探测操做并将带回数据,写操做发起cache写探测操做。写探测操做以前须要准备好待写的cache line,探测工做返回时将会带回数据。当存储器写指令得到最终数据并进行提交操做以后才会将数据写入,这个写入能够Write Through或者Write Back。


      对于写操做,在上述的探测过程当中,若是没有找到相应的cache block,那么就是Write Miss,不然就是Write Hit。对于Write Miss的处理策略是Write-Allocate,即L1 cache控制器将分配一个新的cache line,以后和获取的数据进行合并,而后写入L1 cache中。


      若是探测的过程是Write Hit,那么真正写入有两种模式。



      • Write Through(直写模式):进行写操做时,数据同时写入当前的cache、下一级cache或主存储器中。Write Through策略能够下降cache一致性的实现难度,其最大的缺点是消耗比较多的总线带宽。

      • Write Back(回写模式):在进行写操做时,数据直接写入当前cache,而不会继续传递,当该Cache Line被替换出去时,被改写的数据才会更新到下一级cache或主存储器中。该策略增长了cache一致性的实现难度,可是有效下降了总线带宽需求。


      16.请简述cache line的替换策略。

      因为cache的容量远小于主存储器,当Cache Miss发生时,不只仅意味着处理器须要从主存储器中获取数据,并且须要将cache的某个cache line替换出去。在cache的Tag阵列中,除了具备地址信息以外还有cache block的状态信息。不一样的cache一致性策略使用的cache状态信息并不相同。在MESI协议中,一个cache block一般含有M、E、S和I这4个状态位。


      cache的替换策略有随机法(Random policy)、先进先出法(FIFO)和最近最少使用算法(LRU)。



      • 随机法:随机地肯定替换的cache block,由一个随机数产生器来生成随机数肯定替换块,这种方法简单,易于实现,但命中率比较低。

      • 先进先出法:选择最早调入的那个cache block进行替换,最早调入的块有可能被屡次命中,可是被优先替换,于是不符合局部性规律。

      • 最近最少使用算法:LRU算法根据各块使用的状况,老是选择最近最少使用的块来替换,这种算法较好地反映了程序局部性规律。


      在Cortex-A57处理器中,L1 cache采用LRU算法,而L2 cache采用随机算法。在最新的Cortex-A72处理器中,L2 cache采有伪随机算法(pseudo-random policy)或伪LRU算法(pseudo-least-recently-used policy)。


      17.多进程间频繁切换对TLB有什么影响?现代的处理器是如何面对这个问题的?

      在现代处理器中,软件使用虚拟地址访问内存,而处理器的MMU单元负责把虚拟地址转换成物理地址,为了完成这个映射过程,软件和硬件共同来维护一个多级映射的页表。当处理器发现页表中没法映射到对应的物理地址时,会触发一个缺页异常,挂起出错的进程,操做系统软件须要处理这个缺页异常。咱们以前有提到过二级页表的查询过程,为了完成虚拟地址到物理地址的转换,查询页表须要两次访问内存,即一级页表和二级页表都是存放在内存中的。


      TLB(Translation Look-aside Buffer)专门用于缓存内存中的页表项,通常在MMU单元内部。TLB是一个很小的cache,TLB表项(TLB entry)数量比较少,每一个TLB表项包含一个页面的相关信息,例若有效位、虚拟页号、修改位、物理页帧号等。当处理器要访问一个虚拟地址时,首先会在TLB中查询。若是TLB表项中没有相应的表项,称为TLB Miss,那么就须要访问页表来计算出相应的物理地址。若是TLB表项中有相应的表项,那么直接从TLB表项中获取物理地址,称为TLB命中。


      TLB内部存放的基本单位是TLB表项,TLB容量越大,所能存放的TLB表项就越多,TLB命中率就越高,可是TLB的容量是有限的。目前Linux内核默认采用4KB大小的小页面,若是一个程序使用512个小页面,即2MB大小,那么至少须要512个TLB表项才能保证不会出现TLB Miss的状况。可是若是使用2MB大小的大页,那么只须要一个TLB表项就能够保证不会出现TLB Miss的状况。对于消耗内存以GB为单位的大型应用程序,还可使用以1GB为单位的大页,从而减小TLB Miss状况。


      18.请简述NUMA架构的特色。

      如今绝大数ARM系统都采用UMA的内存架构(Uniform Memory Architechture),即内存是统一结构和统一寻址。对称多处理器(Symmetric Multiple Processing,SMP)系统大部分都采用UMA内存架构。所以在UMA架构的系统中有以下特色。



      • 全部硬件资源都是共享的,每一个处理器都能访问到系统中的内存和外设资源。

      • 全部处理器都是平等关系。

      • 统一寻址访问内存。

      • 处理器和内存经过内部的一条总线链接在一块儿。


      如图1.17所示,SMP系统相对比较简洁,可是缺点也很明显。由于全部对等的处理器都经过一条总线链接在一块儿,随着处理器数量的增多,系统总线成为系统的最大瓶颈。


      NUMA系统[8]是从SMP系统演化过来的。如图1.18所示,NUMA系统由多个内存节点组成,整个内存体系能够做为一个总体,任何处理器均可以访问,只是处理器访问本地内存节点拥有更小的延迟和更大的带宽,处理器访问远程内存节点速度要慢一些。每一个处理器除了拥有本地的内存以外,还能够拥有本地总线,例如PCIE、STAT等。



      图1.17 SMP架构示意图



      图1.18 NUMA架构示意图


      如今的x86阵营的服务器芯片早已支持NUMA架构了,例如Intel的至强服务器。对于ARM阵营,2016年Cavium公司发布的基于ARMv8-A架构设计的服务器芯片“ThunderX2”[9]也开始支持NUMA架构。


      19.ARM从Cortex系列开始性能有了质的飞越,好比Cortex-A8/A15/A53/A72,请说说Cortex系列在芯片设计方面作了哪些重大改进?

      计算机体系结构是一个权衡的艺术,尺有所短,寸有所长。在处理器领域经历多年的优胜劣汰,市面上流行的处理器内核在技术上日渐趋同。


      ARM处理器在Cortex系列以后,加入了不少现代处理器的一些新技术和特性,已经具有了和Intel一较高下的能力,例如2016年发布的Cortex-A73处理器。


      2005年发布的Cortex-A8内核是第一个引入超标量技术的ARM处理器,它在每一个时钟周期内能够并行发射两条指令,但依然使用静态调度的流水线和顺序执行方式。Cortex-A8内核采用13级整型指令流水线和10级NEON指令流水线。分支目标缓冲器(Branch Target Buffer,BTB)使用的条目数增长到512,同时设置了全局历史缓冲器(Global History Buffer,GHB)和返回堆栈(Return Stack,RS)部件,这些措施极大地提升了指令分支预测的成功率。另外,还加入了way-prediction部件。


      2007年Cortex-A9发布,引入了乱序执行和猜想执行机制以及扩大L2 cache的容量。


      2010年Cortex-A15发布,最高主频能够到2.5GHz,最多支持8个处理器核心,单个cluster最多支持4个处理器核心,采有超标量流水线技术,具备1TB物理地址空间,支持虚拟化技术等新技术。指令预取总线宽度为128bit,一次能够预取4~8条指令,和Cortex-A9相比,提升了一倍。Decode部件一次能够译码3条指令。Cortex-A15引入了Micro-Ops概念。Micro-ops指令和X86的uops指令想法较为相似。在x86处理器中,指令译码单元把复杂的CISC指令转换成等长的upos指令,再进入到指令流水线中;而Cortex-A15,指令译码单元把RISC指令进一步细化为Micro-ops指令,以充分利用指令流水线中的多个并发执行单元。指令译码单元为3路指令译码,在一个时钟周期能够同时译码3条指令。


      2012年发布64位的Cortex-A53和Cortex-A57,ARM开始进军服务器领域。Cortex-A57是首款支持64位的ARM处理器内核,采用3发乱序执行流水线(Out-of-Order pipeline),而且增长数据预取功能。


      2015年发布Cortex-A57的升级版本Cortex-A72,如图1.19所示。A72在A57架构的基础上作了大量优化工做,包括新的分支预测单元,改善解码流水线设计等。在指令分发



      图1.19 Cortex-A72处理器架构图[10]


      单元(Dispatch)也作了很大优化,由原来A57架构的3发射变成了5发射,同时发射5条指令,而且还支持并行执行8条微操做指令,从而提升解码器的吞吐量。


      最新近展


      最近几年,x86和ARM阵营都在各自领域中不断创新。异构计算是一个很热门的技术方向,好比Intel公司最近发布了集成FPGA的至强服务器芯片。FPGA能够在客户的关键算法中提供可编程、高性能的加速能力,另外提供了灵活性,关键算法的更新优化,不须要购买大量新硬件。在数据中心领域,从事海量数据处理的应用中有很多关键算法须要优化,如密钥加速、图像识别、语音转换、文本搜索等。在安防监控领域,FPGA能够实现对大量车牌的并行分析。强大的至强处理器加上灵活高效的FPGA会给客户在云计算、人工智能等新兴领域带来新的技术创新。对于ARM阵营,ARM公司发布了最新的Cortex-A75处理器以及最新处理器架构DynamIQ等新技术。DynmaIQ技术新增了针对机器学习和人工智能的全新处理器指令集,并增长了多核配置的灵活性。另外ARM公司也发布了一个用于数据中心应用的指令集——Scalable Vector Extensions,最高支持2048 bit可伸缩的矢量计算。


      除了x86和ARM两大阵营的创新外,最近几年开源指令集(指令集架构,Instruction Set Architecture,ISA)也是很火热的新发展方向。开源指令集的表明做是OpenRISC,而且Open Risk已经被Linux内核接受,成为官方Linux内核支持的一种体系结构。可是因为OpenRISC是由爱好者维护的,所以更新缓慢。最近几年,伯克利大学正在尝试从新设计一个全新的开源指令集,而且不受专利的约束和限制,这就是RISC-V,其中“V”表示变化(variation)和向量(vectors)。RISC-V包含一个很是小的基础指令集和一系列可选的扩展指令集,最基础的指令集只包含40条指令,经过扩展能够支持64位和128位运算以及变长指令。


      伯克利大学对RISC-V指令集不断改进,迅速获得工业界和学术届的关注。2016年,RISC-V基金会成立,成员包括谷歌、惠普、甲骨文、西部数据、华为等巨头,将来这些大公司很是有可能会将RISC-V运用到云计算或者IoT等产品中。RISC-V指令集相似Linux内核,是一个开源的、现代的、没有专利问题和历史包袱的全新指令集,而且以BSD许可证发布。


      目前RISC-V已经进入了GCC/Binutils的主线,相信很快也会被官方Linux内核接受。另外目前已经有多款开源和闭源的RISC-V CPU的实现,不少第三方工具和软件厂商也开始支持RISC-V。RISC-V是否会变成开源硬件或是开源芯片领域的Linux呢?让咱们拭目以待吧!


      推荐书籍


      计算机体系结构是一门计算机科学的基础课程,除了阅读ARM的芯片手册之外,还能够阅读一些经典的书籍和文章。



      • 《计算机体系结构:量化研究方法》,英文版是《Computer Architecture : A Quantitative》,做者John L. Hennessy, David A. Patterson。

      • 《计算机组成与体系结构:性能设计》,做者William Stallings。

      • 《大话处理器:处理器基础知识读本》,做者万木杨。

      • 《浅谈cache memory》,做者王齐。

      • 《ARM与x86》,做者王齐。

      • 《现代体系结构上的UNIX系统:内核程序员的对称多处理和缓存技术》,做者Curt Schimmel。




      [1] infocenter.arm.com

      [2] Trustzone技术在ARMv6架构中已实现,在ARMv7-A架构的Cortex-A系列处理器中开始大规模使用。

      [3] 该图参考pc.watch.impress.co.jp/docs/column…。虽然该图出自非ARM官方资料,可是对理解Cortex-A系列处理器内部架构颇有帮助。

      [4] 详见<ARM CoreLink CCI-400 Cache Coherent Interconnect Technical Reference Manual>。

      [5] 详见<ARM CoreLink DMC-400 Dynamic Memory Controller Technical Reference>。

      [6] 详见<ARM CoreLink NIC-400 Network Interconnect Technical Reference>。

      [7] 详见<ARM CoreLink MMU-400 System Memory Management Technical Reference>。

      [8] frankdenneman.nl/2016/07/06/…

      [9] www.cavium.com/ThunderX2_A…

      [10] pc.watch.impress.co.jp/img/pcw/doc…

      相关文章
      相关标签/搜索