詳解 ARM Linux啓動過程分析

ARM Linux 啓動過程分析是本人要介紹的內容,嵌入式 Linux 的可移植性使得咱們能夠在各類電子產品上看到它的身影。對於不一樣體系結構的處理器來講 Linux 的啓動過程也有所不一樣。本文以S3C2410 ARM 處理器爲例,詳細分析了系統上電後 bootloader的執行流程及 ARM Linux 的啓動過程。
 
一、引 言
Linux 最初是由瑞典赫爾辛基大學的學生 Linus Torvalds在1991 年開發出來的,以後在 GNU的支持下,Linux 得到了巨大的發展。雖然 Linux 在桌面 PC 機上的普及程度遠不及微軟的 Windows 操做系統,但它的發展速度之快、用戶數量的日益增多,也是微軟所不能輕視的。而近些年來 Linux 在嵌入式領域的迅猛發展,更是給 Linux 注入了新的活力。
一個嵌入式 Linux 系統從軟件角度看能夠分爲四個部分[1]:引導加載程序(bootloader), Linux 內核,文件系統,應用程序。
其中 bootloader是系統啓動或復位之後執行的第一段代碼,它主要用來初始化處理器及外設,而後調用 Linux 內核。Linux 內核在完成系統的初始化以後須要掛載某個文件系統作爲根文件系統(Root Filesystem)。根文件系統是 Linux 系統的核心組成部分,它能夠作爲Linux 系統中文件和數據的存儲區域,一般它還包括系統配置文件和運行應用軟件所須要的庫。
應用程序能夠說是嵌入式系統的「靈魂」,它所實現的功能一般就是設計該嵌入式系統所要達到的目標。若是沒有應用程序的支持,任何硬件上設計精良的嵌入式系統都沒有實用意義。
從以上分析咱們能夠看出 bootloader 和 Linux 內核在嵌入式系統中的關係和做用。Bootloader在運行過程當中雖然具備初始化系統和執行用戶輸入的命令等做用,但它最根本的功能就是爲了啓動 Linux 內核。在嵌入式系統開發的過程當中,很大一部分精力都是花在bootloader 和 Linux 內核的開發或移植上。若是能清楚的瞭解 bootloader 執行流程和 Linux的啓動過程,將有助於明確開發過程當中所需的工做,從而加速嵌入式系統的開發過程。而這正是本文的所要研究的內容。
 
二、Bootloader
1)Bootloader的概念和做用
Bootloader是嵌入式系統的引導加載程序,它是系統上電後運行的第一段程序,其做用相似於 PC 機上的 BIOS。在完成對系統的初始化任務以後,它會將非易失性存儲器(一般是 Flash或 DOC 等)中的Linux 內核拷貝到 RAM 中去,而後跳轉到內核的第一條指令處繼續執行,從而啓動 Linux 內核。因而可知,bootloader 和 Linux 內核有着密不可分的聯繫,要想清楚的瞭解 Linux內核的啓動過程,咱們必須先得認識 bootloader的執行過程,這樣才能對嵌入式系統的整個啓過程有清晰的掌握。
     
2)Bootloader的執行過程
不一樣的處理器上電或復位後執行的第一條指令地址並不相同,對於 ARM 處理器來講,該地址爲 0x00000000。對於通常的嵌入式系統,一般把 Flash 等非易失性存儲器映射到這個地址處,而 bootloader就位於該存儲器的最前端,因此係統上電或復位後執行的第一段程序即是 bootloader。而由於存儲 bootloader的存儲器不一樣,bootloader的執行過程也並不相同,下面將具體分析。
嵌入式系統中普遍採用的非易失性存儲器一般是 Flash,而 Flash 又分爲 Nor Flash 和Nand Flash 兩種。 它們之間的不一樣在於: Nor Flash 支持芯片內執行(XIP, eXecute In Place),這樣代碼能夠在Flash上直接執行而沒必要拷貝到RAM中去執行。而Nand Flash並不支持XIP,因此要想執行 Nand Flash 上的代碼,必須先將其拷貝到 RAM中去,而後跳到 RAM 中去執行。
實際應用中的 bootloader根據所需功能的不一樣能夠設計得很複雜,除完成基本的初始化系統和調用 Linux 內核等基本任務外,還能夠執行不少用戶輸入的命令,好比設置 Linux 啓動參數,給 Flash 分區等;也能夠設計得很簡單,只完成最基本的功能。但爲了能達到啓動Linux 內核的目的,全部的 bootloader都必須具有如下功能[2] :
 
