精华内容
下载资源
问答
  • 计算机指令两部分组成,它们是操作码和操作数;其中操作码决定要完成的操作,而操作数指参加运算的数据及其所在的单元地址。计算机指令两部分组成,它们是操作码和操作数。计算机指令格式通常包含...通常一条指...

    计算机指令由两部分组成,它们是操作码和操作数;其中操作码决定要完成的操作,而操作数指参加运算的数据及其所在的单元地址。

    2422e651c2ad087283e6199e82f56feb.png

    计算机指令由两部分组成,它们是操作码和操作数。

    计算机指令格式通常包含操作码和操作数两部分。

    计算机指令就是指挥机器工作的指示和命令,程序就是一系列按一定顺序排列的指令,执行程序的过程就是计算机的工作过程。

    控制器靠指令指挥机器工作,人们用指令表达自己的意图,并交给控制器执行。

    通常一条指令包括两方面的内容: 操作码和操作数,操作码决定要完成的操作,操作数指参加运算的数据及其所在的单元地址。

    在计算机中,操作要求和操作数地址都由二进制数码表示,分别称作操作码和地址码,整条指令以二进制编码的形式存放在存储器中。

    指令的种类和多少与具体的机型有关,在此不详述,请参见具体的机器资料手册。

    原理

    控制器靠指令指挥机器工作,人们用指令表达自己的意图,并交给控制器执行。一台计算机所能执行的各种不同指令的全体,叫做计算机的指令系统,每一台计算机均有自己的特定的指令系统,其指令内容和格式有所不同。

    展开全文
  • 一条指令执行的步骤

    千次阅读 2020-02-23 18:45:57
    指令分为两部分:“操作码”“地址码”,现在假设你写好一个程序,在程序开始执行前,必须将它的起始地址,即程序的一条指令所在的内存单元地址送入PC,因此程序计数器(PC)的内容即是从内存提取的第一条指令的...

    这里是关于C++程序设计的实验课程第三个实验:一条指令执行的步骤分析图解

    为了强化大家的理论知识巩固,在讲解词语时候也会有问题提出来巩固。

    在这里插入图片描述

    指令分为两个部分:“操作码”“地址码”,现在假设你写好一个程序,在程序开始执行前,必须将它的起始地址,即程序的一条指令所在的内存单元地址送入PC,因此程序计数器(PC)的内容即是从内存提取的第一条指令的地址。
    程序计数器(PC):为了保证程序(在操作系统中理解为进程)能够连续地执行下去,CPU必须具有某些手段来确定下一条指令的地址。而程序计数器正是起到这种作用,所以通常又称为指令计数器。
    在程序开始执行前,必须将它的起始地址,即程序的一条指令所在的内存单元地址送入PC,因此程序计数器(PC)的内容即是从内存提取的第一条指令的地址。在这里插入图片描述

    总结:先是PC存储第一条指令,即从内存提取的第一条指令。

    • 在程序中存放指令地址的寄存器叫( )

    A通用寄存器
    B 程序计数器
    C变址寄存器
    D指令寄存器
    答案为:D
    解析:程序计数器存放的是下一条指令所在单元的地址,指令寄存器存放正在执行的指令。(除了执行第一次指令以外,都是存放下一条指令所在单元的地址)

    程序一开始会根据PC存储的指令的内存地址(简称:指令地址),根据地址取出后放在指令寄存器中,图中指令寄存器是0001 110100 110110=加法指令的代码,因为实验在浏览器表现不太好,把两部分数字倒过来了,实际上应该是在这里插入图片描述在这里插入图片描述这样子的
    然后上下图比较后我们可以看到PC多加了一个1,这是为什么?
    答案是因为当执行指令时,CPU将自动修改PC的内容,即每执行一条指令PC增加一个量,这个量等于指令所含的字节数,以便使其保持的总是将要执行的下一条指令的地址。由于大多数指令都是按顺序来执行的,所以修改的过程通常只是简单的对PC加1。 当程序转移时,转移指令执行的最终结果就是要改变PC的值,此PC值就是转去的地址,以此实现转移。有些机器中也称PC为指令指针IP(Instruction Pointer)
    但在这里我要强调一句,这里PC加1不是单纯加1,它是根据先前的指针地址继续加上指令的字节数,因为这里的地址是按字节排序,即0x000A+1=0x000B…从而继续执行下一条指令。如果说一个指令占据两个字节,你可以这样想:执行完一条指令后,下一条指令的地址是多少???是刚刚被执行完的指令地址加上2,也正是因为这个可以继续执行指令。

    AR=0X000A=10=1010 因为0X是十六进制的标记,后面数字全当做十六进制处理,然后转化成二进制

    在这里插入图片描述这里是要把操作码放在CU以此来判断要干啥?(加减乘除之类的)判断后它就能知道后面的执行操作决定用什么工具。

    • 控制器(CU)的功能是______。

    A) 指挥计算机各部件自动、协调一致地工作
    B) 对数据进行算术运算或逻辑运算
    C) 控制对指令的读取和译码
    D) 控制数据的输入和输出
    答案:A
    解析:控制器的主要功能是指挥全机各个部件自动、协调的工作。
    在这里插入图片描述这一段是因为IR里面的值0001 110100 110110的其中110100是0X0034的值,也是我们输入的值的地址(这里我要说的是0X0034这个地址对应的是我输入的值的内存单元),同理,110110也是0X0036的值,对应着我输入的第二个值,然后存在数据缓冲寄存器(DR)中
    在这里插入图片描述
    在这里插入图片描述
    经过累加器ALU的加法计算,得出结果并存储在第一个值存储的位置——数据缓冲寄存器(DR)

    • 在CPU中 数据寄存器DR是指?

    A.可存放指令的寄存器
    B.可存放程序状态字的寄存器
    C.本身具有技术逻辑于移位逻辑的寄存器
    D.可编程指定多种功能的寄存器
    答案:C
    解析:A是指令寄存器IR B是程序状态寄存器PSW D是通用寄存器
    在这里插入图片描述以上,就是我对一条指令执行的步骤总过程实验的书面描写心得。

    如果这段描述有帮到你,请记得点赞并关注哟!
    在这里插入图片描述

    展开全文
  • 跟随一条指令来看LLVM的基本结构

    千次阅读 2020-01-04 18:41:44
    LLVM是个很复杂的软件,了解LLVM的工作原理不是很容易,然而,对于刚开始接触LLVM整个框架的工作原理来说,详细而深入,不如广泛而浅显,所以有了这篇文章。

    LLVM是一个很复杂的软件,了解LLVM的工作原理不是很容易,然而,对于刚开始接触LLVM整个框架的工作原理来说,详细而深入,不如广泛而浅显,所以有了这一篇文章。

    通过跟随一条指令在LLVM中的各个passes中的状态变化,从源程序开始,到目标代码结束,可以让我们对LLVM的整体框架有个大致的认识。

    这篇文章基于Life of an instruction in LLVM,文章大部分内容与参考文章一致,但由于参考文章编辑于2012年11月,当时的LLVM版本是3.2,距现在新的LLVM版本已有一些差异,所以有部分内容我做了调整。

    这篇文章不会详细讲解各个passes中的实现,尽量易于理解,尽量紧贴指令的变化过程。

    有关于LLVM中的一些基本概念,可以参考:https://blog.csdn.net/SiberiaBear/article/details/103111028

    输入代码

    使用的输入代码与参考文章一致,选择一段C语言来开始:

    int foo(int aa, int bb, int cc) {
    	int sum = aa + bb;
    	return sum / cc;
    }
    

    我们focus的指令是除法指令,不关注其他代码。

    Clang

    Clang是LLVM框架的前端,用来将C、C++、ObjectC的源代码转换为LLVM IR结构,它最复杂的实现是处理C++中的一些特殊语法,对于我们这个简单的C代码来说,处理很简单,按照词法+语法+语义的方式走就可以。

    Clang的parser会构建一个AST,并作为它的中间表示,对于我们的除法操作,在AST中会生成一个BinaryOperator节点,承载一个B0_div的操作码类型。通过clang自带的ast dump插件,命令为clang -Xclang -ast-dump -fsyntax-only test.c,可以查看AST的情况(参考文章中需更新)。Clang的代码生成器会将AST转换为一个LLVM IR,这时我们的指令会生成为一个sdiv的LLVM IR指令,这是一个有符号的除法指令。

    LLVM IR

    经过Clang处理后,输出的是LLVM的IR表示:

    define i32 @foo(i32 %aa, i32 %bb, i32 %cc) nounwind {
    entry:
      %add = add nsw i32 %aa, %bb
      %div = sdiv i32 %add, %cc
      ret i32 %div
    }
    

    在LLVM IR中,sdiv是一个BinaryOperator对象,这是一个带有SDiv操作数的Instruction类的子类。就像其他指令一样,LLVM IR可以被LLVM的解析和转换pass来处理,最终会通过LLVM代码生成,进入下一个环节。

    **代码生成器(Code Generator)**是LLVM中一个很复杂的部分。它的工作是将高层级的、目标无关的LLVM IR下降为低层级的、目标相关的机器指令,代码生成通常也就认为是LLVM的后端(注意不是LLVM/Clang的后端,LLVM的前端被认为是LLVM IR的parser)。

    代码生成器有多个阶段组成,包括指令选择、指令调度、寄存器分配、寄存器分配后指令调度以及各阶段可能有的优化和调整过程。其中很重要的一个步骤是类型合法化和指令合法化,使用了目标特殊的处理方法来将所有的指令和类型都转换为目标能够支持的模式。从输入LLVM IR之后,代码生成器首先调用SelectionDAGBuilder将LLVM IR转换为SelectDAG,直到指令调度之前都是SelectionDAG格式表示作为中间表示,之后到代码发射之前,都是MI格式,代码发射步骤中,会把MI翻译为MCInst,进而翻译为目标代码文件或汇编代码文件。

    在下降的过程中,LLVM IR的指令会先变成selection DAG,下一节将会讲解这一部分知识。

    SelectionDAG

    SelectionDAG是由SelectionDAGBuilder类生成的,它服务于SelectionDAGISel类,而这个类是用于指令选择的重要类。SelectionDAGISel遍历所有的LLVM IR指令,调用SelectionDAGBuilder::visit方法来解析它们。处理我们SDiv指令的方法是SelectionDAGBuilder::visitSDiv方法,它请求一个操作数为ISD::SDIV的SDNode(SelectionDAG node),并添加到DAG中。

    DAG叫做有向无环图,是编译器原理中很重要的数据结构,可以协助完成很多指令选择中重要的工作,这里它是我们代码的一种中间表示,指令选择的算法依赖于DAG形式作为中间表示。

    初始化DAG的过程是部分目标相关的,在LLVM的术语中,叫做非法的(illegal),原因是这时的DAG中还包含一部分目标无关的节点,这些节点对于目标来说不支持。

    LLVM中支持一些显示DAG的方法。一种方法是在llc中指定-debug参数,这可以在编译时输出各阶段的DAG的文本信息(需要注意LLVM编译时指定为debug模式)。另一种方法是指定-view选项,这一类选项有很多,分别对应不同阶段(这里的不同阶段是指从LLVM IR输入代码生成器,到指令调度之前的阶段,这个阶段的中间表示是DAG,同时这个阶段也分为好多步骤,后边小节会讲到,所以会有不同阶段的DAG如是说)的DAG形式,它可以自动启动系统的image浏览软件,展示图形化的DAG结构。比如:

    llc的参数对应显示DAG的位置
    -view-dag-combine1-dagsDAG合并1之前的DAG
    -view-dag-legalize-types-dags类型合法化1之前的DAG
    -view-dag-combine-lt-dags类型合法化2之后、DAG合并之前的DAG
    -view-legalize-dagsDAG合法化之前的DAG
    -view-dag-combine2-dagsDAG合并2之前的DAG
    -view-isel-dags指令选择之前的DAG
    -view-sched-dags指令选择之后、指令调度之前的DAG

    x86平台上合法化sdiv到sdivrem

    在x86平台上,除法指令会同时计算商和余数,并将结果分别存在两个独立的寄存器中。因为在LLVM的指令选择中,计算商的节点操作为ISD::SDIV,而计算余数的节点操作是另一个ISD::SDIVREM,所以我们需要使用合法化(legalized)来针对x86做特殊操作。

    代码生成的一个很重要的任务就是将目标无关的信息转换为目标相关的信息,这些算法通过TargetLowering类来实现,而这个过程就叫做合法化,DAG会从非法DAG变为合法DAG,最终的合法DAG中的全部节点都是目标能够支持的(这部分代码很难理解)。x86平台上这个实现类是X86TargetLowering,它的构造函数中指定了哪些操作数需要合法化展开,ISD::SDIV就是其中之一。代码中有这么一段注释:

    // 标量整数除法和求余操作被下降为能够生成两个结果的操作,用以匹配可用的指令。这将两个结果的模式交由普通CSE处理,CSE能够将x/y和x%y组合成一条指令。
    

    SDIV节点中会包含有Expand标记,当SelectionDAGLegalize::LegalizeOp检查到这个标记时,它将会用ISD::SDIVREM来替换ISD::SDIV节点。在合法化过程中,这是一个比较特殊且很有意思的例子,合法化在SelectionDAG结构阶段多次出现,是为了最优处理程序。

    指令选择

    下一个步骤是指令选择。LLVM提供了一套基于查询表的指令选择机制,这套查询表通过TableGen来生成。很多目标平台后端也会选择编写自定义的代码来手动处理一些指令,通常在SelectionDAGISel::Select中实现。其他能够自动生成的指令都是通过TableGen来完成,并且通过SelectCode来完成调用。

    可以参考:https://blog.csdn.net/SiberiaBear/article/details/103319595简单了解TableGen的概念。

    x86平台后端是手动处理ISD::SDIVREM节点的,主要是考虑到一些特殊的情况和优化。DAG节点在这个阶段MachineSDNode,这个类是SDNode的子类,定义了一些与真实机器平台相关的成员,但依然是DAG节点。经过指令选择阶段,我们的除法指令被选择为X86::IDIV32r

    指令调度及发射MI

    到目前为止,我们的代码依然是DAG格式,但是CPU不处理DAG,所以我们需要把DAG转换为线性的指令序列。指令调度的目的是序列化DAG,并且调整指令之间的先后顺序,它使用一些启发式的编排算法,比如register pressure reduction来尝试输出最佳的指令序列。

    指令调度阶段会尽可能的提高指令并行度,使用尽可能多的虚拟寄存器(虚拟寄存器后边会讲到),其目的是使代码运行效率更高(插播一句,后边的寄存器分配倾向于使指令串行化,这样可以尽量少的使用寄存器,所以,指令调度和寄存器分配是两个相互对立的阶段,编译器在双手博弈中,实现编译目标的最优化)。

    目标特殊的一些调度实现算法会加在该过程中,从而影响调度的结果。

    最终,指令调度会通过InstrEmitter发射出指令序列,这些序列放到一个MachineBasicBlock中,这种代码表现形式叫做MachineInstr,简称为MI,之后,DAG格式的代码信息被销毁。

    通过给llc指定-print-machineinstrs参数可以指定打印出MI的信息:

    # After Instruction Selection:
    # Machine code for function foo: SSA
    Function Live Ins: %EDI in %vreg0, %ESI in %vreg1, %EDX in %vreg2
    Function Live Outs: %EAX
    
    BB#0: derived from LLVM BB %entry
        Live Ins: %EDI %ESI %EDX
            %vreg2<def> = COPY %EDX; GR32:%vreg2
            %vreg1<def> = COPY %ESI; GR32:%vreg1
            %vreg0<def> = COPY %EDI; GR32:%vreg0
            %vreg3<def,tied1> = ADD32rr %vreg0<tied0>, %vreg1, %EFLAGS<imp-def,dead>; GR32:%vreg3,%vreg0,%vreg1
            %EAX<def> = COPY %vreg3; GR32:%vreg3
            CDQ %EAX<imp-def>, %EDX<imp-def>, %EAX<imp-use>
            IDIV32r %vreg2, %EAX<imp-def>, %EDX<imp-def,dead>, %EFLAGS<imp-def,dead>, %EAX<imp-use>, %EDX<imp-use>; GR32:%vreg2
            %vreg4<def> = COPY %EAX; GR32:%vreg4
            %EAX<def> = COPY %vreg4; GR32:%vreg4
            RET
    
    # End machine code for function foo.
    

    MI格式表示是一种类似于汇编代码的形式,它采用三地址形式来表现指令信息,并序列化存储信息,每一条MI指令包括有指令操作码、以及一系列操作数。

    寄存器分配

    除了一些例外的情况,指令选择步骤之后输出的大多数DAG节点是SSA格式的,指令调度之后输出的是SSA格式的MI序列,SSA格式全称是static single assignment form,叫做静态单赋值形式,是一种很常见的编译器中间形式,在SSA中,UD链(use-define chain)是非常明确的,变量不会重复定义和赋值。比如:

    x1 = y1 + 1;
    x2 = y2 + 1;
    x1 = x2;
    

    上边这个不是SSA形式,因为x1被重复赋值,而下边这个是SSA格式:

    x2 = y2 + 1;
    x1 = x2;
    

    指令选择时,使用了无限的虚拟寄存器集,但是目标平台不可能识别这些虚拟寄存器,所以寄存器调度的一个工作就是将虚拟寄存器全部替换为物理寄存器(它的另一个工作是一些优化过程)。

    在一些目标架构下,一些指令需要使用指定的固定寄存器。一个例子就是我们X86平台下的除法指令,这条除法指令要求它的输入必须是EDX和EAX寄存器。指令选择时就已经知道这个信息,并且输出时就是物理寄存器,这个过程由X86DAGToDAGISel::Select完成。

    寄存器分配处理所有的虚拟寄存器,并且会做一些优化,比如说伪指令展开,本文不详细展开讲解。同样,这里也不讨论寄存器分配之后的一些步骤,这些步骤不会再改动代码的表现形式(一直是MI),后续的步骤有寄存器分配后的指令调度、一些合法化的工作,目的是进一步的降级代码,使之更接近目标指令,同时这中间还会涉及一些优化passes,通过在工程代码中查找TargetPassConfig::addMachinePasses,能了解这些passes。

    代码发射

    到现在为止,我们已经将C源程序翻译为MI格式代码。我们知道,目标代码分为汇编代码和二进制可执行代码。而现在的LLVM还提供了一种(传统的)JIT的方式,这种JIT的目标输出代码可以直接在内存中执行,我理解为类似Java字节码的东西,而且,最初的LLVM(Low Level Virtual Machine)的目的也是做一个类似Java虚拟机的东西来研究优化问题,所以,这个输出方式就保留下来了。另一种输出方式是MC架构,这是一种非常赞的目标文件和汇编文件输出框架,曾经的LLVM的汇编器功能很单一,后来,为了兼顾目标码的输出,就重新设计了这一套MC框架,替代了之前的汇编器。现在大多数的用法都是从MC框架输出目标码,较少会走传统JIT那条,可能是因为LLVM不把Java作为自己的竞争对手吧。

    JIT

    JIT代码的输出是通过LLVMTargetMachine::addPassesToEmitMachineCode来完成,它调用addPassesToGenerateCode,这个函数中定义了所有这篇文章提到的从IR到MI的各个passes。然后,它调用addCodeEmitter,这是一个目标特殊的pass,用来将MI转换为真实机器指令(当前CPU可执行的),实际上JIT执行的机器指令和MI已经很相似了,所以这部分的翻译工作很直接。对于X86平台,这些代码写在lib/Target/X86/X86CodeEmitter.cpp中,对于我们的除法指令,这里没有什么特殊要讲的,因为MI指令中已经包含了最终目标相关的操作码和操作数。最终,所有指令通过emitInstruction发射。

    MCInst

    另一种输出是MC框架,它的中间表示被称为MCInst。当LLVM被看作静态编译器时(比如clang的一部分),MI序列还会下降到MC层,用来输出静态编译器会输出的目标码或汇编码。MC框架的介绍可以参考官方的文章:Introduce to LLVM MC project,不过这篇文章也比较旧了,也仅供参考学习。

    LLVMTargetMachine::addPassesToEmitFile方法负责定义发射目标代码的passes,真正将MI翻译为MCInst的pass是AsmPrinter::EmitInstruction接口,虽然这个类看着像是汇编码输出的类,然而不是,我一直不是很喜欢MC框架的一些类的命名。对于X86平台,会有个子类继承这个AsmPrinter类,叫做X86AsmPrinter,从而发射MCInst的方法为X86AsmPrinter::EmitInstruction,这个过程需要X86MCInstLower类的协助。和前边DAG时期的Lower不一样,当时协助的下降类是X86TargetLowering,用来提前下降一些必要DAG的,而这里是MC框架下的InstLower,其实一些比较关键的算法都在这几个类里边。

    对于我们的除法指令,这里也没有什么特殊要处理的地方。

    通过给llc指定-show-mc-inst参数,可以打印出MC指令信息和汇编代码:

    foo:                                    # @foo
    # BB#0:                                 # %entry
            movl    %edx, %ecx              # <MCInst #1483 MOV32rr
                                            #  <MCOperand Reg:46>
                                            #  <MCOperand Reg:48>>
            leal    (%rdi,%rsi), %eax       # <MCInst #1096 LEA64_32r
                                            #  <MCOperand Reg:43>
                                            #  <MCOperand Reg:110>
                                            #  <MCOperand Imm:1>
                                            #  <MCOperand Reg:114>
                                            #  <MCOperand Imm:0>
                                            #  <MCOperand Reg:0>>
            cltd                            # <MCInst #352 CDQ>
            idivl   %ecx                    # <MCInst #841 IDIV32r
                                            #  <MCOperand Reg:46>>
            ret                             # <MCInst #2227 RET>
    .Ltmp0:
            .size   foo, .Ltmp0-foo
    

    通过指定-show-mc-encoding参数,可以打印汇编代码和二进制编码(目标码)的信息。

    目标代码或者汇编代码发射是在MCStreamer类中实现的,这个类被两个子类继承,分别是发射目标代码的MCObjectStreamer和发射汇编代码的MCAsmStreamer。对于发射目标代码,因为我们针对不同操作系统平台有不同的目标文件格式,比如Windows的COFF、Linux的ELF等,所以MCObjectStreamer被进一步继承为MCCOFFStreamerMCELFStreamer等子类。在这些子类中都重写了父类的MCObjectStreamer::EmitInstruction,这个方法实现发射目标代码的工作。输出目标文件的过程还需要MCCodeEmitter的支持,最终输出目标可执行文件。

    到这一步,我们的除法指令就被输出成汇编码或者二进制编码格式了,相对应的可执行文件也可以在X86平台下跑起来(需要对应到操作系统),这条指令在LLVM的行程也就结束了。

    汇编器与反汇编器

    MCInst是一种简单的代码表现形式,它尽量屏蔽了语义上的信息,仅保留指令编码和指令操作数,以及一些指令位置信息。和LLVM IR一样,它是多种可能编码形式的中间表示,可以理解为LLVM后端的一种IR,比如说汇编代码和二进制目标代码都可以由它来表示。

    llvm-mc工具是MC框架下边的一个工具,clang工具在一次性驱动编译器输出汇编码和二进制目标码时不会调用llvm-mc,因为我们知道LLVM的设计思想是一切都是库,clang驱动工具和llvm-mc调用的是一样的MC框架下的库,而llvm-mc可以便于我们直接调用MC框架下的库来实现功能,比如说汇编器和反汇编器(另外还有一些目标文件分析工具,比如llvm-objdumpllvm-readobj,也是调用了MC框架下边的库),所以,llvm-mc被可以看作是通常意义下的汇编器和反汇编器,对标gcc下的as和dis,可以输入汇编码吐出二进制可执行文件,或者输入二进制可执行文件吐出汇编码。

    展开全文
  • 当这些操作结束之后,它接着再取下一条指令通常情况下,这个过程是连续不断、循环往复的。 1、寄存器和算数逻辑部件 电子计算机能能做很多事情。计算天气预报,看电影,听音乐,上网等,实际上都是以数学计算为...

    学习交流加

    • 个人qq:
      1126137994
    • 个人微信:
      liu1126137994
    • 学习交流资源分享qq群:
      962535112

    我们已经知道,处理器是一台电子计算机的核心,它会在振荡器脉冲的激励下,从内存中获取指令,并发起一系列由该指令所定义的操作。当这些操作结束之后,它接着再取下一条指令。通常情况下,这个过程是连续不断、循环往复的。

    1、寄存器和算数逻辑部件

    电子计算机能能做很多事情。计算天气预报,看电影,听音乐,上网等,实际上都是以数学计算为基础。它之所以能够计算,是因为它特殊的设计。想要了解计算机为什么能够自动计算,请去阅读书籍《穿越计算机的迷雾》。本片文章主要讲解处理器的简单功能。

    如图是一个处理器:

    在处理器的周围,有大量的引脚,这些引脚可以接受从外面来的电信号或者向外发送电信号(与外设进行信号的交互)。有很多引脚是复用的,假如现在我们要进行加法运算,我们就需要重复使用某一些引脚来依次将加数与被加数送入。一旦被加数被送入处理器,代表这个二进制数字的一组电信号就会出现在与该引脚相连的内部线路上(我们称这个内部线路为内部总线)。这是一组高低电平的组合,代表着二进制数中的每一位。当被加数被送进来了,接下来就要将加数送进来,但是此时内部总线上有被加数,所以处理器内部需要有一个电路来将被加数“锁存”。这个将被加数锁存的电路就是寄存器。它是一个存储器件。

    同样,加数也要锁存进另一个寄存器。如图中RA与RB分别锁存的是被加数与加数。此后,被加数与加数将不再受处理器的内部总线的影响。寄存器是双向器件,它会将它锁存的数,在另一端产生一模一样的电信号(注意此时寄存器中依然锁存着之前的电信号,它只是在另一端产生一个一模一样的电信号),传送给相应的算数逻辑部件(ALU)进行计算。算数逻辑部件(ALU)计算出结果后,将结果送出,给另一个寄存器RC锁存,稍后再将RC中的内容通过内部总线送到处理器外部,或者再次送回RA RB寄存器进行其他计算。

    那么,处理器是如何知道什么时候该干什么,各个部件在各个时间点该干什么,以及哪些部件在哪些时候使用内部总线以避免总线冲突呢?这些实际上都是由处理器内部的一个控制器控制的(图中没有画出)。

    处理器总是繁忙的,所有数据都只能在寄存器中寄存一会,就必须被送往别处。早期的寄存器只能存储4比特、8比特或者16比特,分别叫做4位寄存器,8位寄存器和16位寄存器。现在的处理器大多是32位和64位的。

    2、内存储器

    上一节我们知道,处理器的计算过程实际上是借助于寄存器和算数逻辑部件进行的。那么计算的数据是从哪里来的呢?它是从一个可以保存很多数字的电路,叫做存储器。存储器的种类很多,我们常见的U盘,硬盘,磁盘,内存条,甚至寄存器等都是存储器。

    我们这里主要说内存条。由于它通常是和处理器直接相连的,所以叫内存储器或者主存储器。又或者内存或者主存。和寄存器不同,内存用于保存更多的比特。对应用的最多的个人计算机来说,内存是按字节来组织的,单次访问的最小单位是1字节,这是最基本的存储单元。如下图所示是一个内存和内存访问示意图:

    内存中的每一个字节都对应着一个地址。从0地址开始,由于上面的内存是65536字节,所以该内存的最后一个字节是FFFF(都是用的十六进制表示的)。为了访问一个内存,处理器需要给出一个地址。同时访问内存是什么方式?读方式或者写方式,所以处理器需要有一个读写控制。如果是写数据,处理器还需要有一个数据传输的控制。最后,处理器单次访问内存时,是以字节为单位访问还是以字为单位访问等,需要有一个字长控制来告知。

    3、指令和指令集

    从一开始设计处理器,就是想让它自动工作,另外,还需要提供一种机制,来允许程序员决定进行何种工作。处理器的设计者用某些数来表示该处理器该做什么,这些数,我们称为指令或者叫机器指令。

    下面给个实际例子来分析指令在内存中的布局:

    假设现在我们的处理器是从内存的0000地址开始取指令(实际上肯定不是从0地址开始,这里只是假设)。第一条指令B8 5D 00,该指令的意思是将立即数005D传送到RA寄存器,它具有一个字节的操作码B8。由此我们可以看出简单的一个B8 5D 00指令,是一个传送指令,且该指令将两个字节(005D)传送到RA寄存器,并且由于下一条指令的地址是在0003字节处,所以该指令肯定还知道它自己是一条3字节指令,以便下一次处理器可以直接去到0003字节处去取指令。

    对于一些复杂的指令,一个字节的操作码肯定不够用,所以第二条指令8B 1E 3F 00的指令操作码是两字节8B 1E。

    同时我们注意到上述前两条指令中,第一条指令是将一个数005D直接传送到寄存器,第二条指令,是需要从另外一个地址(003F)先取出数据,然后再将数据传送到寄存器。第一种的操作数005D我们称为立即数(表示可以立即将数传送而不需要再去另外一个地址取数据)。

    由以上分析,我们很容易知道,上述的几条指令,首先将两个数放到寄存器中去运算,然后运算结果放到其中一个寄存器中,最终还需要将运算结果再传回到内存中以便该结果作为其他用处时处理器好可以从内存中得到计算结果。最后F4指令停机。

    指令和非指令的二进制数据是一模一样的,在组成内存的电路中,都是一些高低电平的组合。因为处理器是按顺序取指令,并加以执行的,这样的话,如果指令和数据存放在一起,处理器很容易将数据的二进制当成指令,从而导致错误。所以我们需要将指令与数据分开存放。分别存放在不同的区域。存放指令的区域叫做代码区,存放数据的区域叫做数据区(这就是为什么进程的虚拟地址空间中代码和数据区分开的原因)。

    一个处理器能够识别的指令的集合,称为指令集。

    4、Intel 8086处理器

    要想学好编程,就必须要懂底层原理。但是想要彻底学好底层,就要把汇编学好。想要学好汇编,不得不先学好针对8086的汇编技术。

    4.1、 8086的通用寄存器

    8086处理器内部有8个16位寄存器。分别被命名为:AX,BX,CX,DX,SI,DI,BP,SP。通用的意思是他们中大部分可以根据需要用于多种目的。他们的用处,将在后面的文章逐渐给出。

    下图是几个寄存器:
    在这里插入图片描述

    可以看到,有介个寄存器,AX,BX,CX,DX,又可以分为两个8位的寄存器来使用。在后面文章中,我们会看到具体的应用。

    4.2、 程序的重定位问题

    我们知道,处理器是自动取指令和执行指令的,只要每条指令都正确无误,它就能准确的知道下一条指令的地址。所以,指令必须集中在一起,形成一个段,叫做代码段。
    为了做某件事而编写的指令,形成我们所知道的程序。程序中肯定需要操作大量的数据,这大量的数据也应该集中在一起,位于内存中的某个地方,叫做数据段。

    段在内存中的位置并不重要,因为处理器是可控的,我们可以让处理器在内存中的任何一个位置开始取指令并加以执行。

    下面看一个图示:

    假设上面是一个程序片段在内存中的位置,相关指令的用处已经在图中表明。这里不再赘述。但是现在有一个问题,就是我们写的程序(不管是C语言还是C++语言或者Java语言),最终要运行在内存中的位置,是无法确定的。因为你想一想,本身你的电脑中有各种程序在跑了,有qq程序在跑,微信程序在跑,或者还有微博等在跑,他们可能把你的内存都占完了,当你想运行你写的程序的时候,你的程序本想从0100H处开始存放代码段,但是可能这个时候0100H处有其他程序的代码或者指令在运行,此时你的程序只能去另一个位置存放你的代码和数据,假设你的代码下一次运行的时候代码段和数据段在如下位置:

    此时你的程序的代数据段在内存的位置为1000H处,但是你会发现,你的代码段的第一条指令,还是将地址单元0100H处的内容传送到AX寄存器中。但是!!!此时0100H处的内容,并不是我们想要的内容。

    产生这种情况的原因就是我们在程序中使用的是绝对内存地址。这样的程序是无法重新定位的。所以我们需要使用另一种方式访问内存:相对地址或者叫做逻辑地址。在任何时候,程序的重定位都是非常棘手的事情。在8086处理器中,这个问题得到解决。它使用了分段机制。

    4.3、 内存分段机制

    在内存分段中,段,是很多个连续的字节组成的。如图:

    上图有一个7个字节的段。段的起始地址为A532。那么段中的地址从第一个字节开始,他们的地址此时就是段内的偏移地址(0000,0001,0002…)。而他们的实际的物理地址,又恰好等于段地址加上段内的偏移地址。可以用“段地址:偏移地址”,来表示段中的每一个字节的地址。

    为了在硬件一级提供对“段地址:偏移地址”的支持。处理器至少要提供两个段寄存器,分别是代码段寄存器CS,数据段寄存器DS。

    对代码段CS内容的改变,将导致处理器从新的代码段开始执行。当然,在访问数据之前,也是必须要提前设置好数据段寄存器DS的,使DS之指向数据段。

    很重要的一点是,当处理器取指令执行指令的时候,它是把指令中指定的内存地址看成是段内偏移地址的。而不是内存的物理地址。那么当处理器遇到一条访问内存的指令时,它就会将DS中的数据段起始地址加上指令中提供的段内偏移地址得到访问内存所需要的物理地址的。

    如下图所示:

    代码段地址为1020H,数据段地址为1000H,在代码段中有一条指令:A1 02 00,它的功能是将地址0002H处的的一个字传送到寄存器AX。在这里处理器将0002H看成是段内偏移地址,段地址在DS中,应该在执行这条指令之前就已经用别的地址将数据段地址传送到DS寄存器中了。当处理器执行到指令A1 02 00时,处理器会将DS中的内容和指令中指定的便宜地址相加,得到内存中的一个物理地址,这个物理地址就是处理器要去访问的内存地址,就可以从该地址获取一个字:00A0H。

    如果下一次执行该程序,代码段和数据段发生变化,只需要将程序的代码段地址和数据段地址分别传送到CS和DS就可以正常的执行程序了。

    4.4 、8086的内存分段机制

    前面讲了如何从逻辑地址转换到物理地址,以使得程序的运行和它在内存中的位置是无关的。上述策略在很多处理器上应用得到了支持。但是在8086处理器上,由于8086处理器是16位处理器,如果按照正常计算,给它提供16根地址线的话,那么8086处理器就只能用于寻址最多64KB的内存空间(65536字节=2的15次方+1)。在当时的年代64KB的内存还是不够的。所以8086处理器就将16根地址线扩展到20根地址线。从而使得可寻址的空间变为1M(16*64KB)。

    但是有一个问题就是16位的段地址加上16位的段内偏移地址,还是16位的,并不是20位的。所以有一个解决办法就是在计算内存的物理地址时,先将段地址先左移4位,然后加上段内偏移地址,这样就可以得到20位的物理地址,就可以将整个1M的地址空间表示完全。

    8086在进行分段时,并不是每一个地址都可以作为段地址,地址必须是16的倍数,才能作为段地址。

    如下图,是其中的一种情况,我们从0地址开始分段,每段16字节(从任何地址只要这个地址是16的倍数,都可以作为段地址,每一个段内最低有16字节(可以分为65536个段)的内存,最高有65536(可以分为16个段)个字节的内存)

    4.5 、8086 处理器内部组成框图

    最后我们再来看一下8086处理器内部组成框图:

    在这里插入图片描述

    上图中,我们知道8086内部有8个16位通用寄存器,分别为AX,BX,CX,DX,SI,DI,BP,SP。ALU是算数逻辑部件,用于算数逻辑运算或者数据传送。标志寄存器是用于控制各种依赖于标志位的标志来进行相应的指令执行的,这个可以等到以后的文章中进行详细的说明,目前不需要理解。处理器可以自动执行,依赖于控制器的控制。

    指令队列缓冲器,又名:指令预取队列。什么意思呢?就是当你的CPU在忙于做其他事情(一般是指执行那些不需要去内存中取的指令)而并没有去内存中取指令执行时,此时指令队列缓冲器(指令预取队列)就会去内存中取出6个字节的指令放到指令队列缓冲器,那么当CPU该执行内存中的指令时,它就会就近向指令队列缓冲器中去取指令,这样的话就会比去内存中取指令要快很多,毕竟内存还是通过外部总线与内存条相连接的。

    CS是代码段寄存器。DS是数据段寄存器。SS是栈段寄存器(很重要,以后会讲)。ES是额外的段寄存器,它的用处相当于补充段寄存器,当你的DS在使用时,但是你还要访问另一个数据段,那么此时就可以使用ES寄存器(后面的文章中我们就用ES寄存器指向显卡的内存区域,用来寻址显存,从而控制显卡)。IP寄存器,是存代码段的段内偏移地址的。那么指令的地址此时就可以用“CS:IP”来表示。

    上面的与总线控制逻辑相连的16位总线,实际上是16位的数据总线与20位的地址总线的低16位复用的。20位代表的是20位的地址总线是(当然,低16位是与数据总线复用的)。

    5、 总结

    想要写出优秀的应用程序,就必须对系统编程有深入的理解。想要对系统编程有一定的理解,就必须理解操作系统原理。我选择从X86汇编开始学习,逐步深入理解操作系统与计算机系统之间的关系。

    本片文章可以让我们学会

    • 寄存器与算数逻辑部件的简单关系
    • 内存储器
    • 指令和指令集
    • 程序的重定位问题
    • 处理器与内存之间的关系
    • 内存分段机制

    学习探讨加
    qq:1126137994
    微信:liu1126137994

    展开全文
  • 指令指令(机器指令)有什么区别?

    万次阅读 多人点赞 2019-09-30 17:45:04
    1.控制部件通过控制线向执行...而机器指令则介于微指令与宏指令之间,通常简称为指令,每一条指令可完成一个独立的算术运算或逻辑运算操作. 一台计算机支持(或称使用)的全部指令构成该机的指令系统.指令系统直接与计...
  • 机器指令和微指令

    千次阅读 2020-03-28 13:45:36
    、概念不同 1、机器指令:机器指令是CPU能直接识别并执行的指令。 2、微指令:是指在机器...1、机器指令:机器指令通常由操作码和操作数两部分组成,操作码指出该指令所要完成的操作,即指令的功能,操作数指出参...
  • 【笔记】指令系统(

    千次阅读 2018-04-29 00:50:32
      每一条机器语言的语句称为机器指令,而又将全部机器指令的集合称为机器的指令系统。 1.指令的一般格式   指令是由操作码和地址码两部分组成的。 操作码   操作码用来指明该指令索要完成的操...
  • 抗去除花指令()——花指令基础

    千次阅读 2015-01-04 13:22:53
    指令是对抗反汇编的有效手段之,正常代码添加了花指令之后,可以破坏静态反汇编的过程,使反汇编的结果出现错误。错误的反汇编结果会造成破解者的分析工作大量增加,进而使之不能理解程序的结构和算法,也就很难...
  • 计算机组成原理 指令系统(

    千次阅读 多人点赞 2018-08-14 11:58:15
    台计算机的所有指令的集合构成该机的指令系统,也称为指令集。指令系统是计算机的主要属性,位于硬件和软件的交界面上。 指令字长度:指令包含的二进制数的位数 机器字长:计算机能直接处理的二进制数的位数...
  • 详解 指令寻址方式

    千次阅读 2020-04-24 03:49:05
    一条指令通常部分组成: - 第一部分为操作码(指令码)用于指出指令要进行何种操作; - 另一部分是指令操作的对象,称为操作码; 8086 指令的长度在 1~7个字节之间。操作码占一个字节或个字节。 指令的...
  • 指令系统 —— 指令格式

    千次阅读 2019-07-30 11:08:25
    指令的长度是指一条指令中所包含的二进制代码的位数,因为主存一般是按字节编址的,所以指令的长度一般为字节的整数倍 指令长度与机器字长没有固定的关系,它可以等于机器字长,也可以大于或小于机器字长。根据指令...
  • Java 字节码指令是 JVM 体系中非常难啃的块硬骨头,我估计有些读者会有这样的疑惑,“Java 字节码难学吗?我能不能学会啊?” 讲良心话,不是我谦虚,开始学 Java 字节码和 Java 虚拟机方面的知识我也感觉头大!...
  • CPU内部组成结构及指令执行过程

    万次阅读 多人点赞 2015-05-15 09:53:16
    计算机的基本硬件系统由运算器、控制器、存储器和输入、输出设备五大部件组成。运算器和控制器等部件被集成在一起统称为中央处理单元... 一条指令功能的实现需要若干个操作信号来完成,CPU产生每条指令的操作信号并
  • 指令周期的基本概念

    千次阅读 2012-05-16 10:51:22
    指令周期的基本概念  计算机之所以能自动地工作是... 通常一条指令从取出到执行完毕所需要的时间称为指令周期。对应指令执行的三个阶段,指令周期一般分为:取指周期、取操作数周期和执行周期三个部分。   1
  • ARM指令集和X86指令集的比较

    千次阅读 2012-11-24 00:00:40
    从现阶段的主流体系结构讲,指令集可分为复杂指令集(CISC)和精简指令集(RISC)两部分。 相应的,微处理随着微指令的复杂度也可分为CISC及RISC这两类。 CISC 是种为了便于编程和提高记忆体访问效率的晶片设计...
  • 指令集简介

    万次阅读 2014-01-15 18:22:48
    指令集简介!
  • 计算机如何区分指令和数据(

    万次阅读 多人点赞 2019-09-03 09:44:27
    首先我们要搬出冯诺依曼计算机体系架构,因为它回答了大部分问题。 在冯诺依曼计算机架构体系当中,计算机有以下特定: 1.计算机由计算器、存储器、控制器、输入设备和输出设备五大部件组成 2.指令和数据以同等...
  • 在汇编指令中跳转指令分为两种,种是无条件跳转指令,种是有条件跳转指令。 对于前者无条件跳转指令有点类似于高级语言C中的goto语句,goto标志符,无跳转指令的格式也是类似JMP 标号; 对于有条件跳转指令...
  • 指令系统

    千次阅读 2008-04-11 15:17:00
    指令是指只是计算机执行某些操作的命令,一台计算机的所有指令的集合构成该机的指令集1、指令格式1.1、机器指令的基本格式一条指令就是机器语言的一个语句,它是一组有意义的二进制代码,指令的基本格式如下:操作...
  • 微程序与微指令和微命令

    万次阅读 多人点赞 2018-07-12 08:58:09
    一般的微指令格式由操作控制和顺序控制两部分构成。操作控制部分用来发出管理和指挥全机工作的控制信号。其顺序控制部分用来决定产生下一个微指令的地址。事实上一条机器指令的功能是由许多条微指令组成的序列来实现...
  • RISC-V 指令格式

    千次阅读 2020-03-11 00:37:18
    计算机指令通常两部分操作:操作码和操作数(地址码)。 操作码 操作数(地址码) 操作码 opcode 指令中的操作码:指出该指令需要完成操作的类型或性质; 例如,取数、加法、减法、输出等不同的操作...
  • 80x86指令系统()

    千次阅读 2009-06-18 23:03:00
    、Intel 8086/8088的指令格式 Intel 8086/8088指令长度是可变的...1.指令第1字节的编码通常指令的第1字节是操作码,但是也有特殊情况,比如有的指令寄存器字段(REG)在第1个字节中,有些指令的操作码中有3位辅助操作
  • 指令集: 个给定的计算机体系结构所包含的指令集合。    3. 尽管机器语言种类繁多,但彼此之间十分类似,因此其差异性更像人类语言中的“方言”,而并非独立语言,因此,了解其中种机器语言之后,对其他机器...
  • 指令格式基本定义基本格式零地址指令一地址指令二地址指令三地址指令四地址指令操作码分类定长操作码扩展操作码(不定长操作码)操作类型寻址方式指令寻址数据寻址立即寻址间接寻址寄存器寻址寄存器间接寻址寄存器...
  • 汇编语言 8086/8088指令系统

    千次阅读 2016-12-14 15:16:59
    一条指令一般由两部分组成。第一部分是操作码(指令码),一般用英文缩写表示,用来指出指令要做什么操作,所以是指令中必须给出的内容。第二部分便是操作数,它可以根据不同的情况显式给出或者隐式存在。第二部分中...
  • volatile与lock前缀指令

    万次阅读 多人点赞 2016-08-17 23:48:51
    为什么加上lock指令后就能保证volatile关键字的内存可见性。 lock指令的几个作用: 锁总线,其它CPU对内存的读写请求都会被阻塞,直到锁释放,...不是内存屏障却能完成类似内存屏障的功能,阻止屏障遍的指令重排序
  • 计算机区分指令和数据?

    千次阅读 2014-10-20 16:25:24
    计算机区分指令和数据有以下2种方法:  通过不同的时间段来区分指令和数据,即在取...通常完成一条指令分为取指阶段、分析阶段和执行阶段。在取指阶段通过访问存储器可将指令取出;在执行阶段通过访问存储器可将操
  • 01Linux常用指令

    万次阅读 多人点赞 2021-09-26 15:00:03
    文章目录、Linux简单介绍1.1.Linux的目录结构1.2.常见的具体目录结构/bin/sbin/root/lib/etc/usr/boot/tmp/dev/media/mnt/opt/usr/local/var1.3.路径1.4.空文件的大小二、常用的指令...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 85,831
精华内容 34,332
关键字:

一条指令通常分为两部分