01
單片機存儲分配
在玩單片機(以stm32為例)的時候會有RAM空間和ROM空間,RAM空間主要是用于數據的訪問,而ROM空間用于存放燒錄的固件,當然固件也可以直接加載到RAM中運行,只是說每次上電都需要重新加載。
如上圖所示ROM為FLASH地址,而RAM為SRAM地址,毋庸置疑生成的單片機固件會燒錄到Flash上,這樣才能保證每次上電都有可以正常運行。
對于很多初學者該有疑問了,明明全局變量等等都是分配到RAM上的呀,怎么說固件放到Flash上的呢?
其實并不矛盾,程序指令中訪問變量都是訪問變量的地址也就是內存的地址,所謂的分配到RAM上,僅僅只是說相應的變量占據了對應的RAM地址,并不能理解為這個變量存在于RAM里面。
可能你還會繼續問 : 暫且認同上面的說法,那對這些變量的初值該如何解釋呢?
可以肯定的是,這些全局變量的初值并不是來源于RAM,因為RAM掉完電以后數據就丟失了,而在程序正常運行過程中,不管怎么上下電其初值都是我們程序中規定的,也就是在編譯中確定的。
所以這些初值要保存只可能存在ROM中,這中間肯定有這樣一種機制 : 在上電以后把ROM中存儲的這些變量初值來重新初始化到對應的RAM地址,以便后續程序指令訪問,這種機制通常叫分散加載。
02
簡述分散加載
上圖是一種簡單的分散加載機制,映像文件由不同的段組成,通常都有代碼段(.text)、已初始化數據段(.data)、未初始化及初始化為0的數據段(.bss)等等,而且他們具有不同的屬性RO,RW,ZI等等。
為了便于大家理解,整個系統的存儲區分為ROM和SRAM,左邊Load View表示的是程序存儲地址空間分布情況,也就是程序燒錄到ROM以后的空間分配情況。
固件燒錄到ROM區域并且分為RW區和RO區,RW區域為可讀可寫區域而RO區域為只讀區,分這兩個區域并不是說RW區域存儲地址區域以后就用來數據的讀寫,而是為了上電過程中的copy/decompress(復制或者解壓)過程做好標記,這個過程會把一些非零全局變量(或者靜態變量等)的SRAM地址(實際的運行地址)處賦予初始值。
ZI區域是零填充區域,主要是.bss段的一些初始化為0或者未初始化的全局或者靜態變量分布區域,這些數據沒有必要保存到固件中,所以由加載機制自行清零即可。
一切準備就緒就形成了右側的execution View的運行空間視野,由于ROM中程序運行所涉及到的全局變量等的訪問都是SRAM地址的訪問,而這些地址恰好在程序編譯鏈接過程中已經分配到SRAM里面,經過前面的該部分地址的重新定位,運行空間的程序就可以正確訪問到這些變量的初值等等。
03
stm32啟動流程
很多剛玩MCU的朋友,都會以main函數作為程序的開始運行處,不過幾乎所有的C程序在執行前都會使用匯編指令,通過匯編指令構建C語言運行環境,并運行C程序,所以在C程序執行前做了非常多的工作,其中非常重要的就是堆棧指針的設置,這也是從匯編到C運行環境一定要做的一件事了。
那么stm32的啟動大致流程是怎樣的?這里小哥就簡述一下:
當然還有一些小細節,這里就不展開了,stm32的Flash可以直接運行程序,采用分散加載,只需要把相應的數據區域加載到運行地址處便可以正常的訪問,這個與前面的所說是類似的。
04
uboot部署Linux
在進行Linux系統開發過程中,一切從Bootloader開始,而bootloader本質上就是一個單任務的裸機程序,和單片機程序是一樣的,而在眾多bootloader中最為常用和廣泛的就是uboot了,他就是為了部署Linux環境而生的,下載、燒錄、運行Linux映像、文件系統等等。
uboot都可以搞定,所以它對地址是非常敏感的,程序、參數等等應該存儲在什么地址,在什么地方運行都是需要確定好的,而這些地址在編譯鏈接的過程中,鏈接腳本已經確定好了這一切,uboot的工作就是把這些固件放在編譯鏈接所規定的運行地址處進行運行即可。
比如全局變量在什么地址,函數在什么地址,當程序運行的過程中就會從這些確切的地址處取數據,如果你把全局函數指針變量的地址分配到了NANDFlash上,那么程序在訪問的過程中就有可能跑飛。
程序運行最重要的兩個地址加載地址和 運行地址 。
加載地址也常被大家成為存儲地址 ,即實際固件存儲的位置,其實該地址也只是一個相對的概念,就相當于單片機中bin文件燒錄在什么位置一樣的道理。
運行地址也叫鏈接地址,即程序的絕對地址 。全局變量等等都是以該地址為基礎,來確定程序的運行狀態的各部分的地址布局。
當然Linux以上各部分直接燒寫到RAM也是也可以直接運行的,不過還是那個問題,一旦掉電則全部丟失,所以最終每個部分都會寫入到Flash上(當然在前期調試的時候可以直接下載到RAM中,減少對Flash的反復擦寫),但對于大部分Flash都是無法直接運行程序的,即使能夠運行,比如Norflash也是非常的慢,且不能夠直接寫入,所以Linux內核等都會加載到RAM來運行,以獲得更快的執行速度,那么前面介紹的那種單片機方式只重定位數據段的方式不太適用了。
在嵌入式Linux平臺上,首先執行的就是bootloader,而它只是一個順序執行的程序,它有一個重要的工作就是把Linux內核搬運到RAM中運行,由于我們的內核兼容不同的單板,uboot也會傳遞給內核一些配置參數以配置內核。
往往RAM分配的地址比較高,而整個程序往往都是0地址開始執行了的,如果讓存儲地址與運行地址相同來進行編譯,會導致最終燒錄文件非常之大,并且中間有一大片地址區域是無效的。
那么有什么辦法來解決這個無效區域以縮小我們的固件大小呢?先了解下位置無關指令。
05
位置無關指令
既然有位置無關指令就有位置有關指令,簡單的說所執行的指令是不是與位置相關才能達到目的。
可以類比與 絕對路徑與相對路徑 ,相對路徑你可以把程序放在任何文件夾下面,編輯器均可以根據工程文件路徑找到其他每一個文件,而絕對路徑卻不行,一旦文件夾換了,基本上就是定位不到具體的每個文件了。
所以位置無關就相當于相對路徑,數據的訪問、函數的調用幾乎都是相對的,為什么說是幾乎呢?因為有些情況下訪問絕對地址也是與位置關系不大的,可以把這段程序放在可以執行的任何位置,所以位置無關碼的運行與鏈接地址也沒有直接的聯系。
比如跳轉指令B BL等這些跳轉指令采用PC+偏移量,所以為位置無關指令;而如果我們采用 ldr r0, =標記 ,而這些標記都是實際在鏈接過程中確定的運行地址,所以該指令為位置有關指令;并且全局變量基本上都是位置有關,而局部變量為位置無關;所以對于位置無關代碼區域,跳轉一般都使用B指令,而從位置無關代碼區域跳轉到位置有關指令代碼區域去執行就需要借助位置有關跳轉指令。
06
加載與運行地址不同
當存儲地址與鏈接地址不同時,多數情況下由于采用位置有關指令會出問題,最常見的就是PC指針取的絕對地址,而此時該絕對地址處無存儲,導致程序飛掉。
既然有了位置無關的程序,那么我們就可以把其當作一個搬運工放在位置有關部分的后面,一旦需要運行位置有關碼,那么就會通過位置無關碼把有關部分拷貝到運行地址處,然后跳轉執行即可,這樣整個的程序就可以做得非常的連續且中間幾乎沒有無效區域,該搬運的過程就是常說的 重定位 。
07
地址的設置
大部分ARM處理器其PC都是從0地址開始執行,所以在0地址處要么是運行程序,要么就是引導程序,如果沒有這兩樣,你的程序燒錄到其他位置均無法得到運行。
對于S3C2440芯片能夠支持NorFlash和NandFlash啟動,其中NorFlash上可以直接運行,而NandFlash啟動由于其程序無法直接在上面運行,芯片會把內部SRAM作為0地址處,并且把NandFlash前4K代碼拷貝到SRAM上運行。
因為這里最終想讓所有的程序都在SDRAM里面運行,考慮使用全部重定位的辦法,在鏈接腳本中確定好程序的存儲地址和運行地址。
上圖是GUN linker中截取的段描述格式
具體詳細解讀大家可以參考上面的鏈接,下面看看幾個常用的。
可執行文件由各個段組成,:
1、secname段名,一般使用數據段.data段,代碼段.text段等等。
2、AT(ldadr)表示該段存儲地址,也就是加載地址。
3、contents表示目標文件(比如.o目標文件)中的哪些段放在本段,也可以是整個目標文件全部放在這個段內。
4、start表示本段鏈接(或者稱為運行)的地址,如果沒有使用AT(ldadr),本段存儲的地址也是start,也就是說存儲地址與運行地址相等。
通過上面的段描述格式就可以在鏈接過程中確定好程序的運行地址和載入地址,以方便后續的重定位地址的使用。
下面以一個簡單的實例說明一下:
1//....格式
2SECTIONS {
3...
4secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
5 { contents } >region :phdr =fill
6...
7}
8//.....示例
9SECTIONS {
10...
11.text 0x30000 : AT ( 0x0000 )
12 { *(.text) }
13
14.data 0x3FFFF : AT ( 0xFFFF )
15 { *(.data) }
16...
17}
這樣固件的代碼段的存儲地址為0,數據段存儲地址為0xFFFF,而運行地址分別為0x30000和0x3FFFF,最終重定位部分就根據這鏈接腳本中的符號獲得相應地址,然后把相應的部分"搬運"到運行地址處運行處,比如如果載入地址在NandFlash上,那么重定位的過程中就需要初始化NandFlash控制器,然后讀取NandFlash上的數據并"搬運"到運行地址處。
在嵌入式linux中很多時候這些地址都需要我們自己確認和設置的,不然Linux內核無法啟動或者加載相應程序,而在單片機開發中用慣了IDE工具,所以大部分人涉及得不多~
評論
查看更多