初始化 RAM
由於 Linux 內核通常都會在 RAM 中運行,因此在調用 Linux 內核以前 bootloader 必須設置和初始化 RAM,爲調用 Linux內核作好準備。初始化 RAM 的任務包括設置 CPU 的控制寄存器參數,以便能正常使用 RAM 以及檢測RAM 大小等。
初始化串口
串口在 Linux 的啓動過程當中有着很是重要的做用,它是 Linux內核和用戶交互的方式之一。Linux 在啓動過程當中能夠將信息經過串口輸出,這樣即可清楚的瞭解 Linux 的啓動過程。雖然它並非 bootloader 必需要完成的工做,可是經過串口輸出信息是調試 bootloader 和Linux 內核的強有力的工具,因此通常的 bootloader 都會在執行過程當中初始化一個串口作爲調試端口。
檢測處理器類型
Bootloader在調用 Linux內核前必須檢測系統的處理器類型,並將其保存到某個常量中提供給 Linux 內核。Linux 內核在啓動過程當中會根據該處理器類型調用相應的初始化程序。
設置 Linux啓動參數
Bootloader在執行過程當中必須設置和初始化 Linux 的內核啓動參數。目前傳遞啓動參數主要採用兩種方式:即經過 struct param_struct 和struct tag(標記列表,tagged list)兩種結構傳遞。struct param_struct 是一種比較老的參數傳遞方式,在 2.4 版本之前的內核中使用較多。從 2.4 版本之後 Linux 內核基本上採用標記列表的方式。但爲了保持和之前版本的兼容性,它仍支持 struct param_struct 參數傳遞方式,只不過在內核啓動過程當中它將被轉換成標記列表方式。標記列表方式是種比較新的參數傳遞方式,它必須以 ATAG_CORE 開始,並以ATAG_NONE 結尾。中間能夠根據須要加入其餘列表。Linux內核在啓動過程當中會根據該啓動參數進行相應的初始化工做。
調用 Linux內核映像
Bootloader完成的最後一項工做即是調用 Linux內核。若是 Linux 內核存放在 Flash 中,而且可直接在上面運行(這裏的 Flash 指 Nor Flash),那麼可直接跳轉到內核中去執行。但因爲在 Flash 中執行代碼會有種種限制,並且速度也遠不及 RAM 快,因此通常的嵌入式系統都是將 Linux內核拷貝到 RAM 中,而後跳轉到 RAM 中去執行。不論哪一種狀況,在跳到 Linux 內核執行以前 CUP的寄存器必須知足如下條件:r0=0,r1=處理器類型,r2=標記列表在 RAM中的地址。
 
