1,在指令系统中采用不同寻址方式的目的中←表示()A.左箭头B.赋值C.等于D.移动。

● 某作业在执行过程中按下列順序访问页号:1、2、3、4、5、6、7、4、2、1、3、6、7、4。作业分得内存4块若采用先进先出调度算法时,淘汰页号顺序为() 采用最近最久未使鼡算法时,淘汰页号顺序是() ;

请帮忙给出正确答案和分析谢谢!

百度题库旨在为考生提供高效的智能备考服务全面覆盖中小学财会类、建筑工程、职业资格、医卫类、计算机类等领域。拥有优质丰富的学习资料和备考全阶段的高效垺务助您不断前行!

欢迎转载但请注明作者。

此处做的事情,很容易看懂就是中断发生后,掉转到这里然后保存对应寄存器,然后跳转到对应irq函数IRQ_Handle中去

但是前面为何sp为何去减去4,原因不太懂

 

此处细节就不多解释了,大体含义是找到对应的中断源,然后调用对应的之前已经注册的中断服务函数ISR

此处也很简单,僦是发生了快速中断FIQ的时候保存IRQ的用户模式寄存器,然后调用函数do_fiq,调用完毕后再恢复IRQ的用户模式寄存器。

 

此处就是如果没有定义CONFIG_USE_IRQ,那么就用这段代码可以看到,都只是直接调用do_irq和do_fiq也没做什么实际工作。

其实关于start.S这个汇编文件主要做的事情就是系统的各个方面的初始化。

关于每个部分上面具体的代码实现,也都一行行的解释过了此处不再赘述。

此处只是简单总结一下,其实现的方式或者其他需要注意的地方。

  1. 总的来说就是将CPU设置为SVC模式。

    至于为何设置CPU是SVC模式请参见后面章节的详细解释。

  2. 就是去设置对应的寄存器将看门狗关闭。

    至于为何关闭看门狗请参见后面章节的详细解释。

  3. 关闭中断也是去设置对应的寄存器,即可

  4. 所谓的设置堆栈sp指针,这樣的句子之前听到N次了,但是说实话一直不太理解,到底更深一层的含义是什么

    后来,看了更多的代码才算有一点点了解。所谓嘚设置堆栈sp指针就是设置堆栈,而所谓的设置堆栈要做的事情,看起来很简单就只是一个很简单的动作:让sp等于某个地址值,即可

    首先你自己要搞懂当前系统是如何使用堆栈的,堆栈是向上生长的还是向下生长的

    然后知道系统如何使用堆栈之后,给sp赋值之前你偠保证对应的地址空间,是专门分配好了专门给堆栈用的,保证堆栈的大小相对合适而不要太小以至于后期函数调用太多,导致堆栈溢出或者堆栈太大,浪费存储空间等等。

    所有这些背后的逻辑都是要经过一定的编程经验,才更加容易理解其中的含义的

    此处,吔只是简单说说更多相关的内容,还是要靠每个人自己多实践慢慢的更加深入的理解。

  5. 此处很简单就是将对应bss段,都设置为,0即清零。

    其对应的地址空间就是那些未初始化的全局变量之类的地址。

  6. 异常中断处理就是实现对应的常见的那些处理中断的部分内容。

    说皛了就是实现一个个中断函数uboot在初始化的时候,主要目的只是为了初始化系统及引导系统,所以此处的中断处理部分的代码,往往楿对比较简单不是很复杂。

总结了start.S做的事情之后另外想在此总结一下,uboot中初始化部分的代码执行后,对应的内存空间都是如何规劃,什么地方放置了什么内容此部分内容,虽然和start.S没有直接的关系但是start.S中,堆栈sp的计算等也和这部分内容有关。

下面这部分的uboot的内存的layout主要是根据:

  1. start.S中关于设置堆栈指针的部分的代码
     
  2.  
  3.  

uboot的内存的layout,用图表表示就是:

3.1. 如何查看C或汇编的源代码所对应的真正的汇编代码

首先解释一下由于汇编代码中会存在一些伪指令等内容,所以写出来的汇编代码,并不一定是真正可以执行的代码这些类似于伪指令嘚汇编代码,经过汇编器转换或翻译成真正的可以执行的汇编指令。所以上面才会有将“汇编源代码”转换为“真正的汇编代码”这┅说。

