嵌入式Lab2:Arm指令

這個(gè)實(shí)驗(yàn)的目的是深入理解ARM和Thumb指令的特點(diǎn),了解編譯選項(xiàng)對(duì)代碼產(chǎn)生的影響。

配合課程

ARM處理器

實(shí)驗(yàn)?zāi)康?/h3>
  • 深入理解ARM指令和Thumb指令的區(qū)別和編譯選項(xiàng);
  • 深入理解某些特殊的ARM指令,理解如何編寫C代碼來得到這些指令;
  • 深入理解ARM的BL指令和C函數(shù)的堆棧保護(hù);
  • 深入理解如何實(shí)現(xiàn)C和匯編函數(shù)的互相調(diào)用。

實(shí)驗(yàn)器材

硬件

  • 實(shí)驗(yàn)主板一塊(raspberryPi);
  • 5V/1A電源一個(gè);
  • microUSB線一根;

軟件

  1. 交叉編譯軟件(arm-linux-gcc)

操作方法和實(shí)驗(yàn)步驟

  1. 實(shí)驗(yàn)設(shè)備連接示意圖

    連接示意圖

  2. 同一個(gè)程序,用arm指令和thumb指令編譯,得到的結(jié)果有什么不同
    代碼如下:
    //hello.c
    #include <stdio.h>

    int main(){
        printf("Hello World\n");
        return 0;
    }
    

用兩種不同的方式編譯:


編譯程序 hello.c

得到的結(jié)果:


指令對(duì)比

區(qū)別:
  • thumb的指令地址一次只增加2,即每條指令只占16位。相對(duì)的,arm每個(gè)地址增加4,每條指令占位32位。
  • 在 Thumb 狀態(tài)下,單寄存器加載和存儲(chǔ)指令只能訪問寄存器 R0~R7。
  1. 對(duì)于 ARM 指令,能否產(chǎn)生條件執(zhí)行的指令
    可以的,C語言代碼:
    //if.c
    #include <stdio.h>
    int main(){
    int i;
    i = 1;
    if(i == 1)i++;
    else i--;
    return 0;
    }
    生成的ARM指令代碼<main>函數(shù)部分:

    if_arm.S

    其中可以看到bne指令。

  2. 設(shè)計(jì) C 的代碼場景,觀察是否產(chǎn)生了寄存器移位尋址
    C語言代碼如下:
    //shift.c
    #include <stdio.h>
    int lShift(int *i, int j){
    return i[j << 2];
    }
    int main(){
    int i[256];
    int j = 18;
    lShift(i, j);
    return 0;
    }
    生成的ARM指令代碼主要部分(可以看到ldr指令):

    shift_ARM.S

  3. 設(shè)計(jì) C 的代碼場景,觀察一個(gè)復(fù)雜的 32 位數(shù)是如何裝載到寄存器的
    C語言代碼如下:
    //load32.c
    #include <stdio.h>
    int main(){
    double i;
    long j;
    i = 204800001.1;
    j = 2048000011;
    return 0;
    }
    生成的ARM代碼如下:

    Load32_ARM.S

    可以看出,ARM是將32位數(shù)放到堆棧中,然后依次取出放入到寄存器當(dāng)中。

  4. 寫一個(gè) C 的多重函數(shù)調(diào)用的程序,觀察和分析: a. 調(diào)用時(shí)的返回地址在哪里? b. 傳入的參數(shù)在哪里? c. 本地變量的堆棧分配是如何做的? d. 寄存器是 caller 保存還是 callee 保存?是全體保存還是部分保存?
    C代碼如下:
    //mulFunc.c
    #include <stdio.h>
    int fun1(int i){
    int j = i + 1;
    return j;
    }

    int fun2(int i, int j){
       int x = i + j;
       x = fun1(x);
       return x;
    }
    
    int main(){
       int a = 1, b = 2;
       a = fun2(a, b);
       return 0;
    }
    

得到的ARM匯編代碼如下:


mulFunc_ARM.S

a.調(diào)用時(shí)的返回地址放在lr寄存器當(dāng)中。
b.傳遞的參數(shù)放在了堆棧中。
c.被調(diào)函數(shù)首先將傳遞過來的堆棧指針 sp 拷貝到指針 fp 中,然后將本地變量從 r0 開始存放,當(dāng)存放到 r2 之后,其余的變量將會(huì)通過 r3 存放到堆棧中去,最后將 fp 中保存的指針拷貝回 sp,恢復(fù)被調(diào)前的堆棧情況,然后返回。
d.參數(shù)保存在caller,地址保存在callee,部分保存的

  1. MLA 是帶累加的乘法,嘗試要如何寫 C 的表達(dá)式能編譯得到 MLA 指令
    C代碼如下:
    //mla.c
    #include <stdio.h>
    int mla(int i, int j, int k){
    return i * j + k;
    }

    int main(){
        int i = 2, j = 20, k = 30;
        mla(i, j, k);
        return 0;
    }
    

