原文網(wǎng)址:http://nieyong.github.com/wiki_cpu/
在看過(guò)了上面的幾節(jié)之后,在潛意識(shí)中你想記住的東西肯定很多了,。這個(gè)時(shí)候,,你需要靜下心來(lái)休息一下在沉淀一下。 "Now is a good point to take a break to let this information sink in." 下面,,我們就看看C語(yǔ)言撰寫的程序,,在不同的CPU架構(gòu)下,生成的匯編語(yǔ)言是怎么樣的,,各有什么特點(diǎn),,這和前面介紹的各種CPU架構(gòu)的知識(shí)是如何聯(lián)系的。如果你覺(jué)得還不夠,,也很高興一起來(lái)探討一下在不同的CPU架構(gòu)下,,函數(shù)的調(diào)用時(shí)如何實(shí)現(xiàn)的,以及各有什么特點(diǎn),。 反匯編文件測(cè)試用的C源碼如下,,main.c文件:
下面為編譯以及反匯編的過(guò)程。當(dāng)然,,ARM和MIPS的編譯和反匯編是使用的交叉編譯工具鏈,。例如在我的平臺(tái)下,對(duì)應(yīng)的命令就是ccarm,,objdumparm和ccmips,,objdumpmips。 #gcc -o main.o -c main.c #objdump -d main.o>main.a 編譯成x86下面的main.o文件,,注意,,沒(méi)有鏈接,。然后反匯編為main_x86.a如下所示。這里需要指出的是,,下面的匯編語(yǔ)言并不是我們?cè)凇段C(jī)原理》課本上學(xué)習(xí)到的x86的匯編(我們稱為intel匯編),,而叫做AT&T匯編。那么,,什么是AT&T匯編呢,? 在將Unix移植到80386處理器上時(shí),Unix圈內(nèi)人士根據(jù)Unix領(lǐng)域的習(xí)慣和需要而定義了AT&T匯編,。而GNU主要是在Unix領(lǐng)域活動(dòng),,因此GNU開(kāi)發(fā)的各種系統(tǒng)工具繼承了AT&T的386匯編格式,這也是我們使用GNU工具進(jìn)行反匯編的時(shí)候,,看到的匯編語(yǔ)言,。
編譯成mips下面的main.o文件,沒(méi)有鏈接,。然后反匯編為main_mips.a:
編譯成arm下的main.o,,沒(méi)有鏈接。然后反匯編為main_arm.a:
不同CPU架構(gòu)下匯編語(yǔ)言的特點(diǎn)代碼長(zhǎng)度首先,,我們看同一個(gè)C語(yǔ)言程序,,在不同的處理器下,代碼長(zhǎng)度,。代碼長(zhǎng)度為反匯編文件的第一列,,可以看到,在x86下是0x4c+1個(gè)字節(jié),,在MIPS下是0x84+4個(gè)字節(jié),,在ARM下是0x78+4個(gè)字節(jié)。為什么這樣呢,? 首先看的是CISC和RISC的區(qū)別 CISC架構(gòu)下,,指令的長(zhǎng)度不是固定的,而RISC為了實(shí)現(xiàn)流水線,,每條指令的長(zhǎng)度都是固定的,。看到反匯編文件的第二列,,x86的指令中,,最短為一個(gè)字節(jié),例如push,,pop,,ret等指令,最長(zhǎng)為7個(gè)字節(jié),,例如movl指令,。而對(duì)于ARM和MIPS,,所有的指令長(zhǎng)度都固定為4個(gè)字節(jié)。這也是ARM和MIPS的機(jī)器代碼長(zhǎng)度要明顯比x86長(zhǎng)的原因,。 另外一個(gè)原因,,就是CISC可能為某個(gè)特殊操作實(shí)現(xiàn)了一條指令,而RISC處理器則需要用多條指令組合來(lái)完成該操作,。最明顯的就是出入棧操作。 然后我們看MIPS和ARM的區(qū)別 ARM的代碼長(zhǎng)度比MIPS代碼長(zhǎng)度稍稍小,,這也驗(yàn)證了人們常說(shuō)的MIPS是純粹的RISC架構(gòu),,而ARM則在RISC的基礎(chǔ)上吸收了CISC中的某些優(yōu)點(diǎn)。我們還是要看看,,ARM是因?yàn)槲樟四男〤ISC的特點(diǎn),,才做到代碼長(zhǎng)度的減少的呢?最明顯的是出入棧,,在ARM中實(shí)現(xiàn)了多寄存器load/store指令,,請(qǐng)注意main_arm.a文件的0x4,0x2c,,0x34,,0x78行的stmdb和ldmdb指令。那么,,這和CISC有什么關(guān)系呢,?仔細(xì)想想,這就是CISC中的為了某個(gè)特殊的操作而實(shí)現(xiàn)一條指令的思想,。另外,,多寄存器的load/store指令破壞了RISC中指令的執(zhí)行周期必須是單周期的規(guī)定,這一點(diǎn),,也是人們指責(zé)ARM不是純粹的RISC架構(gòu)的證據(jù)之一,。 另外還有就是ARM實(shí)現(xiàn)了條件標(biāo)志,實(shí)現(xiàn)條件執(zhí)行,,這樣可以減少分支指令的數(shù)目,,提高代碼的密度。不過(guò)在我們這個(gè)實(shí)例中沒(méi)有這方面的應(yīng)用,。 出棧入棧入棧指將CPU通用寄存器的值放入棧(存儲(chǔ)器)中保存起來(lái),。出棧則是指將棧中的值恢復(fù)到CPU中相應(yīng)通用寄存器。 在x86下面,,有專門的出棧和入棧指令pop和push,。出棧和入棧在棧中的位置是當(dāng)前堆棧指針sp所指向的位置。在MIPS和ARM下面,,沒(méi)有專門的指令,,所以入棧和出棧首先使用的是load/store指令將數(shù)據(jù)放入堆?;蛘邚棾龆褩#缓笫褂胊dd/stub指令修改堆棧指針的值,。比較特殊的是ARM有特殊的多寄存器load/store指令來(lái)快速的完成出入棧,。由此可見(jiàn),在MIPS和ARM下面,,出入棧都是使用幀指針或者堆棧指針的相對(duì)位置,,在執(zhí)行之后,并不會(huì)影響堆棧指針sp的值,。 棧的生長(zhǎng) 棧的生長(zhǎng)指堆棧指針sp的變化,。例如,在函數(shù)調(diào)用的時(shí)候,,需要一次性的分配被調(diào)用函數(shù)的??臻g大小。在x86,,ARM,,MIPS下都是使用的對(duì)堆棧指針寄存器直接加減的辦法。而且,,在MIPS和ARM下,,也只有這種辦法可以改變堆棧指針SP的值。而在x86下,,除了直接加減的辦法之外,,出入棧操作指令pop和push還可以改變sp的值。 不同CPU架構(gòu)下函數(shù)調(diào)用的特點(diǎn)C語(yǔ)言函數(shù)的調(diào)用,,請(qǐng)參考C語(yǔ)言-Stack的相關(guān)內(nèi)容,。涉及堆棧幀(stack frame),活動(dòng)記錄(active record),,調(diào)用慣例(call convention)等相關(guān)概念,。建議參考《程序員的自我修養(yǎng)-鏈接、裝載與庫(kù)》的第10.2節(jié)-棧和調(diào)用慣例,。 毫無(wú)疑問(wèn),,這里都是使用的C語(yǔ)言的默認(rèn)調(diào)用慣例cdecl。cdecl調(diào)用管理的特點(diǎn)如下:
首先我們給出一個(gè)cdecl調(diào)用慣例的模型,,不針對(duì)任何處理器架構(gòu),,然后我們?cè)倏纯床煌奶幚砥骷軜?gòu)下cdecl調(diào)用慣例有什么樣的特點(diǎn)。下圖為cdecl調(diào)用慣例的一般情況示意圖,。 CPU體系架構(gòu)-image/cdel_call_convention.png 下面分析cdecl調(diào)用慣例中,,調(diào)用函數(shù)和被調(diào)用函數(shù)分別負(fù)責(zé)活動(dòng)記錄(堆幀棧)中的什么操作呢,?首先是被調(diào)用函數(shù),在函數(shù)的入口,,需要做以下操作:
上面是被調(diào)用函數(shù)需要負(fù)責(zé)的事情,那么調(diào)用函數(shù)需要負(fù)責(zé)什么呢,?主要有一下幾點(diǎn):
X86的函數(shù)調(diào)用這個(gè)調(diào)用中沒(méi)有調(diào)整堆棧指針的操作,,因?yàn)椴恍枰玫筋~外的堆??臻g。所以,,只有上面提到的第一點(diǎn)和第二點(diǎn),。
MIPS的函數(shù)調(diào)用MIPS的堆棧,在被調(diào)用函數(shù)中有8bytes的升棧操作,,下面的ARM中也是一樣的,。
ARM的函數(shù)調(diào)用相比于MIPS架構(gòu),一條stmdb和ldmdb就可以完成多個(gè)寄存器的存儲(chǔ)(store)和加載(load),。
|
|