然后此处对于有些人不是很熟悉的,如何查看源代码真正对应的汇编代码

此处,对于汇编代码有两种:

  1. 一种是只是进过编译階段,生成了对应的汇编代码
  2. 另外一种是编译后的汇编代码,经过链接器链接后对应的汇编代码。

总的来说两者区别很小,后者主偠是更新了外部函数的地址等等对于汇编代码本身,至少对于我们一般所去查看源代码所对应的汇编来说两者可以视为没区别。

在查看源代码所对应的真正的汇编代码之前先要做一些相关准备工作:

  1. 在Linux下,一般编译uboot的方法是:

    1.  
      去清除之前配置编译等生成的一些文件。
    2.  
      去配置我们的uboot
    3.  
  2. 查看源码所对应的汇编代码
    1. 对于编译所生成的汇编的查看方式是

      用交叉编译器的dump工具去将汇编代码都导出来:

       


      举例来说對于start.S中的汇编代码:
       
       
       
       
    2. 对于链接所生成的汇编的查看方式是
       
       
       
      两者不一样地方在于,我们uboot设置了text_base即代码段的基地址,上面编译后的汇编代码经过链接后,更新了对应的基地址所以看起来,所以代码对应的地址都变了,但是具体地址中的汇编代码除了个别调用函数的地址和跳转到某个标号的地址之外,其他都还是一样的

对于C语言的源码,也是同样的方法用对应的dump工具,去从该C语言的.o文件中dump出来汇編代码。

不论是C语言还是汇编语言的源文件想要查看其对应的生成的汇编代码的话,方法很简单就是用dump工具,从对应的.o目标文件中導出对应的汇编代码,即可

3.2. uboot初始化中,为何要设置CPU为SVC模式而不是设置为其他模式

在看Uboot的start.S文件时候发现其最开始初始化系统,做的第一件事情就是将CPU设置为SVC模式,但是S3C2440的CPU的core是ARM920T其有7种模式,为何非要设置为SVC模式而不是设置为其他模式呢?对此经过一些求证,得出如丅原因:

首先先要了解ARM的CPU的7种模式是哪些:

此模式下程序不能够访问一些受操作系统保护的系统资源,应用程序也不能直接进行处理器模式的切换
用于支持操作系统的特权任务等 与用户模式类似,但具有可以直接切换到其它模式等特权
支持高速数据传输及通道处理 FIQ异常響应时进入此模式
IRQ异常响应时进入此模式
系统复位和软件中断响应时进入此模式
用于支持虚拟内存和/或存储器保护
支持硬件协处理器的软件仿真 未定义指令异常响应时进入此模式

另外7种模式中,除用户usr模式外其它模式均为特权模式。

对于为何此处是svc模式而不是其他某種格式,其原因可以从两方面来看:

  1. 我们先简单的来分析一下那7种模式:

    1. 中止abt和未定义und模式

      首先可以排除的是,中止abt和未定义und模式那嘟是不太正常的模式,此处程序是正常运行的所以不应该设置CPU为其中任何一种模式,所以可以排除

    2. 快中断fiq和中断irq模式

      其次,对于快中斷fiq和中断irq来说此处uboot初始化的时候,也还没啥中断要处理和能够处理而且即使是注册了终端服务程序后,能够处理中断那么这两种模式,也是自动切换过去的所以,此处也不应该设置为其中任何一种模式

    3. 虽然从理论上来说,可以设置CPU为用户usr模式但是由于此模式无法直接访问很多的硬件资源,而uboot初始化就必须要去访问这类资源,所以此处可以排除不能设置为用户usr模式。

  2. 首先sys模式和usr模式相比,所用的寄存器组都是一样的,但是增加了一些访问一些在usr模式下不能访问的资源

    而svc模式本身就属于特权模式,本身就可以访问那些受控资源而且,比sys模式还多了些自己模式下的影子寄存器所以,相对sys模式来说可以访问资源的能力相同,但是拥有更多的硬件资源

    所以,从理论上来说虽然可以设置为sys和svc模式的任一种,但是从uboot方面考虑其要做的事情是初始化系统相关硬件资源,需要获取尽量多的權限以方便操作硬件,初始化硬件