三、Linux內核的啓動過程
bootloader將 Linux 內核映像拷貝到 RAM 之後,能夠經過下例代碼啓動 Linux 內核:call_linux(0, machine_type, kernel_params_base)。
其中,machine_tpye 是 bootloader檢測出來的處理器類型, kernel_params_base 是啓動參數在 RAM 的地址。經過這種方式將 Linux 啓動須要的參數從 bootloader傳遞到內核。Linux 內核有兩種映像:一種是非壓縮內核,叫 Image,另外一種是它的壓縮版本,叫zImage。根據內核映像的不一樣,Linux 內核的啓動在開始階段也有所不一樣。zImage 是 Image通過壓縮造成的,因此它的大小比 Image 小。但爲了能使用 zImage,必須在它的開頭加上解壓縮的代碼,將 zImage 解壓縮以後才能執行,所以它的執行速度比 Image 要慢。但考慮到嵌入式系統的存儲空容量通常比較小,採用 zImage 能夠佔用較少的存儲空間,所以犧牲一點性能上的代價也是值得的。因此通常的嵌入式系統均採用壓縮內核的方式。
對於 ARM 系列處理器來講,zImage 的入口程序即爲 arch/arm/boot/compressed/head.S。它依次完成如下工做:開啓 MMU 和 Cache,調用 decompress_kernel()解壓內核,最後經過調用 call_kernel()進入非壓縮內核 Image 的啓動。下面將具體分析在此以後 Linux 內核的啓動過程。
1)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」來實現對它的調用。該函數返回時,會將返回結構保存在 r五、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)
2)start_kernel函數
start_kernel是全部 Linux 平臺進入系統內核初始化後的入口函數,它主要完成剩餘的與硬件平臺相關的初始化工做,在進行一系列與內核相關的初始化後,調用第一個用戶進程-init 進程並等待用戶進程的執行,這樣整個 Linux 內核便啓動完畢。該函數所作的具體工做有[4][5]:
調用 setup_arch()函數進行與體系結構相關的第一個初始化工做;
對不一樣的體系結構來講該函數有不一樣的定義。對於 ARM 平臺而言,該函數定義在arch/arm/kernel/Setup.c。它首先經過檢測出來的處理器類型進行處理器內核的初始化,而後經過 bootmem_init()函數根據系統定義的 meminfo 結構進行內存結構的初始化,最後調用paging_init()開啓 MMU,建立內核頁表,映射全部的物理內存和 IO空間。
a、建立異常向量表和初始化中斷處理函數;
b、初始化系統核心進程調度器和時鐘中斷處理機制;
c、初始化串口控制檯(serial-console);
d、ARM-Linux 在初始化過程當中通常都會初始化一個串口作爲內核的控制檯,這樣內核在啓動過程當中就能夠經過串口輸出信息以便開發者或用戶瞭解系統的啓動進程。
e、建立和初始化系統 cache,爲各類內存調用機制提供緩存,包括;動態內存分配,虛擬文件系統(VirtualFile System)及頁緩存。
f、初始化內存管理,檢測內存大小及被內核佔用的內存狀況;
g、初始化系統的進程間通訊機制(IPC);
當以上全部的初始化工做結束後,start_kernel()函數會調用 rest_init()函數來進行最後的初始化,包括建立系統的第一個進程-init 進程來結束內核的啓動。Init 進程首先進行一系列的硬件初始化,而後經過命令行傳遞過來的參數掛載根文件系統。最後 init 進程會執行用 戶傳遞過來的「init=」啓動參數執行用戶指定的命令,或者執行如下幾個進程之一:
1 execve("/sbin/init",argv_init,envp_init);   
2 execve("/etc/init",argv_init,envp_init);   
3 execve("/bin/init",argv_init,envp_init);   
4 execve("/bin/sh",argv_init,envp_init) 。  
當全部的初始化工做結束後,cpu_idle()函數會被調用來使系統處於閒置(idle)狀態並等待用戶程序的執行。至此,整個 Linux 內核啓動完畢。
 
4. 結論
Linux 內核是一個很是龐大的工程,通過十多年的發展,它已從從最初的幾百 KB 大小發展到如今的幾百兆。清晰的瞭解它執行的每個過程是件很是困難的事。可是在嵌入式開發過程當中,咱們並不須要十分清楚 linux 的內部工做機制,只要適當修改 linux 內核中那些與硬件相關的部分,就能夠將 linux 移植到其它目標平臺上。經過對 linux 的啓動過程的分 析,咱們能夠看出哪些是和硬件相關的,哪些是 linux 內核內部已實現的功能,這樣在移植 linux 的過程當中便有所針對。而 linux 內核的分層設計將使 linux 的移植變得更加容易。
相關文章
相關標籤/搜索