天天看點

tms320dm6446核心啟動分析

關于達芬奇dm6446,裡面内部有兩個部分,一個是arm926ejs的核,還有一個是c64+dsp的視訊處理核,而我需要關心的重點是arm926ejs的核(bootload和linux核心)

從bootloader可知, 第一階段主要是負責檢測arm926ejs的相關硬體平台(主要是記憶體等),而第二階段主要将核心映象以及根檔案映象拷貝進入到ram中運作!

硬體存儲位址說明:

flash位址存放:

利用emif(5.10—external memory interface)外部存儲接口,一共128m空間

起始位址是0x02000000開始,分别存放bootloader, linux-kernel, ramdisk

0x2030000---存放image---ddr2 記憶體進入點是0x80008000

0x21b0000---存放ramdisk----ddr2記憶體進入點是0x80800000

ddr2位址說明:

 利用ddr2專用接口, 位址是0x8000 0000開始(看2.5—memory map summary接口),這個是程式運作的ram接口

程式運作過程:

根據達芬奇手冊的3.3(bootmode)說明,和mx27一樣,達芬奇也有自帶的的一個類似bios的固化啟動程式,根據pin腳的選擇來選擇相應的啟動口!

檢視表table 3-4可得到相關的啟動的詳細資訊!

我們看到arm啟動模式:

當系統複位時,儲存在片内rom的rbl(rom boot load)程式開始運作,該程式根據讀寫btsel[1:0]腳來判斷相應的啟動方式,如果選擇了emifa(nor flash)啟動方式,那麼立即從位址0x02000000開始運作,否則運作rbl程式,rbl程式首先便将flash的開始位置的ubl(使用者user boot loader)拷貝到arm内部的ram(aim)中,并運作該ubl程式(最多14k)大小!

ubl主要完成系統時鐘、ddr頻率的初始化,準備好加載u-boot鏡像的環境。然後加載u-boot代碼到ddr中,并跳轉到u-boot上,然後u-boot啟動核心!

ddr2核心啟動記憶體配置設定:(tftp 啟動模式)

0x8000 0000-0x8000 8000這個32k是存放linux核心的全局變量,如指令參數、頁表等

0x8000 8000-0x807f ffff這個8m左右的空間是存放真正的核心的

0x8080 0000-0x8180 0000這個是存放ramdisk的鏡像的

0x8180 0000—這個存放uimage鏡像檔案

具體的核心運作過程為:

1.u-boot啟動拷貝ramdisk鏡像到0x8080 0000

2.u-boot啟動拷貝uimage鏡像到0x81800000中,如果在鏡像在flash的話,那麼

3.運作uimage鏡像在0x8000 8000開始處,那麼開始核心解壓,然後運作vmlinux

根據相關的資料,可以得出,bootloader的過程比較容易了解,直接将其cpu看成一個單片機類型的應用程式即可,有ram和rom(flash)可用,并且arm資源很吩咐,底層的初始化基本上用彙編實作,定義好了相關的位址啟動cpu之後,那麼可以用c語言實作相關的bootloader功能,比較簡單,複雜的是linux的核心運作!

關于bootloader的相關詳細分析,可以參考一篇強文《嵌入式系統bootloader技術内幕》

由上可知,關于核心運作,不管是tftp模式還是flash模式:

核心運作的情況都是拷貝到0x8000 8000處,然後進行解壓縮,解壓後開始運作核心

核心挂載根檔案系統是在0x8180 0000

接下來,利用u-boot中的兩個有趣的函數實作的指令來做兩個實驗

1. do_go()函數,u-boot中的go指令

2. do_bootm()函數,u-boot對應的bootm指令(包含了寫入核心相關全局啟動參數)

這裡我們核心進行編譯後,可以看到生成zimge ,uimage 以及compressed/image 三個核心檔案,在u-boot中,利用指令

1.tftp  81800000 zimage(其實也可以是任意的,82000000都可以,利用u-boot的tftp協定,将zimage檔案,下載下傳到81800000的位址處)

然後運作 go 81800000 ----可以看到我們的程式開始運作了吧,看到沒?

程式馬上進行解壓核心,然後進入到核心,核心根據boot傳遞的參數開始執行!

2.tftp  8180000 uimage(這個也可以是8180000後的任意位址,因為前面需要用ramdisk),是以我們将uimage下載下傳到0x8180000記憶體位址處

利用bootm 81800000來解壓uimage