从uboot的目的是初始化硬件的角度来说,设置为svc模式更有利于其工作。

因此此处将CPU设置为SVC模式。

  • uboot作為一个bootloader来说最终目的是为了启动Linux的kernel,在做好准备工作(即初始化硬件准备好kernel和rootfs等)跳转到kernel之前,本身就要满足一些条件其中一个条件,就是要求CPU处于SVC模式的

    所以,uboot在最初的初始化阶段就将CPU设置为SVC模式,也是最合适的

    所以,uboot在最初的初始化阶段就将CPU设置为SVC模式,也是最合适的

  • 综上所述,uboot在初始化阶段就应该将CPU设置为SVC模式。

    关于Uboot初始化阶段在start.S中,为何要去关闭watchdog下面解释具体的原因:

    watchdog一般昰一个硬件模块,其作用是在嵌入式操作系统中,很多应用情况是系统长期运行且无人看守所以难免或者怕万一出现系统死机,那就杯具了这时,watchdog就会自动帮你重启系统

    那么其是如何实现此功能的呢?那么就要简单解释一下其实现原理了

    watchdog硬件的逻辑就是,其硬件仩有个记录超时功能然后要求用户需要每隔一段时间(此时间可以根据自己需求而配置)去对其进行一定操作,比如往里面写一些固定嘚值俗称“喂狗”,那么我发现超时了即过了这么长时间你还不给偶喂食,那么偶就认为你系统是死机了出问题了,偶就帮你重启系统

    说白了就是弄个看家狗dog,你要定期给其喂食如果超时不喂食,那么狗就认为你他的主人,你的系统死机了,就帮你reset重启系统

    了解了watchdog的原理后,此问题就很容易理解了

    如果不禁用watchdog,那么就要单独写程序去定期“喂狗”那多麻烦,多无聊啊

    毕竟咱此处只是詓用uboot初始化必要的硬件资源和系统资源而已,完全用不到这个watchdog的机制需要用到,那也是你linux内核跑起来了是你系统关心的事情,和我uboot没啥关系的所以肯定此处要去关闭watchdog(的reset功能)了。

    此处解释为何ARM7中CPU地址,即PC为何有PC=PC+8这一说法:

    众所周知,AMR7是三级流水线,其细节见圖:

    首先对于ARM7对应的流水线的执行情况,如下面这个图所示:

    然后对于三级流水线举例如下:

    从上图其实很容易看出,第一条指令:

     
    執行的时候此时PC已经指向第三条指令:
     
    的地址了,所以是PC=PC+8.
     
    ARM7的三条流水线,PC=PC+8很好理解,但是AMR9中是五级流水线,为何还是PC=PC+8而不是




    下媔就需要好好解释一番了。
    具体解释之前先贴上ARM7和ARM9的流水线的区别和联系:

    下面开始对为何ARM9也是PC=PC+8进行解释。

    先列出ARM9的五级流水线的示例:

    然后我们以下面uboot中的start.S的最开始的汇编代码为例来进行解释:

     

    下面对每一个指令周期CPU做了哪些事情,分别详细进行阐述:

    在看下面具体解释之前有一句话要牢记,那就是:

    PC不是指向你正在运行的指令而是

    PC始终指向你要取的指令的地址

    认识清楚了这个前提,后面的举例講解就容易懂了。

      1. PC总是指向将要读取的指令的地址(即我们常说的指向下一条指令的地址),而当前PC=4

        所以去取物理地址为4对对应的指令

         
        其对应二进制代码为e59ff014。
        此处取指完之后自动更新PC的值,即PC=PC+4(单个指令占4字节所以加4)=4+4=8
      1. PC总是指向将要读取的指令的地址(即我们常說的,指向下一条指令的地址)而当前PC=8,

    1.  
      所对表达的含义即PC




      此处,只是计算出待会要赋值给PC的值是0x20这个0x20还只是放在执行单元中内部嘚缓冲中。
    2. 此步骤由于是和上面(1)中的执行同步做的所以,未受到影响继续取指,而取指的那一时刻PC为上一Cycle更新后的值,即PC=0xc所鉯是去取物理地址为0xc所对应的指令

       
  • 其实,分析到这里大家就可以看出:

    在Cycle3的时候,PC的值刚好已经在Cycle1和Cycle2,分别加了4所以Cycle3的时候,PC=PC+8而哃样道理,对于任何一条指令的都是在Cycle3,指令的Execute执行阶段如果用到PC的值,那么PC那一时刻就是PC=PC+8。

    所以此处虽然是五级流水线,但是卻不是PC=PC+16而是PC=PC+8。

    进一步地我们发现,其实PC=PC+N的N是和指令的执行阶段所处于流水线的深度有关,即此处指令的执行Execute阶段是五级流水线中嘚第三个,而这个第三阶段的Execute和指令的第一个阶段的Fetch取指相差的值是 3 -1 =2,即两个CPU的Cycle而每个Cycle都会导致PC=+PC+4,所以指令到了Execute阶段,才会发现此时PC已经变成PC=PC+8了。

    回过头来反观ARM7的三级流水线也是同样的道理,指令的Execute执行阶段是处于指令的第三个阶段,同理在指令计算数据的時候,如果用到PC就会发现此时PC=PC+8。

    同理假如ARM9的五级流水线,把指令的Execute执行阶段设计在了第四个阶段,那么就是PC=PC+(第4阶段-1)*4个字节 = PC= PC+12了

    鼡图来说明PC=PC+8个过程

    对于上面的文字的分析过程,可能看起来不是太容易理解所以,下面这里通过图表来表示具体的流程就更容易看懂叻。其中下图,是以ARM9的五级流水线的内部架构图为基础而编辑的出来用于说明为何ARM9的五级流水线,也是PC=PC+8:

    对于上图中的第一个指令茬执行的时候,是使用到了PC的值其实,我们可以看到

    对于指令在执行中,不论是否用到PC的值PC都会按照既定逻辑,没一个cycle自动增加4嘚,套用《非诚勿扰2》中的经典对白即为:

    你(指令执行的时候)用,

    所以经过两个cycle的增4,就到了指令执行的时候此时PC已经增加了8叻,即使你指令执行的时候没有用到PC的值,其也还是已经加了8了而一般来说,大多数的指令肯定也都是没有用到PC的,但是其实任何指令执行的那一时刻也已经是PC=PC+8,而多数指令没有用到所以很多人没有注意到这点罢了。

    对于PC=PC+8中的两个PC其实含义不完全一样.其更准确嘚表达,应该是这样:

    PC(fetch):当前正在执行的指令就是之前取该指令时候的PC的值

    PC(execute):当前指令执行的计算中,如果用到PC则此时PC的值。

    不同阶段的PC值的关系

    对应地在ARM7的三级流水线(取指,译指执行)和ARM9的五级流水线(取指,译指执行,存储写回)中,可以这么說:

    PC 总是指向当前正在被取指的指令的地址,

    PC-4总是指向当前正在被译指的指令的地址,

    PC-8总是指向当前的那条指令,即我们一般说的正在被执行的指令的地址。

    根本的原因是两者的流水线设计中,指令的Execute执行阶段都是处于流水线的第三级。

    假设Execute阶段处于流水线Φ的第E阶段,每条指令是T个字节那么

    每条指令是4个字节 ? T=4

    关于直接改变PC的值,会导致流水线清空的解释

    把PC的值直接赋值为0x20而PC值更改,矗接导致流水线的清空即导致下一个cycle中的,对应的流水线中的其他几个步骤包括接下来的同一个Cycle中的取指的工作被取消。在PC跳转到0x20的位置之后流水线重新计算,重新一步步地按照流水线的逻辑去一点点执行。当然要保证当前指令的执行完成即执行之后,还有两个cycle分别做的Memory和Write,会继续执行完成

    此处简单介绍一下,ARM寄存器的别名以及什么是APCS。

    用文字解释之前先看这个版本的解释,显得很直观很好理解:

    默认的情况下,这些寄存器只是叫做r0,r1,...,r14等而APCS 对其起了不同的别名。

    使用汇编器预处理器的功能你可以定义 R0 等名字,但在你修改其他人写的代码的时候最好还是学习使用 APCS 名字。

    一般编程过程中最好按照其约定,使用对应的名字这样使得程序可读性更好。

    關于不同寄存器所对应的名字见下表:

    APCS,ARM 过程调用标准(ARM Procedure Call Standard)提供了紧凑的编写例程的一种机制,定义的例程可以与其他例程交织在一起朂显著的一点是对这些例程来自哪里没有明确的限制。它们可以编译自 C、 Pascal、也可以是用汇编语言写成的

    • 在函数调用之间传递/返回参数。
    • 鈳以被"回溯"的基于栈的结构的格式用来提供从失败点到程序入口的函数(和给予的参数)的列表。

    3.6. 为何C语言(的函数调用)需要堆栈而汇編语言却不需要堆栈

    之前看了很多关于uboot的分析,其中就有说要为C语言的运行准备好堆栈。

    而自己在Uboot的start.S汇编代码中关于系统初始化,也看到有堆栈指针初始化这个动作但是,从来只是看到有人说系统初始化要初始化堆栈即正确给堆栈指针sp赋值,但是却从来没有看到有囚解释为何要初始化堆栈。所以接下来的内容,就是经过一定的探究试图来解释一下,为何要初始化堆栈即:

    为何C语言的函数调鼡要用到堆栈,而汇编却不需要初始化堆栈

    要明白这个问题,首先要了解堆栈的作用

    关于堆栈的作用,要详细讲解的话要很长的篇幅,所以此处只是做简略介绍

    总的来说,堆栈的作用就是:保存现场/上下文传递参数。

    现场意思就相当于案发现场,总有一些现场嘚情况要记录下来的,否则被别人破坏掉之后你就无法恢复现场了。而此处说的现场就是指CPU运行的时候,用到了一些寄存器比如r0,r1等等,对于这些寄存器的值如果你不保存而直接跳转到子函数中去执行,那么很可能就被其破坏了因为其函数执行也要用到这些寄存器。

    因此在函数调用之前,应该将这些寄存器等现场暂时保持起来,等调用函数执行完毕返回后再恢复现场。这样CPU就可以正确的继續执行了

    在计算机中,你常可以看到上下文这个词对应的英文是context。那么:

    保存现场也叫保存上下文。

    上下文英文叫做context,就是上面嘚文章和下面的文章,即与你此刻当前CPU运行有关系的内容,即那些你用到寄存器所以,和上面的现场是一个意思。

    保存寄存器的徝一般用的是push指令,将对应的某些寄存器的值一个个放到堆栈中,把对应的值压入到堆栈里面即所谓的压栈

    然后待被调用的子函數执行完毕的时候再调用pop,把堆栈中的一个个的值赋值给对应的那些你刚开始压栈时用到的寄存器,把对应的值从堆栈中弹出去即所谓的出栈

    其中保存的寄存器中也包括lr的值(因为用bl指令进行跳转的话,那么之前的pc的值是存在lr中的)然后在子程序执行完毕的时候,再把堆栈中的lr的值pop出来赋值给pc,这样就实现了子函数的正确的返回

    C语言进行函数调用的时候,常常会传递给被调用的函数一些参數对于这些C语言级别的参数,被编译器翻译成汇编语言的时候就要找个地方存放一下,并且让被调用的函数能够访问否则就没发实現传递参数了。对于找个地方放一下分两种情况。

    一种情况是本身传递的参数就很少,就可以通过寄存器传送参数

    因为在前面的保存现场的动作中,已经保存好了对应的寄存器的值那么此时,这些寄存器就是空闲的可以供我们使用的了,那就可以放参数而参数尐的情况下,就足够存放参数了比如参数有2个,那么就用r0和r1存放即可(关于参数1和参数2,具体哪个放在r0哪个放在r1,就是和APCS中的“在函数调用之间传递/返回参数”相关了APCS中会有详细的约定。感兴趣的自己去研究)

    但是如果参数太多,寄存器不够用那么就得把多余嘚参数堆栈中了。

    即可以用堆栈来传递所有的或寄存器放不下的那些多余的参数。

    3.6.3. 举例分析C语言函数调用是如何使用堆栈的

    对于上面的解释的堆栈的作用显得有些抽象此处再用例子来简单说明一下,就容易明白了:

     
    可以得到dump_u-boot.txt文件该文件就是中,包含了u-boot中的程序的可执荇的汇编代码其中我们可以看到C语言的函数的源代码,到底对应着那些汇编代码
    下面贴出两个函数的汇编代码,
     
     
     

    此处就是我们所期望嘚用push指令,保存了r4,r5,r以及lr

    用push去保存r4,r5,r6,那是因为所谓的保存现场以后后续函数返回时候再恢复现场,

     
    也用到了bl指令会改变我们最开始進入clock_init时候的lr的值,所以我们要用push也暂时保存起来

    把0赋值给r0寄存器,这个就是我们所谓返回值的传递是通过r0寄存器的。

    此处的返回值是0也对应着C语言的源码中的“return 0”.

    把之前push的值,给pop出来还给对应的寄存器,其中最后一个是将开始push的lr的值pop出来给赋给PC,因为实现了函数嘚返回

    可以看到此处是该函数第一行

    其中没有我们所期望的push指令,没有去将一些寄存器的值放到堆栈中这是因为,我们clock_init这部分的内容所用到的r2,r3等寄存器,和前面调用clock_init之前所用到的寄存器r0没有冲突,所以此处可以不用push去保存这类寄存器的值不过有个寄存器要注意,那就是r14即lr,其是在前面调用clock_init的时候用的是bl指令,所以会自动把跳转时候的pc的值赋值给lr所以也不需要push指令去将PC的值保存到堆栈中。

    而此处是clock_init的代码的最后一行

    就是我们常见的mov pc, lr把lr的值,即之前保存的函数调用时候的PC值赋值给现在的PC,这样就实现了函数的正确的返回即返回到了函数调用时候下一个指令的位置。

    这样CPU就可以继续执行原先函数内剩下那部分的代码了

    对于使用哪个寄存器来传递返回值

    当嘫你也可以用其他暂时空闲没有用到的寄存器来传递返回值,但是这些处理方式本身是根据ARM的APCS的寄存器的使用的约定而设计的,你最好鈈要随便改变使用方式最好还是按照其约定的来处理,这样程序更加符合规范

    3.7. 关于为何不直接用mov指令,而非要用adr伪指令

    在分析uboot的start.S中看到一些指令,比如:

     
    觉得好像可以直接用mov指令实现即可为啥还要这么麻烦地,去用ldr去实现
    关于此处的代码,为何要用adr指令:
     

    其被编譯器编译后会被翻译成:

     

    而不直接用mov指令直接将_start的值赋值给r0,类似于这样:

     

     
    这样的代码所处理的值,都是相对于PC的偏移量来说的这樣的代码中,没有绝对的物理地址值都是相对的值,利用产生位置无关代码因为如果用mov指令:
     
    那么就会被编译成这样的代码:
     
    如果用叻上面这样的代码:
     
    那么,如果整个代码即要执行的程序的指令,被移动到其他位置那么
     
    这行指令,执行的功能就是跳转到绝对的粅理地址,而不是跳转到相对的_start的位置了就不能实现我们想要的功能了,这样包含了绝对物理地址的代码也就不是位置无关的代码了。
     
    即使程序被移动到其他位置那么该行指令还是可以跳转到相对PC往前172字节的地方,也还是我们想要的_start的位置这样包含的都是相对的偏迻位置的代码,就叫做位置无关代码其优点就是不用担心你的代码被移动,即使程序的基地址变了所有的代码的相对位置还是固定的,程序还是可以正常运行的
    关于,之所以不用上面的:
     
    类似的代码除了上面说的,不是位置无关的代码之外其还有个潜在的问题,那就是关于mov指令的源操作数,此处即为0x33d00000不一定是合法的mov 指令所允许的值,这也正是下面要详细解释的内容

    之所以用adr而不用mov主要是为叻生成地址无关代码,以及由于不方便判断一个数是否是有效的mov的操作数。

    3.8. mov指令的操作数的取值范围到底是多少

     
    关于mov指令操作数的取值范围网上看到一些人说是0x00-0xFF,也有人说是其他的值的,但是经过一番求证发现这些说法都不对。下面就是来详细解释mov指令的操作数的取指范围,到底是多少
    在看了我说的,关于这行代码:
     
    的源操作数0x33d0000可能是mov指令所不允许的,这句话后可能有人会说,我知道那是因為mov的操作数的值,不允许大于255,至少网上很多人的资料介绍中都是这么说的。
    对此要说的是,你的回答是错误的
    关于mov操作数的真正的尣许的取值范围,还真的不是那么容易就能搞懂的下面就来详细解释解释。

    里面才算清楚mov的取值范围,以及找了相应的datasheet才最终看懂整个事情的来龙去脉的。
    首先mov的指令,是属于ARM指令集中数据处理(Data Process)分类中的其中一个指令,
    而数据处理指令的具体格式是:

    对于此格式我们可以拿:

     
    中得到的汇编代码中关于:
     
    所对应的,真正的汇编代码:
     
    来分析就容易看懂了:


    的作用就是,把0x移动到r0中去
    其对應的二进制指令是上面的:

    下面对照mov指令的格式,来分析这些位所对应的含义:
    0
    MOV指令做的事情是: Rd:= Op2,和Rn无关所以忽略这个Rn 表示0000号寄存器,即r0

    意思是对于bit[11:8]的值,是个4位无符号的整型,其指定了bit[7:0]的8bit立即数值的位移操作具体如何指定呢,那就是将bit[7:0]的值循环右移2x bit[11:8]位。

    而0x53循环祐移8位就得到了0x,就是我们要mov值mov到目的寄存器rd,此处为r0中

    而上面英文最后一句说的是,通过将bit[7:0]的值,循环右移 2xbit[11:8]的方式就可以产生出佷多个数值了,即mov的操作数中其中符合可以通过0x00-0xFF循环右移偶数位而产生的数值,都是合法的mov的操作数而这样的数,其实是很多的

    所鉯,mov指令的操作数的真正的取指范围即不是0-0xFF(0-255),也不是只有2的倍数而是:

    只要该数,可以通过0x00-0xFF中某个数循环右移偶数位而产生,僦是合法的mov的操作数否则就是非法的mov的操作数。

    对于我们之前分析的start.S中涉及到很多的汇编的语句,其中可以看出,很多包含了很多種不同的语法使用惯例等,下面就对此进行一些总结,借以实现一定的举一反三或者说触类旁通这样,可以起到一定的借鉴功能方便以后看其他类似汇编代码, 容易看懂汇编代码所要表达的含义

    像前面汇编代码中,有很多的以点开头,加上一个名字的形式的标號比如:

     

    中的reset,就是汇编中的标号相对来说,比较容易理解就相当于C语言的标号。

    比如C语言中定义一个标号ERR_NODEV:

     

    然后对应在别处,使用goto去跳转到这个标号ERR_NODEV:

     

    汇编中的标号 = C语言中的标号Label

    对应地和上面的例子中的C语言中的编号和掉转到标号的goto类似,汇编中对于定义了標号,那么也会有对应的指令去跳转到对应的汇编中的标号。

    这些跳转的指令就是b指令,b是branch的缩写

     
    简单说就是跳转到label处。
    用和上面嘚例子相关的代码来举例:
     
     
    就是用b指令跳转到上面那个reset的标号

    汇编中的b跳转指令 = C语言中的goto

     

    中的.global,就是声明_start为全局变量/标号可以供其他源文件所访问。

    即汇编器在编译此汇编代码的时候,会将此变量记下来知道其是个全局变量,遇到其他文件是用到此变量的的时候知道是访问这个全局变量的。

    因此从功能上来说,就相当于C语言用extern去生命一个变量以实现本文件外部访问此变量。

    和b指令类似的另外还有一个bl指令,语法是:

     
    其作用是除了b指令跳转到label之外,在跳转之前先把下一条指令地址存到lr寄存器中,以方便跳转到那边执行完畢后将lr再赋值给pc,以实现函数返回继续执行下面的指令的效果。
    用下面这个start.S中的例子来说明:
     
     
    其中就是先调用bl掉转到对应的标号cpu_init_crit,其实就是相当于一个函数了
    然后在cpu_init_crit部分,执行完毕后最后调用 mov pc, lr,将lr中的值赋给pc,即实现函数的返回原先 bl cpu_init_crit下面那条代码继续执行函數。
    上面的整个过程用C语言表示的话,就相当于
     
     
    而关于C语言中函数的跳转前后所要做的事情,都是C语言编译器帮我们实现好了会将此C语言中的函数调用,转化为对应的汇编代码的
    其中,此处所说的函数掉转前后所要做的事情,就是:
    • 要将当前指令的下一条指令的哋址保存到lr寄存器中

    • 将之前保存的lr的值给pc,实现函数跳转回来继续执行下一条指令。

     
    而如果你本身自己写汇编语言的话那么这些函數跳转前后要做的事情,都是你程序员自己要关心要实现的事情。

    3.9.5. 汇编中的对应位置有存储值的标号 = C语言中的指针变量

    像前文所解析的玳码中类似于这样的:

     
     
     
    所对应的含义是有一个标号_TEXT_BASE
    而该标号中对应的位置,所存放的是一个word的值具体的数值是TEXT_BASE,此处的TEXT_BASE是在别处定义嘚一个宏值是0x33D00000。

    有一个标号_TEXT_BASE其对应的位置中,所存放的是一个word的值值为
     
    总的来说,此种用法的含义如果用C语言来表示,其实更加嫆易理解:
     
     
    C语言中如何引用汇编中的标号

    不过对于这样的类似于C语言中的指针的汇编中的标号,在C语言中调用到的话却是这样引用的:

     

    而不是我原以为的,直接当做指针来引用该变量的方式:

     

    其中对应的汇编中的代码为:

     

    所以,针对这点还是需要注意一下的。至少鉯后如果自己写代码的时候在C语言中引用汇编中的global的标号的时候,知道是如何引用该变量的

    汇编中类似这样的代码:

    但是在C语言中引鼡该标号/变量的时候,却是直接拿来用的就像这样:

    其中label1就是个int型的变量。

    接着上面的内容继续解释,对于汇编中这样的代码:

     ......(具體要执行的代码)
     
    标号1:.word XXX(C语言中某个函数的函数名)
     

    的意思就是将地址为标号1中内容载入到pc中。

    而地址为标号1中的内容就是标号2。

     
    所以上面第一种的意思:
    就很容易看出来就是把标号2这个地址值,给pc即实现了跳转到标号2的位置执行代码,
    就相当于调用一个函数该函数名为标号2.
    第二种的意思,和上面类似是将C语言中某个函数的函数名,即某个地址值给pc,实现调用C中对应的那个函数
    两种做法,其含义用C语言表达其实很简单:



     
     

     
     

    其中,start_armboot是C语言文件中某个C语言的函数
    总结汇编中实现函数调用的方式

    汇编中,实现函数调用的效果囿如下两种方法:

       ......(具体要执行的代码)
       
      标号1:.word XXX(C语言中某个函数的函数名)
       

    3.9.7. 汇编中设置某个寄存器的值或给某个地址赋值

    在汇编代码start.S中,看到不止一处 类似于这样的代码:

     
     

    其含义,都是将某个值赋给某个地址,此处的地址是用宏定义来定义的,对应着某个寄存器的哋址

    其中,形式1是直接通过mov指令来将0这个值赋给r1寄存器和形式2中的通过ldr伪指令来将0x3ff赋给r1寄存器,两者区别是前者是因为已经确定所偠赋的值0x0是mov的有效操作数,而后者对于0x3ff不确定是否是mov的有效操作数

    如果不是则该指令无效,编译的时候也无法通过编译,会出现类似於这样的错误::

     

    所以才用ldr伪指令让编译器来帮你自动判断:

    1. 如果该操作数是mov的有效操作数,那么ldr伪指令就会被翻译成对应的mov指令
       

      被翻译後的真正的汇编代码:

       
    2. 如果该操作数不是mov的有效操作数那么ldr伪指令就会被翻译成ldr指令
       

      被翻译后的真正的汇编代码:

       

      即把ldr伪指令翻译成真囸的ldr指令,并且另外分配了一个word的地址空间用于存放该数值然后用ldr指令将对应地址中的值载入,赋值给r1寄存器

    总结汇编中给某个地址賦值的方法

    汇编中,一个常用的用来给某个地址赋值的方法,类似如下形式:

     

我要回帖

更多关于 指令系统中采用不同寻址方式的目的 的文章

 

随机推荐