嵌入式-ARM-學習總結(3):彙編啓動,設定棧,呼叫C語言

2020-08-11 17:30:31

一、C語言執行時需要和棧的意義

1.棧

C語言執行時,由彙編來提供條件,主要是需要棧

C語言與棧的關係:C語言的區域性變數是用棧來實現的。如果彙編部分沒有給C部分預設合理合法的棧地址,那麼C程式碼定義的區域性變數就會落空,整個程式就會崩潰。

我們平時在編寫微控制器程式(譬如51微控制器)或者編寫應用程式時並沒有去設定棧,但是C程式還是可以執行的。 原因是:在微控制器中由硬體初始化時提供了一個預設可用的棧,在應用程式中我們編寫的C程式其實並不是全部,編譯器(gcc)在鏈接的時候會幫我們自動新增一個頭,這個頭就是一段引導我們的C程式能夠執行的一段彙編實現的程式碼,這個程式碼中就幫我們的C程式設定了棧及其他的執行時需要。

2.CPU模式和各種模式下的棧

在ARM中37個暫存器中,每種模式下都有自己的獨立的SP暫存器(r13)
在这里插入图片描述
r13(sp)主要用來設定爲棧。
如果各種模式都使用同一個SP,那麼就意味着整個程式(操作系統內核程式、使用者自己編寫的應用程式)都是用一個棧的。你的應用程式如果一旦出錯(譬如棧溢位),就會連累操作系統的棧也損壞,整個操作系統的程式就會崩潰。這樣的操作系統設計是非常脆弱的,不合理的。
解決方案就是各種模式下用不同的棧。我的操作系統內核使用自己的棧,每個應用程式也使用自己獨立的棧,這樣各是各的,一個損壞不會連累其他人。
我們現在要設定棧,不可能也懶的而且也沒有必要去設定所有的棧,我們先要找到自己的模式,然後設定自己的模式下的棧到合理合法的位置,即可。
我們現在要設定的模式是SVN模式(可以檢視之前的文章嵌入式-ARM-學習總結(1):初識ARM)

3.查閱文件並設定棧指針至合法位置

棧必須是當前一段可用的記憶體,可用的意思是這個地方必須有被初始化過的記憶體,而且這個記憶體只會被我們用作棧,不會被其他程式徵用。

當前CPU剛復位(啓動),外部的DRRAM還沒有初始化,目前可用的記憶體只有記憶體的SRAM(因爲他不需要初始化就可以使用)。因此我們只能在SRAM中找一段記憶體來當作SVC的棧。

棧有四種:滿減棧,滿增棧,空減棧 空增棧
滿棧:進棧:先移動指針再存;出棧:先出數據再移動指針
空棧: 進棧:先存再移動指針;出棧:先移動指針再出數據
減棧: 進棧:指針向下移動; 出棧:指針向上移動
增棧: 進棧:指針向上移動; 出棧:指針向下移動
在ARM中,ATPCS要求使用滿減棧,查詢數據手冊可得:

在这里插入图片描述
SVC的棧地址從0xD00377800xD0037D80.

#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語言原始檔(以.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

如果有問題,歡迎指出討論。 一人一