《计算机组成与设计(ARM版)》读书笔记-第二章指令2
文章目錄
- 2.9 人機交互
- 2.10 LEGv8中的寬立即數和地址的尋址
- 2.10.1 寬立即數
- 2.10.2 分支中的尋址
- 2.10.3 LEGv8尋址模式總結
- 2.10.4 機器語言解碼
- 2.11 并行與指令:同步
- 2.12 翻譯并啟動程序
- 2.12.1 編譯器
- 2.12.2 匯編器
- 2.12.3 鏈接器
- 2.12.4 加載器
- 2.12.5 動態(tài)鏈接庫
- 2.12.6 啟動Java程序
- 2.13 綜合實例:C排序程序
- 2.13.1 swap過程
- 1 為swap分配寄存器
- 2 為swap過程體生成代碼
- 3 完整的swap過程
- 2.13.2 sort過程
- 1 為sort分配寄存器
- 2 為sort過程體生成代碼
- 3 sort 中的過程調用
- 4 sort中的參數傳遞
- 5 在sort中保存寄存器
- 6 完整的sort過程
2.9 人機交互
計算機發(fā)明最初是用于處理數字的,但在進入商業(yè)應用時,計算機已經能夠處理文字了。今天計算機基本都使用8位的字節(jié)來表示字符,并遵行美國信息交換標準代碼(即ASCII碼)。
注意,在ASCII碼中,所有大寫字母和對應小寫字母的差均為32,這個規(guī)律可以用于快速檢查和切換大小寫。 另外一個有用的ASCII值是0,表示null,C語言常用它來標記字符串的結尾。
通過使用一系列指令可以從一個雙字中提取出一個字節(jié),所以load register 和 store register 足以完成字和字節(jié)的傳輸。然而,很多程序中經常要處理文本,因此LEGv8額外提供了字節(jié)移動指令。讀字節(jié)指令LDURB(load byte)從內存中讀出一個字節(jié),并將其放在一個寄存器最右邊的8位。存字節(jié)指令STURB(store byte)把一個寄存器最右邊的8位(即一個字節(jié))取出,并寫到內存。這樣,我們可以通過以下指令序列復制一個字節(jié):
LDURB X9,[X0,#0] // 讀取字節(jié) STURB X9,[X1,#0] // 向目的地寫 字節(jié)字符通常被組合為字符數目可變的字符串。表示一個字符串有三種方法:(1) 保留字符串的第一個位置用于給出字符串的長度;(Java采用該種方法)(2)附加一個指明字符串長度的變量(如再結構體中)。(3)字符串最后的位置用一個字符來標識其結尾。C語言使用第三種方法,用一個值為零(ASCII碼中的null)的字節(jié)來結束字符串。因此,在C語言中,字符串“Cal” 由4字節(jié)表示,由十進制分別表示為67,97,108,0.
例題
編譯一個字符串復制過程,體會如何使用C語言的字符串
過程strcpy利用C語言中以null字節(jié)結束字符串的規(guī)定,將字符串y賦值給字符串x:
void strcpy( char x[],char y[]){size_t i;i=0;while((x[i]=y[i])!='\0') //賦值并且判斷是否符合條件i+=1;}請給出編譯后的LEGv8匯編代碼。
下面是在DS-5中 反匯編得到的結果,值得研究
strcpy0x0000000080001218: SUB sp, sp, #0x200x000000008000121C: STR x0, [sp, #0x8]0x0000000080001220: STR x1, [sp]0x0000000080001224: STR xzr, [sp, #0x18]0x0000000080001228: B 0x800012380x000000008000122C: LDR x0, [sp, #0x18]0x0000000080001230: ADD x0, x0, #0x10x0000000080001234: STR x0, [sp, #0x18]0x0000000080001238: LDR x1, [sp]0x000000008000123C: LDR x0, [sp, #0x18]0x0000000080001240: ADD x1, x1, x00x0000000080001244: LDR x2, [sp, #0x8]0x0000000080001248: LDR x0, [sp, #0x18]0x000000008000124C: ADD x0, x2, x00x0000000080001250: LDRB w1, [x1]0x0000000080001254: STRB w1, [x0]0x0000000080001258: LDRB w0, [x0]0x000000008000125C: CMP w0, #0x00x0000000080001260: B.NE 0x8000122c0x0000000080001264: NOP0x0000000080001268: ADD sp, sp, #0x200x000000008000126C: RET答案
下面是基本的LEGv8代碼片段。假設數組x和y的基址在X0和X1中,而i在X19中。strcpy調整棧指針,然后將保存寄存器X19保存在棧中。
strcpy:SUBI SP,SP,#8 //調整堆棧指針,為一個元素留出空地STUR X19, [SP,#0]//SAVE X19Java中的字符和字符串
Java對字符采用Unicode編碼,默認情況下,Unicode使用16位來表示一個字符。ASCII使用8位表示一個字符。
LEGv8指令集中的一些指令能夠顯示地讀取和存儲16位量,即半字。讀半字指令LDURH(load half) 從存儲器中讀出一個半字,然后將其放到寄存器的最右邊16位。與讀字節(jié)類似,讀半字指令LDURH也將半字看作有符號蘇,并進行符號擴展以填充寄存器中剩余的48位。存半字指令STRUH(store half)將寄存器最右邊的16位寫入寄存器。 下面的指令可以復制半字
LDURH X19,[X0,#0]//從源地址讀取半字(16bits) STURH X9,[X1,#0]//向目的寫入半字下面哪種類型的變量存放1000 000 000占用的內存空間最大?
A C語言中的long long int
long long在win32中存在,長度為8個字節(jié);定義為LONG64。
B C語言中的 string
C Java中的string
答案是C
參考:Java String 占用內存大小分析
2.10 LEGv8中的寬立即數和地址的尋址
2.10.1 寬立即數
2.10.2 分支中的尋址
LEGv8跳轉指令(無條件分支指令)采用最簡單的尋址方式,使用B型LEGv8指令格式,操作碼為6位,其余為都是地址段。
B 10000 // go to location 10000(Decimal)可以匯編成下面的格式(實際中更為復雜):
| 6位 | 26位 |
其中,跳轉指令的操作碼值為5,分支地址為10000ten10000_{ten}10000ten?。
和跳轉指令不同,條件分支指令除了分支地址之外還可以指定一個操作數。
因此:
被匯編成下面的指令,其中只有19位用于指定分支地址:
| 8位 | 19位 | 5位 |
對于條件分支指令,這種格式叫作CB型。
如果程序的地址只能放在19位的字段中,這意味著沒有程序能大于2192^{19}219,這在今天來說實在太小,因此是一種很不現實的選擇。
另一種方法是指定一個寄存器,該寄存器的值用于和分支地址的偏移量相加以得到最終地址,這樣分支指令的地址可按下面的方式計算:
程序計數器=寄存器內容+分支地址偏移量程序計數器=寄存器內容+分支地址偏移量程序計數器=寄存器內容+分支地址偏移量
這個求和結果允許程序的大小得到2642^{64}264,并且仍能使用條件分支,從而解決了分支地址大小的問題。但隨之而來的問題是,使用哪個寄存器?
答案取決于條件分支是如何使用的。 條件分支在循環(huán)和if語句中都可以找到,它們傾向于轉向附近的指令。例如,在SPEC基準測試程序中,大概一半的條件分支轉移范圍都在16條指令以內。因為程序計數器(PC)包含當前指令的地址,所以如果我們使用PC作為計算地址的寄存器,就可轉移到距當前指令±218±2^{18}±218個字(1個字=32位)的地方。 幾乎所有的循環(huán)和if語句都遠遠小于±218±2^{18}±218個字,因此PC是一個理想的選擇。這種分支地址的尋址方式稱為PC相對尋址(PC-relative addressing).
PC相對尋址:一種尋址方式,將PC和指令中的常數相加作為地址。
像近期大多數計算機一樣,LEGv8對所有條件分支使用PC相對尋址,因為這些指令的跳轉目標一般都比較接近分支指令本身。 另一方面,分支和鏈接(branch-and-link)指令引發(fā)的過程則并不一定總是靠近調用者,所以通常使用其他尋址方式。 因此,LEGv8 體系結構通過對分支指令以及分支和鏈接指令采用B型指令格式,為過程調用提供長地址。
因為LEGv8的所有指令都是4字節(jié)長,所以將PC相對尋址的地址設計成字地址(1個字=32位)而不是字節(jié)地址,從而可以擴展分支轉移的范圍。 通過將字段解釋成相對字地址而不是相對字節(jié)地址,19位的地址字段所指示的轉移范圍擴大了4倍:當前PC±1MB。同樣,分支指令的26位字段 也是字地址,即表示28位字節(jié)地址。
這里的1MB是怎么算出來的呢? 19位地址提供2182^{18}218種尋址方式,又因為是字地址,還需要乘以4倍(即222^222)所以 尋址大小為218×22=220=1MB2^{18}\times 2^2=2^{20} =1MB218×22=220=1MB
無條件分支(即跳轉指令)也采用PC相對尋址,這意味著轉移范圍是當前PC值±128MB。
這里的128MB是怎么算出來的呢? 26位地址提供2252^{25}225種尋址方式,又因為是字地址,還需要乘以4倍(即222^222)所以 尋址大小為225×22=227=220×27=128MB2^{25}\times 2^2=2^{27}=2^{20} \times 2^7 =128MB225×22=227=220×27=128MB
2.10.3 LEGv8尋址模式總結
2.10.4 機器語言解碼
小測驗:
LEGv8中條件分支的地址范圍多大(K=1024)?
答案: 分支前后大約1024K(即1MB)的地址范圍
LEGv8中條件分支的指令格式為
| 8位 | 19位 | 5位 |
其中19位用于指定分支地址。
這里的1MB是怎么算出來的呢? 19位地址提供2182^{18}218種尋址方式,又因為是字地址,還需要乘以4倍(即222^222)所以 尋址大小為218×22=220=1MB2^{18}\times 2^2=2^{20} =1MB218×22=220=1MB
LEGv8中跳轉和跳轉鏈接指令的地址范圍(M=1024K)是多大?
答案:分支前后大約128M的地址范圍
| 6位 | 26位 |
這里的128MB是怎么算出來的呢? 26位地址提供2252^{25}225種尋址方式,又因為是字地址,還需要乘以4倍(即222^222)所以 尋址大小為225×22=227=220×27=128MB2^{25}\times 2^2=2^{27}=2^{20} \times 2^7 =128MB225×22=227=220×27=128MB
另外關于MIPS指令跳轉的地址范圍可參考下面這篇文章:MIPS中分支和跳轉的地址范圍
2.11 并行與指令:同步
2.12 翻譯并啟動程序
本節(jié)描述了將一個存儲在外存(磁盤或閃存)某文件中的C程序轉換為計算機上可執(zhí)行程序的4個步驟。
C語言的翻譯層次。用高級語言編寫的程序首先被編譯為匯編語言程序,然后被匯編為機器語言組成的目標模塊。鏈接器將多個模塊和庫例程組合在一起解析所有的引用。加載器將機器代碼加載到內存的適當位置供處理器執(zhí)行。為了加速翻譯過程,某些步驟被跳過或和其他步驟結合在一起。
圖片來自:《計算機組成與設計(ARM版)》英文版
為了識別文件類型,UNIX使用文件的后綴,x.c代表C源文件,x.s表示匯編文件,x.o代表目標文件,x.a代表靜態(tài)鏈接庫,x.so代表動態(tài)鏈接庫,a.out默認情況下表示可執(zhí)行文件。
MS-DOS使用后綴.C表示C源文件,.ASM代表匯編文件,.OBJ代表目標文件,.LIB代表靜態(tài)鏈接庫,.DLL代表動態(tài)鏈接庫,.EXE代表可執(zhí)行文件。
2.12.1 編譯器
2.12.2 匯編器
2.12.3 鏈接器
2.12.4 加載器
2.12.5 動態(tài)鏈接庫
2.12.6 啟動Java程序
Java程序并不會被編譯成目標計算機的匯編語言,而是首先被編譯成易于解釋的指令序列—Java字節(jié)碼(Java bytecode)指令集。 該指令集被設計得與Java語言接近,因此編譯步驟相對簡單。事實上,并不需要進行任何優(yōu)化。就像C語言編譯器那樣,Java編譯器檢查數據類型并且為每種數據類型生成正確的操作。Java程序最終將轉化成這些字節(jié)碼的二進制形式。
圖片來自:《計算機組成與設計(ARM版)》英文版
一種稱為Java虛擬機(Java Virtual Machine,JVM)的軟件解釋器能夠執(zhí)行Java字節(jié)碼。解釋器是一個用來模擬一種指令集體系結構的程序。例如,這門書提供下載的DS-5,ARMv8模擬器就是一種解釋器。由于翻譯非常簡單,地址可以由編譯器填寫或在運行時被JVM發(fā)現,因此不需要單獨的匯編步驟。
解釋的優(yōu)勢是可移植性。
解釋的不足是性能較差。 和傳統(tǒng)方式編譯的C程序相比,相差10倍的性能。
為了既能夠保持可移植性又提高執(zhí)行速度,Java發(fā)展的下一階段是實現能夠在程序執(zhí)行同時進行翻譯的編譯器。這種即時編譯器(Just In Time complier,JIT)通過記錄運行的程序來找到“熱點”(“熱點”指程序中運行特別頻繁的代碼塊),然后將它們編譯成Java虛擬機所運行于的宿主機上的本地指令。編譯過的部分被保存起來供下次程序運行時使用,從而使以后每次運行變得更快。
小測驗: 和 翻譯器相比,對Java開發(fā)者來說,解釋器的哪些優(yōu)點是最重要的?
回答: 機器獨立性。即與機器無關。
2.13 綜合實例:C排序程序
2.13.1 swap過程
一個將內存中兩個不同位置的內容進行交換的C過程。
void swap(long long int v[],size_t k){//數組v,位置k //交換 v[k]和v[k+1]long long int tmp;tmp=v[k];v[k]=v[k+1];v[k+1]=tmp; }我們按照以下步驟把該過程從C語言手動翻譯成匯編語言:
1 為swap分配寄存器
在LEGv8中,使用寄存器X0到X7進行參數傳遞。由于swap只有兩個參數v和k,因此可以被分配給寄存器X0和X1.僅剩的另一個變量是tmp,由于swap是一個葉過程,我們將其分配給寄存器X9.
寄存器分配如下表:
| X0 | X1 | X9 |
2 為swap過程體生成代碼
LEGv8存儲地址按字節(jié)編址,因此雙字由8個字節(jié)組成。因此索引k需先乘上8,再與地址相加。忘記連續(xù)的雙字之間的地址相差8而不是1,是用匯編語言編程時常見的錯誤。
因此,第一步通過左移3位來將k乘以8以獲得v[k]的的地址:
LSL X10,X1,#3 // X10=K*8 ADD X10,X10,X0 // X10=V+K*8接下來根據X10取出v[k]的值,并將X10加8得到v[k+1]:(使用寄存器X9和X11)
LDUR X9,[X10,#0] //X9=V[K] LDUR X11,[X10,#8] //X10=V[K+1]最后將X9和X11存儲到需要交換數據的地址中
STUR X11,[X10,#0]// 交換 STUR X9,[X10,#8]至此,我們已經為該過程分配了寄存器并獲得了實現交換操作的代碼。保存swap中使用的保存寄存器的代碼并沒有包括在其中。但由于我們并不使用葉過程(這里swap是一個葉過程)中的保留寄存器,因此沒有需要保留的東西。
3 完整的swap過程
現在得到完整的例程,包括過程標簽以及返回的跳轉指令。
swap:LSL X10,X1,#3 //k*8ADD X10,X10,X0//得到v[k]LDUR X9,[X10,#0] //取出到寄存器LDUR X11,[X10,#8]STUR X9,[X10,#8]//交換,存儲到存儲器STUR X11,[X10,#0]BR LR //過程返回2.13.2 sort過程
該例中,我們將編寫一個調用swap過程的例程,使用冒泡排序算法(這種排序算法雖然不是最快的,但確是最簡單的)對數組中的整數進行排序。
一個對數組v中元素進行排序的C過程(冒泡排序):
1 為sort分配寄存器
寄存器X0和X1分配給過程sort的兩個參數v和n,寄存器X19和X20分配給變量i和j。
寄存器分配如下表:
| X0 | X1 | X19 | X20 |
2 為sort過程體生成代碼
過程體包含兩個嵌套的for循環(huán)和一個帶參數的swap過程。下面由外向內來展開代碼。
第一步翻譯第一個for循環(huán)
for(i=0;i<n;i++){C語言中的for語句有三個部分:初始化、循環(huán)條件判斷和迭代遞增。將i初始化為0只需要一條指令,故for循環(huán)的第一部分為:
MOV X19,XZR //i=0(注意:MOV是為了方便匯編程序員編程而由匯編器提供的偽指令)。將i遞增同樣也只需要一條指令實現,因此for語句的最后部分為:
ADDI X19,X19,#1 //i+=1當i<n非真時需要退出循環(huán),換句話說,也就是i≥n時退出循環(huán)。循環(huán)條件判斷需要兩條指令:
for1tst: CMP X19,X1 //比較X19和X1(也就是i和n)B.GE exit1 //如果(i≥n)則跳轉 到exit1循環(huán)最后僅僅跳回循環(huán)條件判斷的地方:
B for1tst // branch to test of outer loop exit1:第一個for循環(huán)的框架代碼如下:
MOV X19,XZR for1tst:CMP X19,X1 //循環(huán)條件判斷B.GE exit1...(body of first for loop)...ADDI X19,X19,#1//i+=1B for1tst //回到for1tst exit1:第二個for循環(huán)的C語句如下:
for(j=i-1;j>=0&&v[j]>v[j+1];j--)//從小到大排序這個循環(huán)的初始化部分仍然只需要一條指令:
SUBI X20,X10,#1 //j=i-1循環(huán)末尾j的遞減也只需一條指令:
SUBI X20,X20,#1//J-=1;循環(huán)條件測試由兩部分組成,任何一個條件為假就退出循環(huán)。因此,如果第一個條件測試為假(j<0),循環(huán)就要退出:
for2tst:CMP X20,XZRB.LT exit2 //if (j<0) go to exit2這條跳轉指令將跳過第二個條件測試。如果沒有跳過,則j≥0.
下面來看第二個條件,當v[j]≤v[j+1]時為假。
獲取v[j]
現在取出v[j] (放在寄存器X12中):
LDUR X12,[X11,#0]因為第二個元素恰好是順序下一個雙字,因此將寄存器X11中的地址值加8就可以取出v[j+1] (存放在X13中):
LDUR X13,[X11,#8]測試v[j]≤v[j+1],以判斷是否跳出循環(huán):
CMP X12,X13 B.LE exit2 //小于等于則跳轉循環(huán)末尾跳轉到內存循環(huán)測試處:
B for2tst將這些代碼片組合起來就可以得到第二個for循環(huán)的框架:
SUBI X20,X19,#1 //J=I-1 for2tst:CMP X20,XZR // J AND 0 進行比較B.LT exit2LSL X10,X20,#3ADD X11,X0,X10// X11存放v[j]LDUR X12,[X11,#0] LDUR X13,[X11,#8]CMP X12,X13B.LE exit2 //小于等于則跳轉...(body of second for loop)...SUBI X20,X20,#1//J-=1;B for2tstexit2:3 sort 中的過程調用
下一步處理第二個for循壞體:
swap(v,j);調用swap足夠簡單(一條BL指令即可實現):
BL swap4 sort中的參數傳遞
當我們想傳遞參數時問題出現了,因為sort過程需要使用寄存器X0和X1中的值,而swap過程需要將其參數放入相同的寄存器中。一種解決辦法是在過程執(zhí)行的早期就將sort的參數復制到其他寄存器中,讓出X0和X1寄存器供swap過程使用。(這種復制要比使用棧進行保存和恢復快得多。)在過程中,首先將寄存器X0和X1的值用如下方法復制到X21和X22中:
MOV X21,X0 // COPY PARAMETER X0 INTO X21 MOVE X22,X1 //COPY PARAMETER X1 INTO X22然后用下面兩條指令將參數傳遞給swap:
MOV X0,X21 // swap的第一個參數v MOV X1,X20//swap的第二個參數jswap(v,j);
5 在sort中保存寄存器
剩下的代碼保存和恢復寄存器的值。顯然,我們必須將返回地址保存在寄存器LR中,因為sort是一個過程并且本身也被調用。sort過程還使用了由被調用者保存的寄存器X19,X20,X21和X22,這些寄存器的值也必須被保存。因此,sort過程開頭的代碼如下:
SUBI SP,SP,#40 // MAKE ROOM ON STACK FOR 5 REGS STUR LR,[SP,#32] //SAVE LR ON STACK STUR X22,[SP,#24] // SAVE X22 STUR X21,[SP,#16] //SAVE X21 STUR X20,[SP,#8] //SAVE X20 STUR X19,[SP,#0] //SAVE X19過程末尾只需要簡單地反向執(zhí)行這些指令,最后加入一條BR指令以實現返回。
6 完整的sort過程
注意for循環(huán)中對寄存器X0和X1的引用被替換成對寄存器X21和X22的引用。
//保存寄存器的值 sort: SUBI SP,SP,#40 // MAKE ROOM ON STACK FOR 5 REGSSTUR LR,[SP,#32] //SAVE LR ON STACKSTUR X22,[SP,#24] // SAVE X22STUR X21,[SP,#16] //SAVE X21STUR X20,[SP,#8] //SAVE X20STUR X19,[SP,#0] //SAVE X19 //過程體//傳送參數MOV X21,X0 // COPY PARAMETER X0 INTO X21MOVE X22,X1 //COPY PARAMETER X1 INTO X22//外部循環(huán)SUBI X20,X19,#1 //J=I-1 for2tst:CMP X20,XZR // J AND 0 進行比較B.LT exit2LSL X10,X20,#3ADD X11,X0,X10// X11存放v[j]LDUR X12,[X11,#0] LDUR X13,[X11,#8]CMP X12,X13B.LE exit2 //小于等于則跳轉//傳遞參數和調用MOV X0,X21 // swap的第一個參數vMOV X1,X20//swap的第二個參數jBL swap//內部循環(huán)SUBI X20,X20,#1//J-=1;B for2tst//外部循環(huán) exit2: ADDI X19,X19,#1B for1tst//恢復寄存器的值 exit1: STUR X19,[SP,#0] //SAVE X19STUR X20,[SP,#8] //SAVE X20STUR X21,[SP,#16] //SAVE X21STUR X22,[SP,#24] // SAVE X22STUR LR,[SP,#32] //SAVE LR ON STACKSUBI SP,SP,#40 // MAKE ROOM ON STACK FOR 5 REGS//過程返回BR LR //RETURN TO CALLING ROUTINE 《新程序員》:云原生和全面數字化實踐50位技術專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的《计算机组成与设计(ARM版)》读书笔记-第二章指令2的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2020年汤家凤直播讲解1800题基础篇
- 下一篇: 2020年李永乐线性代数强化笔记-行列式