天天看點

彙編寫啟動代碼之設定棧和調用C語言

彙編寫啟動代碼之設定棧和調用C語言

                                                                                         --參考朱老師ARM裸機程式設計

1、為什麼彙編寫啟動代碼之設定棧和調用C語言

(1) C語言運作時:(環境runtime)需要一定的環境和條件,這些環境由彙編來提供。

有些硬體已經預設内部提供了C語言的環境來運作。

我們自己來提供一個C語言運作時環境,主要是需要棧。

C語言中的局部變量都是存放在棧裡面的。

如果我們彙編部分沒有給C部分預先設定合理的合法的棧的位址的話,

那麼C代碼中定義的局部變量就會落空。整個程式就會死掉。

(2) 我們平時在編寫單片機程式(比如51單片機)或者

編寫應用程式時并沒有去設定棧,但是C程式還是可以運作的

原因是:在單片機中由硬體初始化時提供了一個預設可以使用的棧。

在應用程式中我們編寫的C程式其實并不是全部,編譯器(gcc)

在連結的時候會幫我們添加一個頭,這個頭就是一段引導我們的C程式。

執行的彙編實作的代碼,這個代碼中就幫我們設定了棧以及其他的運作時需要。

(3)CPU模式和各種模式下面的棧

棧在什麼地方設定的?

其實就是指SP指針指向的内容。

ARM的37個寄存器中,每種模式下面都有自己獨立的SP寄存器(r13)

2、為什麼要這麼設定?

這樣設定的話,不會耗費很多有用的記憶體嗎?

但是如果所有的模式都使用同一個SP,那麼就意味着整個程式(作業系統核心程式

使用者自己編寫的應用程式)都是使用一個棧的,那麼整個程式都是一個棧。

你的應用程式一旦出錯,比如棧溢出。

在程式裡面如果使用遞歸的話,你的應用程式一旦出錯的話,

就會連累作業系統的棧也會損壞,整個程式都是用一個棧,

你的應用程式一旦出錯,就會連累作業系統的棧也會損壞,

整個作業系統的程式就會奔潰。

這樣的作業系統設計是非常脆弱的,不合理的。

解決方案就是各種模式下面使用不同的棧。

這樣我的作業系統核心使用自己的棧,每個應用程式也是用自己獨立的棧。

這樣各是各的,一個損壞不會連累到其他的地方。

雖然我們現在要設定棧,也沒有必要設定所有的棧。

我們先要找到自己的模式,然後設定自己的模式下的棧道合理合法。

我們目前處于SVC模式下面,系統在複位後預設都是進入SVC模式的,

我們怎麼去通路SVC模式下面的棧呢?很簡單,先把模式設定為SVC。

再直接設定為SVC模式的。

3、查閱文檔并設定棧指針至合法位置

棧必須是目前一段可用的記憶體(可用的意思就是這個地方必須由CPU

初始化可以通路的記憶體,而且這個記憶體隻會被我們用作棧,不會被其他程式征用)

目前CPU剛複位(剛啟動)外部的DRAM尚未初始化,目前可用的記憶體隻有内部的SRAM。

在SRAM裡面找一段記憶體來作為SVC的棧。

他要求你的BL1隻有16K

這96K怎麼配置設定别人已經配置設定好了,主要是0xd0037780還有0xd0037D80這兩個位址。

在ARM中的ATPCS要求使用滿減棧,ARM關于程式應該程式怎麼實作的規範。

是以我們一般是使用滿減棧。

4、使用C語言來通路寄存器的文法

寄存器的位址類似于記憶體位址(IO與記憶體統一編址)

是以這裡的問題使用C語言讀寫寄存器。

就是使用C語言來讀寫記憶體位址,用C語言來通路記憶體,就是要用指針

volatile unsigned int *p = (unsigned int *)0xE0200240;

因為你是寄存器通路的尋址,是以寄存器的數是經常的變化的,

是以需要volatile來變化。

5、編譯報錯(實際上是連接配接的階段報錯)

undefinded

直接去搜尋到的内容一個一個看一個一個嘗試。

遇到這個問題的時候,加上-nostdlib選項即可。

就是不使用标準的函數庫。

标準函數庫就是我們編譯器中的自帶的函數庫。

可以讓編譯器和連結器優先選擇我程式。

我們就要用我們自己的。

彙編和C語言的聯合調式方法。

這根編譯器是有關的,好幾年前做過這個東西。

volatile的作用是:程式在編譯時,編譯器不對程式做優化。

優化有時候是OK的,但是有時候是自作聰明的,造成程式的不對。

如果你的一個變量是易變的,不希望編譯器幫我們做優化,

就在這個變量定義時加volatile。

加不加有沒有差别,取決于編譯器,如果編譯器做了優化則有差異。

如果編譯器本身沒有做優化,那麼就沒有差别。

在我們這裡,arm-2009q3,實際測試加不加效果是一樣的。

volatile加上是肯定是對的,但是效率會有下降。

一般人家寫裸機程式都是這麼寫寄存器的,用來在寄存器寫東西

#define rGPJ0CON  *((volatile unsigned int *)GPJ0CON)

#define rGPJ0DAT  *((volatile unsigned int *)GPJ0DAT)

#define WTCON  0xE2700000
#define SVC_STACK 0xd0037d80

//彙編語言的初始化的語言
.global _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       //這是C 語言實作的一個函數
	
	//最後彙編的這個死循環不能丢掉
	b .
	
	
//led.c
#define GPJ0CON  0xE0200240
#define GPJ0DAT  0xE0200244

void delay(void);

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;
   while(i--);
}
           

繼續閱讀