arm-linux-gcc编译器定义寄存器变量
uboot代碼中有這么一句話“#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")”,困擾了山人多時(shí)。經(jīng)過(guò)多番求索,才得知原來(lái)是定義了一個(gè)全局的寄存器變量gd_t(r8是它的專用寄存器)。
詳細(xì)解釋一下,register意思是定義的變量保存在寄存器中,volatile代表禁止編譯器優(yōu)化,后邊的asm ("r8")說(shuō)的是使用的寄存器是r8。
一、何謂寄存器變量
百度百科
在程序運(yùn)行時(shí),根據(jù)需要到內(nèi)存中相應(yīng)的存儲(chǔ)單元中調(diào)用,如果一個(gè)變量在程序中頻繁使用,例如循環(huán)變量,那么,系統(tǒng)就必須多次訪問(wèn)內(nèi)存中的該單元,影響程序的執(zhí)行效率。因此,CC++語(yǔ)言還定義了一種變量,不是保存在內(nèi)存上,而是直接存儲(chǔ)在CPU中的寄存器中,這種變量稱為寄存器變量。
山人認(rèn)為寄存器變量的優(yōu)勢(shì)的確是提高了變量的訪問(wèn)速度,也犧牲了寶貴的寄存器資源。在學(xué)譚浩強(qiáng)的C語(yǔ)言時(shí),書(shū)中有關(guān)寄存器變量的介紹。
二、APCS 簡(jiǎn)介
本文講的是寄存器變量的事,看似與APCS無(wú)關(guān)。但是,實(shí)質(zhì)上APCS涉及了寄存器是怎樣被使用的,也就關(guān)寄存器變量的事了。所以,還是需要先介紹APCS。以下文字摘錄于“arm指令集”。
APCS,ARM 過(guò)程調(diào)用標(biāo)準(zhǔn)(ARM Procedure Call Standard),提供了緊湊的編寫(xiě)例程的一種機(jī)制,定義的例程可以與其他例程交織在一起。最顯著的一點(diǎn)是對(duì)這些例程來(lái)自哪里沒(méi)有明確的限制。它們可以編譯自 C、 Pascal、也可以是用匯編語(yǔ)言寫(xiě)成的。
APCS 定義了:
對(duì)寄存器使用的限制。
使用棧的慣例。
在函數(shù)調(diào)用之間傳遞/返回參數(shù)。
可以被‘回溯’的基于棧的結(jié)構(gòu)的格式,用來(lái)提供從失敗點(diǎn)到程序入口的函數(shù)(和給予的參數(shù))的列表。
APCS 不一個(gè)單一的給定標(biāo)準(zhǔn),而是一系列類似但在特定條件下有所區(qū)別的標(biāo)準(zhǔn)。例如,APCS-R (用于 RISC OS)規(guī)定在函數(shù)進(jìn)入時(shí)設(shè)置的標(biāo)志必須在函數(shù)退出時(shí)復(fù)位。在 32 位標(biāo)準(zhǔn)下,并不是總能知道進(jìn)入標(biāo)志的(沒(méi)有 USR_CPSR),所以你不需要恢復(fù)它們。如你所預(yù)料的那樣,在不同版本間沒(méi)有相容性。希望恢復(fù)標(biāo)志的代碼在它們未被恢復(fù)的時(shí)候可能會(huì)表現(xiàn)失常...
如果你開(kāi)發(fā)一個(gè)基于 ARM 的系統(tǒng),不要求你去實(shí)現(xiàn) APCS。但建議你實(shí)現(xiàn)它,因?yàn)樗浑y實(shí)現(xiàn),且可以使你獲得各種利益。但是,如果要寫(xiě)用來(lái)與編譯后的 C 連接的匯編代碼,則必須使用 APCS。編譯器期望特定的條件,在你的加入(add-in)代碼中必須得到滿足。一個(gè)好例子是 APCS 定義 a1 到 a4 可以被破壞,而 v1 到 v6 必須被保護(hù)。現(xiàn)在我確信你正在撓頭并自言自語(yǔ)“a 是什么? v 是什么?”。所以首先介紹 APCS-R 寄存器定義...
ARM寄存器
| Reg# | APCS | 意義 | 
| R0 | a1 | 工作寄存器 | 
| R1 | a2 | ·· | 
| R2 | a3 | ·· | 
| R3 | a4 | ·· | 
| R4 | v1 | 必須保護(hù) | 
| R5 | v2 | ·· | 
| R6 | v3 | ·· | 
| R7 | v4 | ·· | 
| R8 | v5 | ·· | 
| R9 | v6 | ·· | 
| R10 | sl | 棧限制 | 
| R11 | fp | 幀指針 | 
| R12 | ip | |
| R13 | sp | 棧指針 | 
| R14 | lr | 連接寄存器 | 
| R15 | pc | 程序計(jì)數(shù)器 | 
筆者實(shí)驗(yàn)總結(jié)
通過(guò)反匯編查看C語(yǔ)言的代碼,可以知道r0-r3用作形參傳遞參數(shù),倘若形參超過(guò)4個(gè),則需要用堆棧來(lái)傳遞其余的參數(shù)。r0-r3在函數(shù)中間是作為中間變量的,在退出函數(shù)之后,其值也不用特別處理(試想形參也沒(méi)有返回值,而中間變量是暫存數(shù)據(jù),也不需要先保存再?gòu)棾觯S纱耍陆Y(jié)論r0-r3是可以被破壞的。
r4-r14更像是全局變量,因?yàn)榧僭O(shè)在函數(shù)中因?yàn)橹虚g變量不夠用而使用了r4-r12中的一個(gè)或者某幾個(gè),再進(jìn)入函數(shù)時(shí)將用到的寄存器保存,出去的時(shí)候再?gòu)棾觯詒4-r12在經(jīng)過(guò)函數(shù)之后是保持不變的。
三、如何在程序中實(shí)現(xiàn)定義寄存器變量
以實(shí)驗(yàn)來(lái)說(shuō)明定義寄存器變量的方法
head.s
1 .text 2 .global _start 3 _start: 4 ldr sp, =4096 5 bl init 6 bl main 7 halt_loop: 8 b halt_loop
init.c
1 register volatile int* gd_t asm("r8");  
2 void init()
3 {
4     gd_t    = (int *)0;
5 }
main.c
1 register volatile int* gd_t asm ("r8");
2 
3 int main(void)
4 {
5     gd_t = (int* )1;
6     while(1);
7     return 0;
8 }
Makefile
objs := head.o init.o main.o
test.bin : $(objs)
    arm-linux-ld -Ttext 0x0    -o test_elf $^
    arm-linux-objcopy -O binary -S test_elf $@
    arm-linux-objdump -D -m arm  test_elf > test.dis
