C語言執行時,由彙編來提供條件,主要是需要棧。
C語言與棧的關係:C語言的區域性變數是用棧來實現的。如果彙編部分沒有給C部分預設合理合法的棧地址,那麼C程式碼定義的區域性變數就會落空,整個程式就會崩潰。
我們平時在編寫微控制器程式(譬如51微控制器)或者編寫應用程式時並沒有去設定棧,但是C程式還是可以執行的。 原因是:在微控制器中由硬體初始化時提供了一個預設可用的棧,在應用程式中我們編寫的C程式其實並不是全部,編譯器(gcc)在鏈接的時候會幫我們自動新增一個頭,這個頭就是一段引導我們的C程式能夠執行的一段彙編實現的程式碼,這個程式碼中就幫我們的C程式設定了棧及其他的執行時需要。
在ARM中37個暫存器中,每種模式下都有自己的獨立的SP暫存器(r13)
r13(sp)主要用來設定爲棧。
如果各種模式都使用同一個SP,那麼就意味着整個程式(操作系統內核程式、使用者自己編寫的應用程式)都是用一個棧的。你的應用程式如果一旦出錯(譬如棧溢位),就會連累操作系統的棧也損壞,整個操作系統的程式就會崩潰。這樣的操作系統設計是非常脆弱的,不合理的。
解決方案就是各種模式下用不同的棧。我的操作系統內核使用自己的棧,每個應用程式也使用自己獨立的棧,這樣各是各的,一個損壞不會連累其他人。
我們現在要設定棧,不可能也懶的而且也沒有必要去設定所有的棧,我們先要找到自己的模式,然後設定自己的模式下的棧到合理合法的位置,即可。
我們現在要設定的模式是SVN模式(可以檢視之前的文章嵌入式-ARM-學習總結(1):初識ARM)
棧必須是當前一段可用的記憶體,可用的意思是這個地方必須有被初始化過的記憶體,而且這個記憶體只會被我們用作棧,不會被其他程式徵用。
當前CPU剛復位(啓動),外部的DRRAM還沒有初始化,目前可用的記憶體只有記憶體的SRAM(因爲他不需要初始化就可以使用)。因此我們只能在SRAM中找一段記憶體來當作SVC的棧。
棧有四種:滿減棧,滿增棧,空減棧 空增棧
滿棧:進棧:先移動指針再存;出棧:先出數據再移動指針
空棧: 進棧:先存再移動指針;出棧:先移動指針再出數據
減棧: 進棧:指針向下移動; 出棧:指針向上移動
增棧: 進棧:指針向上移動; 出棧:指針向下移動
在ARM中,ATPCS要求使用滿減棧,查詢數據手冊可得:
SVC的棧地址從0xD0037780到0xD0037D80.
#define WTCON 0xE2700000 //這裏是開關看門狗的地址
#define SVC_STACK 0xd0037d80 //從SVC棧的高位開始寫
.global _start // 把_start鏈接屬性改爲外部,這樣其他檔案就可以看見_start了
_start:
// 第1步:關看門狗(向WTCON的bit5寫入0即可)
ldr r0, =WTCON
ldr r1, =0x0
str r1, [r0]
// 第2步:設定SVC棧
ldr sp, =SVC_STACK
……從這裏之後就可以開始呼叫C程式了
在工程中新建並且新增一個C語言原始檔(以.c結尾的檔案,這裏面是程式執行的程式碼)。在彙編啓動程式碼中設定好棧後,使用bl xxx的方式來呼叫C中的函數xxx。注意要修改Makefile,使其包括編譯C檔案的部分。
(1)彙編程式
#define WTCON 0xE2700000
#define SVC_STACK 0xd0037d80
.global _start // 把_start鏈接屬性改爲外部,這樣其他檔案就可以看見_start了
_start:
// 第1步:關看門狗(向WTCON的bit5寫入0即可)
ldr r0, =WTCON
ldr r1, =0x0
str r1, [r0]
// 第2步:設定SVC棧
ldr sp, =SVC_STACK
// 從這裏之後就可以開始呼叫C程式了
bl led_blink // led_blink是C語言實現的一個函數
// 彙編最後的這個死回圈不能丟
b .
bl指令呼叫led_blink函數,並用C程式編寫led_blink函數內部執行內容。
(2)C語言程式
#define GPJ0CON 0xE0200240
#define GPJ0DAT 0xE0200244
void delay(void);
// 該函數要實現led閃爍效果
void led_blink(void)
{
// led初始化,也就是把GPJ0CON中設定爲輸出模式
unsigned int *p = (unsigned int *)GPJ0CON;
unsigned int *p1 = (unsigned int *)GPJ0DAT;
*p = 0x11111111;
while (1)
{
// led亮
*p1 = ((0<<3) | (0<<4) | (0<<5));
// 延時
delay();
// led滅
*p1 = ((1<<3) | (1<<4) | (1<<5));
// 延時
delay();
}
}
void delay(void)
{
volatile unsigned int i = 900000; // volatile 讓編譯器不要優化,這樣才能 纔能真正的減
while (i--); // 才能 纔能消耗時間,實現delay
}
(3)Makefile檔案
led.bin: start.o led.o //這裏需要彙編生成的.o檔案和C生成的.o檔案都體現出來
arm-linux-ld -Ttext 0x0 -o led.elf $^
arm-linux-objcopy -O binary led.elf led.bin
arm-linux-objdump -D led.elf > led_elf.dis
gcc mkv210_image.c -o mkx210
./mkx210 led.bin 210.bin
%.o : %.S
arm-linux-gcc -o $@ $< -c -nostdlib //nostdlib就是不使用標準函數庫。
%.o : %.c
arm-linux-gcc -o $@ $< -c -nostdlib
clean:
rm *.o *.elf *.bin *.dis mkx210 -f
如果有問題,歡迎指出討論。 一人一