編譯得到的ARM匯編文件(編譯時(shí)用-O1優(yōu)化編譯)


mla_ARM.S

可以看到mla指令。

  1. BIC是對(duì)某一個(gè)比特清零的指令,嘗試要如何寫 C 的表達(dá)式能編譯得到 BIC 指令
    C代碼如下:
    //bic.c
    #include <stdio.h>
    int bic(int i, int j){
    return i&~j;
    }

    int main(){
        int i = 0x8828;
        int j = 20484;
        bic(i, j);
        return 0;
    }
    

匯編得到的ARM指令(要使用-O1編譯優(yōu)化)


bic_ARM.S

可以很明顯的看到bic指令。

  1. 編寫一個(gè)匯編函數(shù),接受一個(gè)整數(shù)和一個(gè)指針做為輸入,指針?biāo)笐?yīng)為一個(gè)字符串,該匯編函數(shù)調(diào)用C語言的 printf()函數(shù)輸出這個(gè)字符串的前n個(gè)字符,n即為那個(gè)整數(shù)。在C語言寫的main()函數(shù)中調(diào)用并傳遞參數(shù)給這個(gè)匯編函數(shù)來得到輸出。
    匯編函數(shù)代碼(cutprint.S):
    .global cutprint
    cutprint:
    push {R5, R6, R7, lr}
    MOV R5, R0
    MOV R6, R1
    MOV R7, #0
    CMP R7, R6
    BGE exit
    begin:
    LDR R0, =char
    LDR R1, [R5, R7]
    CMP R1, #0
    BEQ exit
    bl printf
    ADD R7, R7, #1
    CMP R7, R6
    BLT begin
    exit:
    LDR R0, =newline
    bl printf
    MOV R0, R7
    pop {R5, R6, R7, pc}

    .data
        char: .asciz "%c"
        newline: .asciz "\n"
    

C語言主函數(shù)代碼:
//cutprint.c
#include <stdio.h>

    extern int cutprint(char *, int);
    int main(){
        int a;
        char s[100];
        scanf("%s %d", s, &a);
        a = cutprint(s, a);
        printf("Print %d character.\n", a);
        return 0;
    }

編譯后在樹莓派的執(zhí)行結(jié)果:


測試1

測試2

測試3
  1. 自選擴(kuò)展內(nèi)容1:編寫測試程序,測試ARM指令和Thumb指令的執(zhí)行效率
    測試代碼:
    //speed.c
    #include <stdio.h>

       int fibo(int n){
           if(n <= 1) return 1;
           return fibo(n - 2) + fibo(n - 1);
       }
    
       int main(){
           int a;
           scanf("%d", &a);
           printf("%d\n", fibo(a));
           return 0;
       }
    

在編譯時(shí),在普通編譯之外,還有-O2和-O3編譯優(yōu)化的編譯。


編譯選項(xiàng)

測試結(jié)果

從測試結(jié)果中可以看到,arm的執(zhí)行速度要比thumb的執(zhí)行速度快一些。

  1. 自選擴(kuò)展內(nèi)容2:編寫測試程序,測試使用帶條件的ARM指令和不使用時(shí)的執(zhí)行效率。
    含帶條件的ARM指令(useif.S):
    .global add
    add:
    CMP R0, #0
    ADDNE R0, R0, R1
    MOV pc, lr
    不含的ARM指令(noif.S):

    .global add
    add:
       CMP R0, #0
       ADD R0, R0, R1
       MOV pc, lr
    

測試代碼:
//test.c
#include <stdio.h>

        extern int add(int, int);

        int main(){
            int a, b, i, times;
            scanf("%d %d %d", &a, &b, &times);
            for (i=0; i<times; i++)
                a = add(a, b);
            printf("%d\n", a);
            return 0;
        }

編譯選項(xiàng):


編譯過程

執(zhí)行結(jié)果

通過比較執(zhí)行結(jié)果可以發(fā)現(xiàn)兩個(gè)程序執(zhí)行的時(shí)間基本上沒有什么差別。

討論與心得

本次實(shí)驗(yàn)主要是對(duì)代碼的編寫和對(duì)ARM指令的了解,沒有涉及過多的硬件上的內(nèi)容。需要的硬件內(nèi)容(交叉編譯環(huán)境以及連接和文件傳遞)在上次實(shí)驗(yàn)已經(jīng)設(shè)計(jì),所以從總體上來說比上一次實(shí)驗(yàn)簡單。
同時(shí)本次使用和讓我了解到了ARM指令的一些性質(zhì),以及ARM指令和Thumb指令的區(qū)別。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容