本文轉自:http://www.techbulo.com/1347.htmlhtml
在前邊咱們使用匯編完成了一個流水燈實驗: https://my.oschina.net/cht2000/blog/1622224linux
可是,彙編語言可讀性太差,在這一節咱們用 C語言來實現了一樣的功能,而之後的試驗也儘可能用 C語言實現。編程
咱們在編寫上位機程序時,C語言程序執行的第一條指令,並不在main函數中。生成一個 C程序的可執行文件時,編譯器一般會在咱們的代碼中加上幾個被稱爲啓動文件的代碼—— crtl.o 、crti.o、crtend.o 、crtn.o 等,它們是標準庫文件。這些代碼設置C程序的堆棧等,而後調用 main 函數。它們依賴於操做系統,在裸板上這些代碼沒法執行,因此須要本身寫一個。服務器
這段代碼很簡單, 關鍵指令只有2條。本身編寫的 start .S啓動文件內容以下:函數
.text .globl _start _start: ldr sp, =0x02060000 // 調用C函數以前必須設置棧,棧用於保存運行環境,給局部變量分配空間 // 參考ROM手冊P14, 咱們把棧指向BL2上方1K處(1K已經夠用), // 即:0x02020000 (iRAM基地址) + 5K(iROM代碼用) + 8K(BL1用) + 16K(BL2用) + 1K(用做棧)) bl main // 調用main函數(main這個名稱不是固定的,能夠隨意改) halt_loop: b halt_loop
它在第 4行設置好棧指針後,就能夠經過第8行調用C函數 main了-----------C函數執行前,必須設置棧。oop
問:CPU不是有看門狗嘛?爲何沒有看到關看門狗的代碼?這樣程序能正常運行嗎?操作系統
答:在文章《Exynos 4412的啓動過程分析》中,咱們已經介紹過了,在執行咱們的程序前,CPU會首先執行iROM中的代碼和BL1的代碼,在這兩部分程序中會關閉看門狗。.net
其實咱們本身關閉看門狗也很簡單,只需往寄存器WTCON寫入0便可;翻譯
問:爲何調用C函數要設置棧?指針
答:1. 棧的總體做用
2. 詳細解釋
1)保存現場:
現場,意思就至關於案發現場,總有一些現場的狀況,要記錄下來的,不然被別人破壞掉以後,你就沒法恢復現場了。而此處說的現場,就是指CPU運行的時候,用到了一些寄存器,好比r0,r1等等,對於這些寄存器的值,若是你不保存而直接跳轉到子函數中去執行,那麼極可能就被其破壞了,由於其函數執行也要用到這些寄存器。所以,在函數調用以前,應該將這些寄存器等現場,暫時保持起來(入棧push),等調用函數執行完畢返回後(出棧pop),再恢復現場。這樣CPU就能夠正確的繼續執行了。保存寄存器的值,通常用的是push指令,將對應的某些寄存器的值,一個個放到棧中,把對應的值壓入到棧裏面,即所謂的壓棧。而後待被調用的子函數執行完畢的時候,再調用pop,把棧中的一個個的值,賦值給對應的那些你剛開始壓棧時用到的寄存器,把對應的值從棧中彈出去,即所謂的出棧。其中保存的寄存器中,也包括lr的值(由於用bl指令進行跳轉的話,那麼以前的PC的值是存在lr中的),而後在子程序執行完畢的時候,再把棧中的lr的值pop出來,賦值給PC,這樣就實現了子函數的正確的返回。
2)傳遞參數:
C語言進行函數調用的時候,經常會傳遞給被調用的函數一些參數,對於這些C語言級別的參數,被編譯器翻譯成彙編語言的時候,就要找個地方存放一下,而且讓被調用的函數可以訪問,不然就沒發實現傳遞參數了。對於找個地方放一下,分兩種狀況。一種狀況是,自己傳遞的參數很少於4個,就能夠經過寄存器r0~r3傳送參數。由於在前面的保存現場的動做中,已經保存好了對應的寄存器的值,那麼此時,這些寄存器就是空閒的,能夠供咱們使用的了,那就能夠放參數。另外一種狀況是,參數多於4個時,寄存器不夠用,就得用棧了。
3)臨時變量保存在棧中:
包括函數的非靜態局部變量以及編譯器自動生成的其餘臨時變量。
如今,咱們能夠很容易寫出控制 LED 的程序了。畢竟是用C語言嘛,至關的靈活。 main函數在main.c文件中,代碼以下:
#define GPM4CON (*(volatile unsigned int *)0x110002E0) #define GPM4DAT (*(volatile unsigned int *)0x110002E4) void delay(volatile int time) { for(; time > 0; time-- ); } int main(void) { unsigned long tmp = 0; int i = 0; /* * GPM4_0-GPM4_3 設置爲輸出功能 */ tmp = GPM4CON; tmp &= ~0xffff; tmp |= 0x1111; GPM4CON = tmp; /* * 實現流水燈 */ while(1) { GPM4DAT = i; if (++i == 16) i = 0; delay(9999999); } return 0; }
來看看Makefile:
objs := start.o main.o led.bin : $(objs) arm-linux-ld -Tled.lds -N -o led.elf $^ arm-linux-objcopy -O binary -S led.elf $@ arm-linux-objdump -D -m arm led.elf > led.dis %.o:%.c arm-linux-gcc -Wall -marm -c -O2 -o $@ $< %.o:%.S arm-linux-gcc -Wall -marm -c -O2 -o $@ $< clean: rm -f *.dis *.bin *.elf *.o
執行 make 命令時,它的目是去生成第1個目標,即 led.bin ;
led.bin 依賴於start.o 和 main.o,因此要先生成這 2個.o 文件;
start.o 依賴於start.S ,符合第 11 行的規則,會使用第 12 行的命令生成start.o ;
相似的, main.o 依賴於main.c ,符合第 8行的規則,會使用第 9行的命令生成 main.o ;
當這 2個.o 文件都生成以後,就會執行第 4~6行的命令生成 led.bin文件: 第 4行將編譯獲得的 .o 文 件鏈接爲led.elf
可執行程序,第 5行是生成二進制格式的可執行程序,第 6行是獲得反彙編程序以供查看。
連接腳本還和彙編流水燈同樣。這裏再也不介紹。
好了,下面開始驗證咱們的程序了。
1.將程序源碼上傳到服務器,並執行make,生成led.bin文件。
2.借鑑上一個實驗的步驟,將程序燒寫到SD卡。