精华内容
下载资源
问答
  • 问题提出:我用后台服务程序来控制ffmpeg进程来组播音频流,但发现ffmpeg不支持...红线相关的是ffmpeg工程需要包含的文件,只需要修改ffmpeg.c中的一些代码即可实现,修改代码的原则是尽可能把"改动"独立出来,放到另一...

    问题提出: 我用后台服务程序来控制ffmpeg进程来组播音频流, 但发现ffmpeg不支持按键暂停, 只支持按键退出"q"和其他几个按键功能,如下所示:

    为了让我的后台服务程序能发按键指令给ffmpeg进程,我仔细查看了ffmpeg程序相关的源码,

    红线相关的是ffmpeg工程需要包含的文件,只需要修改ffmpeg.c中的一些代码即可实现,修改代码的原则是尽可能把"改动"独立出来,放到另一个qzm_pause.c和qzm_pause.h中。在不得不改动ffmpeg.c时,用#if 0 #else #endif来注释掉改动的代码和使能qzm改动的代码,如下所示:

    经测试,能实现在ffmpeg的stdin输入"p"后, 组播的媒体流暂停,输入"P"后,组播流继续。下图是关键代码切片。

    思路是:ffmpeg的main函数用一个while()来循环一个transcode()转码函数,而进入transcode()后会调用transcode_step()函数来执行一步转码。只要用一个flag变量来代表Pause和Resume,即能实现进入transcode_step()的控制,从而控制媒体流暂停和继续。

    展开全文
  • 阅读经典——《深入理解计算机系统》06 本文,我们要做一件大胆的...指令的分阶段执行 SEQ的状态改变周期 SEQ的各阶段实现 流水线的一般原则 流水线冒险 更完善的设计 与真实指令集架构的差距 指令集...

    阅读经典——《深入理解计算机系统》06

    本文,我们要做一件大胆的事情,从零开始实现一个全新的指令集架构,以此深入理解处理器的工作原理。

    1. 指令集发展历史概况
    2. Y86指令集
    3. 指令集及其编码
    4. 硬件控制语言HCL
    5. 存储器和时钟
    6. 指令的分阶段执行
    7. SEQ的状态改变周期
    8. SEQ的各阶段实现
    9. 流水线的一般原则
    10. 流水线冒险
    11. 更完善的设计
    12. 与真实指令集架构的差距

    指令集发展历史概况

    开始我们的创造之旅前,先了解一下历史上的指令集架构都有哪些。

    一个处理器支持的指令和指令的字节级编码称为它的指令集架构(Instruction Set Architecture, ISA)。

    最为我们熟知的就是x86架构,因为我们日常所用的个人电脑就采用了x86架构的处理器。目前世界上最大的两个处理器制造商Intel和AMD都有基于x86架构的一系列产品。从Intel i386处理器开始,x86架构进入32位时代,称为IA32架构(Intel Architecture 32bit)。后来,32位也不能满足我们的需求了,Intel开始进军64位处理器领域,提出IA64架构。但是,这个架构并不是我们现在在用的64位处理器,而是一个与x86完全无关的新的处理器架构,不保持向后兼容。虽然可以实现很高的性能,但是由于兼容性不好,市场反应冷淡。于此同时,AMD公司抓住机会,率先提出了x86-64处理器架构,支持64位的同时保持向后兼容,一举在与Intel的市场竞争中占据了主动权。当然,Intel也不会执迷不悟,他们果断放弃了IA64,开始转向x86-64架构,并逐步收回丧失的市场份额。后来,虽然AMD将自己的架构命名为AMD64,Intel将自己的架构命名为Intel64,但人们仍然习惯性地将它们统称为x86-64。

    Y86指令集

    为了致敬伟大的x86指令集架构,我们将自己的指令集架构命名为Y86。其实呢,Y86的设计理念完全借鉴x86,相当于一个简化的x86架构。

    要想从头设计一个指令集架构,需要先规定指令集和指令集编码,然后将每个指令划分为几个阶段分步执行,每个阶段只需要做简单的一两项工作,之后,将硬件设备结合适当的逻辑电路实现指令每个阶段的工作。下面我们详细讲解具体的实现过程。

    指令集及其编码

    对于一个简易的指令集来说,不需要太多的指令,能实现基本的数据转移和流程控制就够了。下图列出了Y86指令集中包含的所有指令,以及每个指令的编码。

     
    Y86指令集

    这些都是非常基本的指令,不过看起来有些奇怪,这是因为我们把x86中的movl指令替换成了四个独立的指令rrmovlirmovlrmmovlmrmovl,每个指令指明了操作数的来源,这样就避免了各种寻址方式的麻烦。

    可以看到,各个指令的长度从1字节到6字节不等,这样编码可以减少程序代码占用的空间。第1个字节的高4位作为指令编码,用来区分不同的指令,低4位要么是0,要么是fnfn称为功能代码,用来区分不同的操作。如下图所示,不同的功能码在不同的指令中有不同的含义。在运算指令中,分别代表加、减、与和异或;在分支跳转指令中,分别代表不同的跳转条件;在条件转移指令中,分别代表不同的转移条件。

     
    Y86指令集的功能码

    第2个字节,对于大部分指令来说存放的是寄存器标识符,请看下图:

     
    Y86程序寄存器标识符

    每个寄存器与一个数字一一对应,F代表无寄存器操作数。

    最后,有些指令还包含四个字节的立即数。

    举一个例子来帮助我们更好地理解指令编码。例如对于如下指令

    rmmovl %esp, 0x12345(%edx)
    

    对应的编码为

    40 42 45 23 01 00
    

    其中,从左到右,40是指令编码,42分别是寄存器%esp对应的4和寄存器%edx对应的2,45230100是偏移量0x12345在小端机器上的表示。

    硬件控制语言HCL

    处理器的各个硬件设备(比如ALU、程序计数器)之间通常需要特定功能的逻辑电路来连接,在设计阶段,我们使用一种结构化的语言来描述这些逻辑关系。

    HCL(Hardware Control Language)是一种类似C的硬件控制语言,用于描述处理器的控制逻辑。

    举一个简单的例子,对于如下所示的组合逻辑电路:

     
    组合电路

    可以用HCL语言表示为

    e = (a && !(b||c)) || (!d && !(b||c))
    

    这句话描述了输出和输入的逻辑关系,无论多么复杂的组合电路,都可以用最基本的与或非门来实现。HCL在后面将会有大量的应用。

    存储器和时钟

    细心的读者可能会注意到,上一段话讲到“无论多么复杂的组合电路”。为什么特别强调组合电路呢,因为还有另一种电路——时序电路。

    大家应该都有基本的电路知识,组合电路只是完成了一个函数的功能,不同的输入导致不同的输出,电路本身并不存储任何信息。而时序电路就不一样了,它可以存储信息,而且在时钟信号的控制下对输入做出反应。

    接下来,重点来了。在处理器中有两种存储设备:

    • 时钟寄存器(简称寄存器) 存储单个位或字。时钟信号控制寄存器加载输入值。
    • 随机访问存储器(简称存储器) 存储多个字,由地址选择读写哪个字。这里所说的存储器可以分为两种:处理器的虚拟存储器系统和寄存器文件。前者是通常意义上的内存系统,后者才是我们指令集中8个寄存器标识符对应的通用寄存器。

    下图为寄存器的工作原理,寄存器输出一直保持在当前状态,直到时钟上升沿,新的输入将成为当前的寄存器状态。

     
    寄存器操作

    寄存器文件可以看成这样一个功能块:

     
    寄存器文件.png

    它有两个读端口和一个写端口,支持读写同时操作。值得注意的是,寄存器文件的读操作是即时的,而写操作是基于时钟的。也就是说,读出的值valA和valB随时根据srcA和srcB的变化而变化,而要写入的值valW只在clock的上升沿才能写入。仔细想想,寄存器文件的读写特性好像和寄存器是完全一样的,只不过是多了一个选址操作。

    指令的分阶段执行

    虽然宏观上来看,指令已经是程序不可分割的基本元素。但在处理器中,一条指令的执行还是要分多个阶段,这样才可以提高硬件的处理效率。在Y86架构中,我们将每个指令的执行分为6个阶段。

    取指:从PC中取出当前要执行的指令,并按照指令编码对其分解,得到icode、ifun、rA、rB、valC等值。
    译码:根据rA、rB取出对应寄存器的值valA、valB。
    执行:ALU在不同指令下执行不同的操作,包括简单运算、地址加减等等,运算结果为valE,运算时会对条件码产生影响。
    访存:从存储器读取数据或向存储器写入数据。读出的值为valM。
    写回:将前面生成的结果写回寄存器文件。
    更新PC:将PC设置成下一条指令的地址。

    这些步骤现在看起来杂乱无章,不知有何用处。但仔细分析,可以看到,每个阶段只做与一两个硬件相关的事情,由输入决定输出,完全可以在一个时钟周期内做完。而各个阶段之间的联系就是各种信号的输入和输出,比如,译码阶段的输出valA可以作为执行阶段的输入,而执行阶段的输出又可以作为写回阶段的输入,这样就可以用简单的组合电路把这些硬件单元连接起来,实现我们需要的功能。

    为了大家更清楚地理解各个阶段的作用,我们用一个例子来详细说明。

     
    指令的分阶段实现

    上图分别为OPl rA, rBrrmovl rA, rBirmovl V, rB这三个指令的分阶段执行过程。在取指阶段中,M表示存储器,M1[PC]表示以PC为基址从存储器中取出1字节数据。由于各个指令长短不一,因此取指阶段做的事情也不尽相同。在该阶段最后,会计算出PC的新值valP。译码阶段是从寄存器文件中取出寄存器的值,用R[rA]来表示寄存器rA的值。执行阶段对于OPl指令来说会设置状态码CC,而后两个指令则不会对状态码产生影响。访存阶段在这三个指令中都没有涉及。最后的更新PC阶段将valP的值赋值给PC。

    当我读到这里的时候,我有很大的疑问:不是说每个阶段只做一件简单的事情吗,但是不同的指令在同一个阶段做的事情似乎各不相同。比如刚才的三个指令,在执行阶段只有OPl指令会设置状态码,而另外两个不会,这是为什么?包括书中后面举的其它例子,更新PC阶段并不一定是把valP的值赋值给PC,有些指令比如call和ret,它们会将valC的值或valM的值赋值给PC,这又是怎么做到的?

    大家是否也想到了这些问题呢?很显然,每个阶段对不同的指令有不同的响应是很自然的事情,不然怎么适应各个指令的不同功能呢。我们前面提到的HCL硬件控制语言,就是要完成这个任务,控制每个指令在每个阶段要完成的任务。

    好了,在详细说明如何用HCL控制逻辑之前,先给出完整的硬件结构图。

     
    SEQ硬件结构

    我们要注意图中不同颜色的方块和不同粗细的线条,它们代表着不同的意思。绿色块代表基本的硬件单元,比如ALU、寄存器文件、PC,基本上我们都已经接触过。灰色方块将是我们下一步研究的重点,它们是HCL描述的组合逻辑电路,用于连接绿色块并实现特定的选择或逻辑运算。白色圆圈并没有特殊的含义,只是用来标识信号线的名称。图中还有三种线条,粗实线表示宽度为字长的信号线,细实线表示宽度为1个字节或更窄的信号线,而虚线表示单个位的信号线。

    图中从下到上分别是刚才介绍的取指、译码(写回)、执行、访存和更新PC阶段。由于译码和写回阶段都是对寄存器文件的操作,因此它们在图中画在了同一个位置。用圆圈标出的信号就是前文提到的各个阶段产生的中间值,这些值通常在不同指令中担任着不同的角色,因此会出现一个信号分叉为两个信号的情况。例如图中valA产生之后分为两条线,一条通向ALUB控制逻辑,另一条通向Data控制逻辑。再例如图中valM产生之后分为两条线,一条通向New PC控制逻辑,另一条通向寄存器文件的输入端。我们需要明白的是,一个信号分为两个信号,意味着两个接收端都可以读取到该信号的值,但读取到该值并不意味着使用该值,接收端的控制逻辑决定是否使用该值,下文将会详细叙述。

    SEQ的状态改变周期

    上一张图的标题我没做解释,其实是留了个疑问。SEQ的意思是Sequential(顺序的),“SEQ硬件结构”就是说“顺序的硬件结构”或者“硬件结构的顺序实现”。什么!!难道还有其它方式的实现?答案是当然的,我们留到后面再揭开谜底。SEQ的硬件结构使得指令必须按顺序一个接一个地执行,下一条指令的开始必须晚于上一条指令的结束。这就导致处理器效率极其低下,因为一个指令必须在一个时钟周期内通过所有阶段,而由于电路延迟的固有因素,通过所有阶段需要的时间很长,也就限制了时钟周期无法提高。然而,为什么一个指令必须在一个时钟周期内通过所有阶段呢?

    因为对于时序逻辑电路,比如SEQ中的存储器、寄存器文件、CC和程序计数器,它们只在时钟信号的上升沿写入数据。当前个指令结束,下个指令开始的时候,时钟信号上升沿触发这几个硬件单元的更新。如果在下一个时钟周期上升沿到来之前,需要更新的新值还没有产生,这个指令就相当于没执行或执行了一半。因此时钟周期不能提得太高,否则将造成指令执行紊乱。

    下图展示了两个指令周期的过程中,由时钟控制的各个硬件单元的状态改变。

     
    跟踪SEQ的两个执行周期

    可以看到,图中将四个时序逻辑电路之外的其它部分作为一个组合逻辑电路的整体来看待。当周期3开始时,组合逻辑电路开始运行,直到周期3结束前,所有结果都已得出,准备写入存储器等设备。当周期4开始时,存储器、寄存器文件、CC和程序计数器的值被更新,同时,这些新值被组合逻辑电路读取并开始计算结果,如此循环往复。因此,每个时钟周期SEQ的状态改变一次。

    SEQ的各阶段实现

    前文给出的SEQ硬件结构图只是一个大概的实现,有些细节并没有给出。现在,我们一个阶段一个阶段地分析SEQ的具体实现。

    取指阶段

     
    SEQ取指阶段

    指令从内存中取出后按字节分为了两部分:Split和Align。Split又分为icode和ifun。Align分为rA、rB和valC,这些都很容易理解。重点在于PC增加的逻辑。PC增加多少要根据本条指令的长短来决定,而本条指令的长短又在于指令中是否包含寄存器标识,以及是否包含常数valC,图中的两个组合电路Need valC和Need rigids就是用来做这个判断。

    以Need rigids为例,它的HCL语言描述如下:

    bool need_rigids = 
        icode in { IRRMOVL, IOPL, IPUSHL, IPOPL, IIRMOVL, IRMMOVL, IMRMOVL };
    

    意即,当icode等于括号中7种指令码之一时,need_rigids为真。也就是说这7种指令中包含寄存器标识。同理,need_valC也可以用这个枚举的方法确定,只需要查前面的指令集编码表,找到包含valC的指令,放在括号里面就行了。

    当need_rigids和need_valC都确定了之后,PC increment将按如下公式计算新的PC值,其实就是加上了该条指令的长度:

    newPC = oldPC + 1 + need_rigids + 4*need_valC
    

    现在我们明白了,灰色方框代表的组合电路可以用HCL语言来描述。而实际电路中这些HCL语句将通过综合成为真正的组合逻辑电路。在这里,HCL是一种很好的抽象,将原理与具体的实现相分离,方便我们的设计。

    译码和写回阶段

     
    SEQ译码和写回阶段

    这两个阶段都与寄存器文件的读写相关。从取指阶段得到的信号icode、rA和rB在这里作为输入信号,经过一些组合电路生成寄存器文件的输入。我们的目的是,在译码阶段,对于那些需要使用特定寄存器的命令,从寄存器文件中取出这些寄存器的值,地址由srcA和srcB来决定,结果输出为valA和valB;在写回阶段,将执行阶段的结果valE或访存阶段的结果valM写回特定的寄存器,寄存器的地址由dstE和dstM来决定。以组合电路srcA为例,它的HCL表述为:

    int srcA = [
        icode in { IRRMOVL, IRMMOVL, IOPL, IPUSHL } : rA;
        icode in { IPOPL, IRET } : RESP;
        1 : RNONE;    #Don't need register
    ];
    

    方括号类似C语言中的switch语句,当第一个分号前的条件满足时返回rA,后面的两个条件不再考虑;否则再判断第二个条件是否满足,满足则返回RESP;否则返回RNONE,表示不需要读取寄存器文件。从中可以看出,在译码阶段,当指令为第一个分号前的四种时,将读取rA寄存器的值并放入结果valA;当指令为第二个分号前的两种时,将读取RESP寄存器的值并放入结果valA;否则,不必读取任何寄存器。

    与srcA类似的还有srcB、dstE和dstM三个组合逻辑电路,它们的HCL表述可以从SEQ的硬件结构和指令集编码中分析得出,不再一一叙述。

    执行阶段

     
    SEQ执行阶段

    ALU需要两个操作数和一个alufun信号,alufun信号用于指明ALU对两个操作符执行怎样的逻辑运算(加、减、与、异或)。

    以第一个操作数aluA为例,它的HCL描述如下:

    int aluA = [
        icode in { IRRMOVL, IOPL } : valA;
        icode in { IIRMOVL, IRMMOVL, IMRMOVL } : valC;
        icode in { ICALL, IPUSH } : -4;
        icode in { IRET, IPOPL } : 4;
        # Other instructions don't need ALU
    ];
    

    可以看出,操作数aluA有时取valA,有时取valC,有时取-4或4,完全决定于指令类型。

    alufun信号的HCL描述如下:

    int alufun = [
        icode == IOPL : ifun;
        1 : ALUADD;
    ];
    

    仅当指令为IOPL指令(即运算指令)时,alufun由ifun决定,其它情况下ALU全部当做加法器来使用。这也就不难理解为什么刚才aluA会取-4或4,因此此时aluA作为加法器的一个加数,而另一个加数从图中可以看到只能来自于valB,虽然valB在译码阶段的HCL我们并没有给出,不过可以告诉大家valB在这四种情况下的输出都是RESP。因此对于ICALL和IPUSH来说是为了让栈指针esp-4,对于IRET和IPOPL来说是为了让栈指针esp+4。

    访存阶段

     
    SEQ访存阶段

    Mem read和Mem write决定当前指令对存储器是读操作还是写操作。Mem addr和Mem data决定读写操作的地址和数据。以Mem addr为例,HCL描述如下:

    int mem_addr = [
        icode in { IRMMOVL, IPUSHL, ICALL, IMRMOVL } : valE;
        icode in { IPOPL, IRET } : valA;
        # Other instructions don't need address
    ];
    

    更新PC阶段

     
    SEQ更新PC阶段

    新的PC值来源可以从valC、valM和valP中选择,New PC的HCL描述如下:

    int new_pc = [
        # Call. Use instruction constant
        icode == ICALL : valC;
        # Taken branch. Use instruction constant
        icode == IJXX && Cnd : valC;
        # Completion of RET instruction. Use value from stack
        icode == IRET : valM;
        # Default. Use incremented PC
        1 : valP;
    ];
    

    流水线的一般原则

    到此为止,我们的前奏刚刚落幕,终于要步入正题了。(这个前奏的确有点长,哈哈。)

    在“SEQ的状态改变周期”中埋下了一个伏笔,现在我们来揭开谜底。由于SEQ的时钟频率太低,我们需要想些办法来提高时钟频率。通常可以想到两种途径,一是缩短每条指令的执行时间,二是让多条指令同时执行。方法一不可行,因为每条指令的执行时间很难压缩,这是由电路的固有性质决定的。因此只能采用方法二,即流水线技术。

    先来用一个形象的比喻来形容流水线技术。有一种带传送带的自助餐厅,食物摆在传送带上经过顾客,顾客可以随意取走自己喜欢的食品。如果我们把一盘食物当做一条指令,而传送带两旁的顾客当做指令执行的各个阶段,那么SEQ的实现就相当于每次只往传送带上放一盘食物,当这盘食物走到传送带尽头后再放下一盘食物,如果餐馆真这么做的话顾客恐怕都要饿死了。实际情况是,食物一盘接一盘地放在传送带上,每个顾客送走这一盘食物马上迎来下一盘食物,效率大大提高。

    处理器架构的流水线技术也是这样,每个阶段都有一条指令正在执行,6个阶段就会有6条指令同时执行,将吞吐量提高为SEQ时的6倍。这样是不是感觉非常给力呢,不过,事情远没有想象中那么简单,最直接的问题是多个指令间会不会相互干扰?

    我们回顾一下SEQ的硬件结构图,不同阶段间经常有跨阶段的连线,比如取指阶段得到的valC直接连接到了更新PC阶段的New PC。这在流水线情况下会出问题,因为后面的指令会覆盖前面指令产生的valC,因此,当先前的指令到达更新PC阶段再回头取valC的值时,已经不是当初自己在译码阶段生成的值了。怎么办呢?

    解决方案也很容易想到,把每条指令后面有可能用到的值都保存下来不就行了。相当于每个阶段多加一套寄存器,在阶段开始时将这些寄存器的值更新为当前指令配套的值。在流水线技术中,这些插入到各个阶段间的寄存器称为流水线寄存器。

    现在我们的处理器架构更新为PIPE-(Pipeline-,减号表示非最终版本),如下图所示。

     
    PIPE-硬件结构

    与SEQ相比有两处变化,一是将更新PC阶段和取指阶段放在了一起,在取指之前更新PC;二是每两个阶段间插入了流水线寄存器。这些流水线寄存器是基于时钟更新的,每个时钟周期的开始将会更新这些寄存器中的数据,相当于把当前指令的状态传递到了下一个阶段。

    流水线冒险

    现在大功告成了吗?还没有。当我们仔细分析PIPE-的时候我们会发现仍然存在一些问题。虽然流水线寄存器隔离了各个指令之间的数据共享,但是多个指令之间仍然存在依赖,包括两个方面:

    数据依赖:前一条指令写入的寄存器或存储器正好是后一条指令需要读取的寄存器或存储器。在PIPE-中,当后一条指令在译码阶段读寄存器的时候,前一条指令才刚刚到执行阶段,因此新值还没有写入寄存器,如果此时后一条指令直接读寄存器的话,读到的是旧值,这就违反了代码顺序执行的规则。

    控制依赖:当一条指令是jump、call或return时,下一条指令的地址是无法提前确定的,它依赖于当前指令的执行结果。因此流水线很可能需要中断。

    这些依赖可能导致流水线产生计算错误,这种现象称为流水线冒险。我们先来考虑数据冒险。下图画出了一段代码的分阶段执行过程。

     
    prog1代码段的执行过程

    irmovl $3, %eaxaddl %edx, %eax之间插入了三个空指令。这样的话,前者执行完写回阶段,后者才开始执行译码阶段,保证了读取寄存器前已经写入完毕。不发生数据冒险。

    再看下图。

     
    prog2代码段的执行过程

    现在去掉了一个空指令,情况立马恶化。指令0x006的写回阶段和指令0x00e的译码阶段同时发生,但由于写回寄存器的操作直到第7周期的开始才会生效,因此译码阶段读出的值仍然是旧值,出现数据冒险现象。

    如果把剩下的两个空指令也去掉,结果可想而知,肯定会发生更严重的数据冒险,我们在此不再验证。接下来考虑如何避免数据冒险。

    仍然有两种解决方案:

    暂停:与插入nop空指令类似,处理器自动向可能发生数据冒险的代码间插入bubble,使当前正在执行的指令暂停一个时钟周期。

     
    prog2使用暂停时的执行过程

    如上图所示,当addl指令执行到译码阶段时,检测到将会发生数据冒险,于是插入一个bubble,addl指令在译码阶段重复一个时钟周期。

    如果把所有nop都去掉,仍然可以用插入bubble的方法解决数据冒险,只不过需要插入多个bubble而已,如下图所示。

     
    prog4使用暂停时的执行过程

    转发:暂停有一点很不好,它会降低程序执行效率,因为加入了很多无用的指令,纯粹在浪费时间。而转发可以更充分地利用每一个周期的时间。

    仍然以刚才的代码段为例讲解转发如何起作用。

     
    prog2使用转发时的执行过程

    如图,当addl到译码阶段的时候,irmovl到写回阶段,由于还没有写入寄存器,因此读取数据时发生数据冒险。不过,我们可以用一个巧妙的方法避免这个冒险。既然写回阶段需要等到下个周期开始才能写入寄存器,那不如直接把要写入的值转发给译码阶段,这样的话译码阶段也不需要再从寄存器读了,直接拿转发来的值用就行了。

    接下来,如果是prog3代码段呢?

     
    prog3使用转发时的执行过程

    prog3和prog2的区别在于少了一个nop指令,这就导致当addl到译码阶段的时候irmovl指令才到访存阶段。不过似乎对转发并没有影响,因为irmovl指令并不操作内存,在下一个阶段将要写入寄存器的值现在已经产生了,就是M_valE(需要注解一下,M_valE的意思是M阶段的流水线寄存器中保存的valE的值,请查看前面的PIPE-硬件结构图),所以直接把M_valE转发给译码阶段就行了。

    再接下来,如果是prog4代码段呢?

     
    prog4使用转发时的执行过程

    现在,一个nop指令都没有了,irmovl后面紧跟着addl,当addl到译码阶段的时候irmovl才到执行阶段。可是令人惊讶的是,仍然可以转发。首先,我们可以发现最后需要的寄存器的值就是在执行阶段经过计算得出的。其次,我们要考虑到执行阶段得出结果需要一定时间,这个时间会不会导致不能按时转发到译码阶段呢?答案是否定的。因为译码阶段即使很早拿到这个值,也会等到下一个周期开始才把它写入执行阶段的流水线寄存器。因此只要在下个周期开始之前计算出这个值就可以了,而这个条件是永远都能得到满足的。

    有没有感觉到很神奇呢?竟然可以用转发在不降低程序效率的条件下解决数据冒险问题,简直太棒了。可是任何事情都不是完美的,刚才的例子只是irmovl后面跟addl且两者使用同一个寄存器,而实际程序有非常多种可能的组合,是不是转发可以解决所有的问题?我们看下面这个例子。

     
    load/use数据冒险

    prog5代码段的0x018和0x01e两行代码称为加载/使用数据冒险,mrmovl将数据从存储器加载到寄存器%eax,然后紧接着addl使用寄存器%eax的值。仍然用转发,将mrmovl执行阶段的值转发给addl,却得到了错误的结果。其实原因很容易想到,因为mrmovl指令需要到访存阶段才能获取到正确的值并赋值给%eax,因此再从执行阶段转发到译码阶段已经完全不可行了。如何解决这个问题呢?我们可以把暂停和转发两种方式结合起来,先暂停一个周期,然后mrmovl到了访存阶段就可以把值正确地转发给addl了。

    好了,解决了这么多问题,终于可以给出我们的最终版硬件结构图了。

     
    PIPE硬件结构

    比PIPE-增加的内容就是为了解决数据冒险问题而增加的转发电路,转发的接收方基本都在译码阶段。

    更完善的设计

    任何事情都讲究完美,我们现在得到的PIPE其实还不够完美,有些关键细节没有考虑到。

    异常处理:处理器非常重要的一个方面就是异常处理。很多指令执行过程中都可能发生各种各样的异常,比如访问存储器时无效的地址、无效的指令的编码等等。当程序发生异常时,应该立即中止程序,从外面来看的效果应该是正好停在异常发生的位置:即前面的代码已经完全执行,而后面的代码完全没有执行。看起来很简单的事情在PIPE中并不那么容易实现,因为流水线中有多个指令同时执行,如果某个指令在某个阶段发生了异常,此时很可能后面的代码已经执行了一部分,要想得到完全没执行的效果,就要消除掉已经产生的影响,这需要加强控制逻辑的功能。

    控制冒险:上一节流水线冒险中我们提到了控制依赖,它会导致控制冒险。当执行到条件跳转指令时,需要做分支预测,一旦预测错误,就需要消除已经执行的若干条指令,重新执行正确分支的指令。当执行到子函数返回指令时,需要从存储器中取出返回地址,因此下一条指令直到访存阶段才能开始执行。这些特殊情况都需要我们特殊考虑,并在控制逻辑中实现。

    如果详细讲解这两部分的具体实现,又会花很多篇幅,有兴趣的朋友可以访问这本书的官网进一步了解。

    与真实指令集架构的差距

    本文讲述了Y86指令集架构的设计过程,虽然叙述已经足够粗略,可还是写了这么长的篇幅。然而如果与真实的指令集架构(比如x86)的复杂度相比那又真是小巫见大巫了。我们只规定了一个非常简单的指令集,并完成了一个简易的实现。而真实的指令集会包含非常多的指令,包括一些多周期的指令,比如浮点数运算指令,这些指令无法在一个周期内完成,因此需要一些额外的硬件单元的支持。Y86中的存储器被我们看做是理想的存储单元,我们认为数据的存取操作都可以在一个时钟周期内完成。然而CPU速率与内存速率其实相差上千倍,通常需要多级缓存构成一个复杂的存储器层次结构才能加快存取效率。现代处理器还采用了多发射和乱序执行技术,已经不是Y86中所描述的一个阶段一个阶段地执行了,而是多条指令同时执行,而且与它们在代码中的先后顺序无关。近些年,处理器向多核方向发展,多个核具有更强的处理能力,也使指令在代码级别的并行执行成为潮流。今后,处理器会采用哪些新技术我们无从得知,但一定会变得越来越复杂。不过万变不离其宗,理解了处理器和指令集的基本原理,我们可以看透一切,再复杂的系统也是从基本形式一步步扩展得到的,把握核心才是最关键的。

    关注作者文集《深入理解计算机系统》,第一时间获取最新发布文章。

    参考资料

    深入理解计算机系统(4.2)---硬件的魅力 左潇龙



    作者:金戈大王
    链接:https://www.jianshu.com/p/009e31c93d7c
    來源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

    转载于:https://www.cnblogs.com/zzdbullet/p/9354591.html

    展开全文
  • 选手们都站在同一个起跑线上,体现了公平公正的原则。 1 8路数字抢答器的总体设计 1.1 方案的确定 采用MCS-51系列单片机AT89S51作为控制核心,该系统可以完成运算控制、信号识别以及显示功能的实现。由于用了单片机...
  • sdxypchl20180323井下人员定位系统是由地面监控中心主计算机在系统软件支持下,无线信息基站将自动采集有效识别距离内的标识卡的信息,并根据... 遵循“统一发卡、统一配备、统一管理”的原则,将标识卡视为“上岗证...

    sdxypchl20180323

    井下人员定位系统是由地面监控中心主计算机在系统软件支持下,无线信息基站将自动采集有效识别距离内的标识卡的信息,并根据系统指令,通过传输网络将相关数据传送至地面中心站。数据信息经分析处理后,将井下人员(或机车等移动目标)动态分布在主计算机界面中得以实时反映,从而实现井下安全状态在井上数字化管理的目的。  遵循“统一发卡、统一配备、统一管理”的原则,将标识卡视为“上岗证”或“坑道准入证”,按准许上岗人员实行“一人一卡”制。  

    1、根据矿井监测需求,在井下坑道、峒室、作业面等地点安装ML-M5000无线信息基站,并通过485总线传输接口相互连接为井下高速工业以太网,从而构成完整通讯线路。

    2、矿山生产单位输入工作人员相关信息后,向下井工作人员颁发并装备标识卡。系统数据库记录该标识卡相对应人员的基本信息,包括姓名、年龄、性别、所属班组、所属工种、职务、本人照片、有效期等基本信息。  

    3、井下人员定位系统可自动生成考勤作业的统计与管理等方面的报表资料,提高管理效益。  

    4、生产单位可根据生产计划,对该标识卡进行授权管理。授权范围包括:该员工可以准入的坑道或作业面、该员工可进行工作的时段等,超出规定工作范围或时间范围的给与警示,严重者可进一步处理。为防止无关人员和非法人员进入坑道或作业面,系统设臵该卡准入坑道或作业面的时效管理模块及卡的失效、报失等。  

    5、坑道一旦发生安全事故,监控中心在第一时间内可以知道被困人员所在的位臵及事故发生的时间,便于事故救助工作的开展。  

    进入坑道的工作人员必须随身携带标识卡,当持卡人员经过设臵识别系统的地点时被系统识别,系统将读取该卡号信息,通过系统传输网络,将持卡人通过的地点、时间等资料传输到地面监控中心进行数据管理。


    展开全文
  • 4)DMA方式也算是一个很确定方式,不会丢失脉冲,但是高速时候,会较多占用内部总线同时会使用一个多余DMA控制器,而且有个缺点,就是使用起来比较复杂,没有达到KISS原则 个人推荐方式,低速时中断方式,...
  • 关于windows下ftp客户端问题

    千次阅读 2018-04-06 10:47:14
    近期在研究ftp服务器,本着方便原则,就是用了window系统自带ftp客户端进行测试。测试使用post模式进行测试,功能正常,使用passive模式时发现怎么测试,都无法未完成功能,百度了下,发送literal PASV 指令,...

     近期在研究ftp服务器,本着方便原则,就是用了window系统自带的ftp客户端进行测试。测试使用post模式进行测试,功能正常,使用passive模式时发现怎么测试,都无法未完成功能,百度了下,发送literal PASV 指令,服务端能正常回返码,但是还是无法进入passive模式进行数据传输。后来查询了资料,发现windows下的ftp cli无法是用passive模式。

     查找了很多资料发现ncftp能实现passive,并且能支持passive模式,其下载网址为http://www.ncftp.com/download/,希望能对有疑惑的朋友有所帮助。

    展开全文
  • ARM是微处理器行业一家知名企业,arm处理器以体积小和高性能...armv6, armv7, armv7s是ARM CPU不同指令集,原则上是向下兼容。如iPhone4S CPU支持armv7, 但它同时兼容armv6,只是使用armv6指令可能无法充分
  • (注意)本资源为网上整理分享,如有侵权请站内信,本人立即删除 文件1 实验一 进程调度(时间片轮转法) 实验二 模拟银行家算法 文件2 实验一 进程管理 实验二 进程调度1)优先数调度;2)循环轮转调度 实验三 存储管理...
  • CAS

    2021-03-30 09:59:08
    CAS是Central Authentication Service缩写,中央认证服务,一种独立开放指令协议 CAP原则又称CAP定理,指是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition ...
  • cpu模型机课程设计.zip

    2010-12-07 13:59:04
    进行的顺序是:先执行控制台指令的微程序流程图,然后执行机器指令的微程序流程图。当全部微程序流程图检查完毕后,如果存储器和运算器功能执行正确,就算总调第一步完成。 第二步是在内存中装入包括有全部指令系统...
  • CruiseYoung提供带有详细书签电子书籍目录 http://blog.csdn.net/fksec/article/details/7888251 SQL Server 2008实战(SQL Server程序员和DBA不可或缺权威参考手册) 基本信息 原书名: SQL Server 2008 ...
  • 文章目录1.概述1.1. 走进并行世界1.2 java并行程序基础1.3 jdk并发包1.4 锁优化以及注意事项1.5 并行模式与算法1.6 java 8 9 10与并发1.7 使用akka构建高并发程序1.8 并行程序...指令重排与happen-Before原则。 1.
  • 软件设计规范

    2015-03-11 11:57:50
    须知,经过培训,莫尔司码电报报可以比说话语速还快!因此,计算机语言前途迷茫;实际上也确实迷茫,历史上语言层出不穷本身就说明了问题,至今仍然如此。在当今,必须建立这样观点:语言是因人而异;...
  • 操作系统实验

    2013-12-30 11:30:48
    指令的地址按下述原则生成: ①50%的指令是顺序执行的;②25%的指令是均匀分布在前地址部分;③25%的指令是均匀分布在后地址部分。 具体的实施方法是:①在[0,319]的指令地址之间随机选取一起点m;②顺序执行一条...
  • 数据对齐问题

    千次阅读 2002-07-04 09:52:00
    发信人: law (游戏*人生), 信区: C 标 题: Re: 句牢骚 发信站: 饮水思源 (Fri May 24 15:59:56 2002)... 对齐原则一般是对应指令操作数据长度。 例如*(int *)p = 1; 在MIPS上一般会被编译成sw(store word)指令 p要
  • ERP标准模板

    2014-03-19 11:06:14
    化纤企业ERP推广模板 2003年4月 1 化纤企业总体设计原则: 3 1.1 化纤行业主要业务流程设计原则 3 ... SAP向优化软件提供相应生产、销售和物料移动信息,能够从优化软件中获得优化后生产订单等指令信息。
  • c语言编写单片机技巧

    2009-04-19 12:15:17
    答:汇编语言是一种用文字助记符来表示机器指令的符号语言,是最接近机器码的一种语言。其主要优点是占用资源少、程序执行效率高。但是不同的CPU,其汇编语言可能有所差异,所以不易移植。 C语言是一种结构化的...
  • 正是为满足这一类需求,EnableDTS公文传输管理系统构建了一个对上、对下、对左右的纵向和横向交错的公文传输网络,实现一点对一点或一点对多点的公文传输功能,简化了繁杂的工作环节和程序,推动了行政指令的有效...
  • asp.net知识库

    2015-06-18 08:45:45
    遭遇 ASP.NET 2.0 只读 TextBox 回后信息丢失 bug asp.net2.0:扩展ImageButton控件定制自己需要功能 ASP.NET 2.0 正式版中无刷新页面开发(示例代码补充) ASP.NET2.0中themes、Skins轻松实现网站换肤! ...
  • 1.2.6 一颗现代处理器,每秒大概可以执行多少条简单MOV指令,有哪些主要影响因素 1.2.7 请分析 MaxCompute 产品与分布式技术关系、当前大数据计算平台类产品市场现状和发展趋势 1.2.8 对大数据平台中...
  • 不同为1"的原则得到0,0001的第3位为0,0010的第3位为0,则异或结果的第3位得到0,0001的第2位为0,0010的第2位为1,则异或结果的第2位得到1,0001的第1位为1,0010的第1位为0,则异或结果的第1位得到1,组合起来...
  • 交通信号灯用于道路平面交叉路口,通过对车辆、行人发出行进或停止的指令,使人与人、车与车之间尽可能减少相互干扰,从而提高路口通行能力,保障路口畅通和安全。本文介绍了一种城市十字路口交通信号灯控制系统。...
  • 什么是VLAN

    2013-10-23 09:59:12
     从技术角度讲,VLAN划分可依据不同原则,一般有以下三种划分方法: 基于端口  这种划分是把一个或多个交换机上几个端口划分一个逻辑组,这是最简单、最有效划分方法。该方法只需网络管理员对网络设备交换...
  • 微机原理与接口技术试题及答案

    热门讨论 2009-06-20 21:23:27
    2. CPU执行IN指令时有效信号组合是( 1 )。 (1) =0, =1 (2) =0, =0 (3) =0, =1 (4) =0, =0 3.某计算机字长是16位,它存储器容量是64KB,若按字编址那么它最大寻址范围是( 2 )。 (1)64K字 (2)32K字 (3)64KB (4...
  • 图解 Java 虚拟机系列(二)字节码指令 Android 进阶攻略 精华面试题,长期更新! Android 入门学习指南 Android 高级学习指南 Android 资深(专家)学习指南 图解 Android 系列 图解 Android 系列(一)揭秘...
  • 面向对象高级知识 - “三大支柱” / 类与类之间关系 / 垃圾回收 / 魔术属性和方法 / 混入 / 元类 / 面向对象设计原则 / GoF设计模式 迭代器和生成器 - 相关魔术方法 / 创建生成器两种方式 / 并发和异步编程 - 多...
  • MongoDB管理与开发精要(PDF)

    热门讨论 2012-01-17 10:41:45
     9.4.1 性能优化的原则 / 123  9.4.2 影响性能的因素 / 124  9.5 常用的优化方案 / 124  9.5.1 创建索引 / 124  9.5.2 限定返回结果条数 / 125  9.5.3 只查询用到的字段 / 125  9.5.4 采用...
  • (5)优先数法:急事先办的原则 第三章进程及处理机管理 1、为什么要引入“进程” (1)进程调度属于低级处理机管理,即确定系统中哪个进程将获得CPU;而作业调度属于高级处理机管理,即确定系统中哪些...

空空如也

空空如也

1 2
收藏数 30
精华内容 12
关键字:

发指令的原则