上面的兩個方法都可以實作核心的啟動,這裡順便說一下initrd參數,如果我們是挂載ramdisk類型,那麼我們設定boot參數的時候,設定initrd=0x80800000這個位址來挂載系統的根檔案系統,那麼核心啟動的時候,初始化之後,在挂載真正的根檔案系統之前,會挂載一個初始根檔案系統,該系統就是參數initrd指定位址開始,如果是yjttfs2類型的檔案系統,那麼直接用rootfs=/dev/來挂載,并且前面定義noinitrd來進行說明!

是以我們在boot的參數可以直接指定initrd=0x8080000 ,也就是前面

利用指令tftp 80800000 ramdisk 将ramdisk存放到指定位置後的位址

總結,上面我們運作實驗知道了一點,就是利用u-boot中的tftp功能,将相關的核心可執行映象檔案zimage存放到後面的ram中,然後直接運作!

接下來我們看看zimage是什麼,它為什麼可以直接運作?

關于兩個起始檔案:linux-2.6.*/arch/arm/kernel/*.s

  linux-2.6.*/arch/arm/boot/compressed/*.s

核心編譯流程:

如上,首先編譯生成的是未壓縮過的核心檔案,然後再将其拷貝進入boot/compressed/下進行壓縮,生成相應的zimage或者是其他的uimage之類的(uimage就是在zimage前面加上一個64位元組大小的資訊頭,包含了linux版本、核心大小等資訊,是需要對應uboot的do_bootm指令來解壓,如果你的bootloader不是u-boot,那麼還是使用zimage,利用位址來直接跳轉吧),從上可以大概明确一點:

1. 利用kernel/*.s vmlinux.lds等相關檔案, 将編譯好的相關核心代碼進行分段連結,首先,生成一個叫vmlinux(虛拟記憶體核心elf格式檔案),該檔案包含了sections等資訊,各個分段辨別都可見 ,這個才是其實才是真正的核心執行檔案image,但是需要記憶體重映射才行,這個核心定義的運作位址和bootloader定義的基位址一定要比對(如同這裡的80008000才是真正的核心運作起始位址,8000 0000-80007fff是核心存放全局變量參數)

因為核心定義的運作位址其實是0x0000 0000,是以才需要一個基位址,因為不同的系統定義的sdram的實體位址是不一樣的,如同mx27中是a000 0000是sdram的首實體位址,而s3c2440是0xc000 0000才是首實體位址,而這裡規定的是0x8000 0000才是首位址!(如果你不相信, 可以試着做個實驗,利用tftp  80008000 image先下載下傳到闆子上,然後直接運作80008000 ,看看是不是直接可以運作下去,但是如果你不是将該image下載下傳到bootloader比對的80008000處,那麼不管是其它任何位址,都不會運作image,  但是zimage就不同)

2. 利用objcopy把上面的檔案進行處理,去掉sections等資訊,生成一個vmlinux.bin檔案

3. 利用gzip将其壓縮成vmlinux.bin.gz, 并删除vmlinux.bin檔案

4. 将vmlinux.bin.gz檔案設為資料段檔案,生成一個叫piggy.o的檔案

5. 連結:compressed/vmlinux=head.o+misc.o+piggy.o,其中head.o+misc.o主要用于解壓用

6. 再次利用objcopy将vmlinux去掉相關的section等資訊,生成vmlinux.bin

7. 最後連結_start,生成zimage

zimage 的入口程式即為 arch/arm/boot/compressed/head.s。它依次完成以下工作:開啟 mmu 和 cache,調用 decompress_kernel()解壓核心,最後通過調用 call_kernel()進入非壓縮核心 image 的啟動

明白了上面大概的核心生成的過程,如果需要,請自行參考boot/compressed/下的相關代碼

如果需要詳細的明白該流程,可以相關參考資料:百度文庫中《核心閱讀心得》

那麼真正的核心入口位址是0x80008000, 才是運作image真正的入口位址!

一般情況下,不同的平台,檔案下初始化linux-2.6/arch/arm/kernel/下的檔案是不同的

下面我們分析image的真正的執行情況!

linux核心入口 (轉)

linux 非壓縮核心的入口位于檔案/arch/arm/kernel/head-armv.s 中的 stext 段。該段的基位址就是壓縮核心解壓後的跳轉位址。如果系統中加載的核心是非壓縮的 image,那麼bootloader将核心從 flash中拷貝到 ram 後将直接跳到該位址處,進而啟動 linux 核心。不同體系結構的 linux 系統的入口檔案是不同的,而且因為該檔案與具體體系結構有關,是以一般均用彙編語言編寫[3]。對基于 arm 處理的 linux 系統來說,該檔案就是head-armv.s。該程式通過查找處理器核心類型和處理器類型調用相應的初始化函數,再建立頁表,最後跳轉到 start_kernel()函數開始核心的初始化工作。 

檢測處理器核心類型是在彙編子函數__lookup_processor_type中完成的。通過以下代碼可實作對它的調用:bl __lookup_processor_type。__lookup_processor_type調用結束傳回原程式時,會将傳回結果儲存到寄存器中。其中r8 儲存了頁表的标志位,r9 儲存了處理器的 id 号,r10 儲存了與處理器相關的 struproc_info_list 結構位址。 

檢測處理器類型是在彙編子函數 __lookup_architecture_type 中完成的。與__lookup_processor_type類似,它通過代碼:“bl __lookup_processor_type”來實作對它的調用。該函數傳回時,會将傳回結構儲存在 r5、r6 和 r7 三個寄存器中。其中 r5 儲存了 ram 的起始基位址,r6 儲存了 i/o基位址,r7 儲存了 i/o的頁表偏移位址。當檢測處理器核心和處理器類型結束後,将調用__create_page_tables 子函數來建立頁表,它所要做的工作就是将 ram 基位址開始的 4m 空間的實體位址映射到 0xc0000000 開始的虛拟位址處。對筆者的 s3c2410 開發闆而言,ram 連接配接到實體位址 0x30000000 處,當調用 __create_page_tables 結束後 0x30000000 ~ 0x30400000 實體位址将映射到0xc0000000~0xc0400000 虛拟位址處。 

當所有的初始化結束之後,使用如下代碼來跳到 c 程式的入口函數 start_kernel()處,開始之後的核心初始化工作: 

b symbol_name(start_kernel) 

3.2 start_kernel函數 

start_kernel是所有 linux 平台進入系統核心初始化後的入口函數,它主要完成剩餘的與硬體平台相關的初始化工作,在進行一系列與核心相關的初始化後,調用第一個使用者程序-init 程序并等待使用者程序的執行,這樣整個 linux 核心便啟動完畢。該函數所做的具體工作有[4][5] 

: 

1) 調用 setup_arch()函數進行與體系結構相關的第一個初始化工作; 

對不同的體系結構來說該函數有不同的定義。對于 arm 平台而言,該函數定義在arch/arm/kernel/setup.c。它首先通過檢測出來的處理器類型進行處理器核心的初始化,然後通過 bootmem_init()函數根據系統定義的 meminfo 結構進行記憶體結構的初始化,最後調用paging_init()開啟 mmu,建立核心頁表,映射所有的實體記憶體和 io空間。 

2) 建立異常向量表和初始化中斷處理函數; 

3) 初始化系統核心程序排程器和時鐘中斷處理機制; 

4) 初始化序列槽控制台(serial-console); 

arm-linux 在初始化過程中一般都會初始化一個序列槽做為核心的控制台,這樣核心在啟動過程中就可以通過序列槽輸出資訊以便開發者或使用者了解系統的啟動程序。 

5) 建立和初始化系統 cache,為各種記憶體調用機制提供緩存,包括;動态記憶體配置設定,虛拟檔案系統(virtualfile system)及頁緩存。 

6) 初始化記憶體管理,檢測記憶體大小及被核心占用的記憶體情況; 

7) 初始化系統的程序間通信機制(ipc); 

當以上所有的初始化工作結束後,start_kernel()函數會調用 rest_init()函數來進行最後的初始化,包括建立系統的第一個程序-init 程序來結束核心的啟動。init 程序首先進行一系列的硬體初始化,然後通過指令行傳遞過來的參數挂載根檔案系統。最後 init 程序會執行用 戶傳遞過來的“init=”啟動參數執行使用者指定的指令,或者執行以下幾個程序之一: 

execve("/sbin/init",argv_init,envp_init); 

execve("/etc/init",argv_init,envp_init); 

execve("/bin/init",argv_init,envp_init); 

execve("/bin/sh",argv_init,envp_init)。 

當所有的初始化工作結束後,cpu_idle()函數會被調用來使系統處于閑置(idle)狀态并等待使用者程式的執行。至此,整個 linux 核心啟動完畢。

繼續閱讀