%.o:%.c
    arm-linux-gcc -Wall -c -O2 -o $@ $<
%.o:%.S
    arm-linux-gcc -Wall -c -O2 -o $@ $<
clean:
    rm -f  test.dis test.bin test_elf *.o
查看反匯編內(nèi)容
test.dis
 1 test_elf:     file format elf32-littlearm
 2 
 3 Disassembly of section .text:
 4 
 5 00000000 <_start>:
 6    0:    e3a0da01     mov    sp, #4096    ; 0x1000
 7    4:    eb000001     bl    10 <init>
 8    8:    eb000002     bl    18 <main>
 9 
10 0000000c <halt_loop>:
11    c:    eafffffe     b    c <halt_loop>
12 
13 00000010 <init>:
14   10:    e3a09000     mov    r8, #0    ; 0x0
15   14:    e1a0f00e     mov    pc, lr
16 
17 00000018 <main>:
18   18:    e3a08001     mov    r8, #1    ; 0x1
19   1c:    eafffffe     b    1c <main+0x4>
20 Disassembly of section .comment:
21 
22 00000000 <.comment>:
23    0:    43434700     cmpmi    r3, #0    ; 0x0
24    4:    4728203a     undefined
25    8:    2029554e     eorcs    r5, r9, lr, asr #10
26    c:    2e342e33     mrccs    14, 1, r2, cr4, cr3, {1}
27   10:    47000035     smladxmi    r0, r5, r0, r0
28   14:    203a4343     eorcss    r4, sl, r3, asr #6
29   18:    554e4728     strplb    r4, [lr, #-1832]
30   1c:    2e332029     cdpcs    0, 3, cr2, cr3, cr9, {1}
31   20:    00352e34     eoreqs    r2, r5, r4, lsr lr
寄存器全局變量與普通的全局變量使用區(qū)別
普通的全局變量使用是在一個(gè)文件中定義全局變量,然后通過(guò)“extern”將其作用域擴(kuò)展到其他的文件;寄存器的全局變量是每一個(gè)使用這個(gè)全局變量的文件都要對(duì)這個(gè)全局變量定義。
為什么有這樣的區(qū)別呢
普通全局變量在一個(gè)文件被定義后,就會(huì)在相應(yīng)的數(shù)據(jù)段(data或者bss)開(kāi)辟一個(gè)空間,這個(gè)空間編譯器會(huì)給它命一個(gè)名字。例如“int a=10;”就會(huì)在data段開(kāi)辟一個(gè)4字節(jié)的空間,并且編譯器給這個(gè)空間命名為a。其它文件也需要調(diào)用這個(gè)全局變量時(shí),是對(duì)這個(gè)變量進(jìn)行聲明,例如“extern int a;”。在連接程序階段,連接器會(huì)把所有使用a變量的代碼都定位在之前定義時(shí)在data段開(kāi)辟命名為a的空間。
寄存器全局變量,它的存儲(chǔ)空間已經(jīng)確定。當(dāng)對(duì)寄存器全局變量的定義進(jìn)行編譯處理時(shí),不會(huì)試圖從data或者bss開(kāi)辟空間,而是直接使用寄存器(例如實(shí)驗(yàn)程序中,直接使用r8來(lái)處理gd_t)。因?yàn)椴淮嬖诰幾g器產(chǎn)生連接時(shí)所需要有關(guān)全局變量的標(biāo)號(hào)(編譯器不會(huì)產(chǎn)生gd_t的標(biāo)號(hào)),所以實(shí)際上也不存在“哪一個(gè)文件是定義,哪一個(gè)文件是聲明的問(wèn)題了”。
當(dāng)init.c文件使用了寄存器全局變量gd_t,它在編譯時(shí)被r8替換。當(dāng)main.c也使用了gd_t,它也被r8替換。實(shí)際上,在連接過(guò)程中已經(jīng)沒(méi)有處理gd_t的任務(wù)。這與普通全局變量的編譯、連接是有本質(zhì)的區(qū)別。
為了體現(xiàn)這種巨大差異,再做一個(gè)實(shí)驗(yàn)修改init.c的內(nèi)容,其他保持不變。
init.c
1 register volatile int* gd_t asm("r9");  
2 void init()
3 {
4     gd_t    = (int *)0;
5 }
對(duì)應(yīng)的反匯編代碼
test.dis
 1 test_elf:     file format elf32-littlearm
 2 
 3 Disassembly of section .text:
 4 
 5 00000000 <_start>:
 6    0:    e3a0da01     mov    sp, #4096    ; 0x1000
 7    4:    eb000001     bl    10 <init>
 8    8:    eb000002     bl    18 <main>
 9 
10 0000000c <halt_loop>:
11    c:    eafffffe     b    c <halt_loop>
12 
13 00000010 <init>:
14   10:    e3a09000     mov    r9, #0    ; 0x0
15   14:    e1a0f00e     mov    pc, lr
16 
17 00000018 <main>:
18   18:    e3a08001     mov    r8, #1    ; 0x1
19   1c:    eafffffe     b    1c <main+0x4>
20 Disassembly of section .comment:
21 
22 00000000 <.comment>:
23    0:    43434700     cmpmi    r3, #0    ; 0x0
24    4:    4728203a     undefined
25    8:    2029554e     eorcs    r5, r9, lr, asr #10
26    c:    2e342e33     mrccs    14, 1, r2, cr4, cr3, {1}
27   10:    47000035     smladxmi    r0, r5, r0, r0
28   14:    203a4343     eorcss    r4, sl, r3, asr #6
29   18:    554e4728     strplb    r4, [lr, #-1832]
30   1c:    2e332029     cdpcs    0, 3, cr2, cr3, cr9, {1}
31   20:    00352e34     eoreqs    r2, r5, r4, lsr lr
可以看到編譯能夠正常通過(guò),也就是說(shuō)gd_t在不同的文件中可以使用不同的寄存器,因?yàn)樵谶B接過(guò)程中沒(méi)有g(shù)d_t處理的任務(wù)(它們都被寄存器所代替)。
四、使用全局寄存器變量需要注意的地方
1、每一個(gè)使用這個(gè)全局變量的文件都要對(duì)這個(gè)全局變量定義,而且要一樣
倘若像修改后的代碼一樣,同一個(gè)全局變量gd_t卻在不同的文件中使用了不同的寄存器,結(jié)果不能實(shí)現(xiàn)全局變量的效果。
2、確保匯編程序中沒(méi)有隨意修改所使用寄存器的值
寄存器全局變量有可能會(huì)在匯編程序中被破壞,可能因?yàn)榧拇嫫鞑粔蛴没蛘咧袛喾?wù)。要做的任務(wù)就是,使用前對(duì)其保存和退出使用后對(duì)其恢復(fù)。
3、不能使用r0-r3作為寄存器全局變量
因?yàn)樗鼈冏駨腁PCS規(guī)則,會(huì)被經(jīng)常破壞,不能當(dāng)做寄存器全局變量來(lái)用。而r4-r12的特性適合全局變量來(lái)使用。
后記
本文為筆者實(shí)驗(yàn)以及查找資料所得結(jié)論,可能有謬誤,望讀者發(fā)現(xiàn)指正。
總結(jié)
以上是生活随笔為你收集整理的arm-linux-gcc编译器定义寄存器变量的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
 
                            
                        - 上一篇: 如何修改微软share point si
- 下一篇: SAP Spartacus 服务器端渲染
