ARM Linux啟動過程分析

1. 引 言
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的啟動過程,將有助于明確開發過程中所需的工作,從而加速嵌入式系統的開發過程。而這正是本文的所要研究內容


2. Bootloader
2.1 Bootloader的概念和作用

???? Bootloader是嵌入式系統的引導加載程序,它是系統上電后運行的第一段程序,其作用類似于 PC 機上的 BIOS。在完成對系統的初始化任務之后,它會將非易失性存儲器(通常是 Flash或 DOC 等)中的Linux 內核拷貝到 RAM 中去,然后跳轉到內核的第一條指令處繼續執行,從而啟動 Linux 內核。由此可見,bootloader 和 Linux 內核有著密不可分的聯系,要想清楚的了解 Linux內核的啟動過程,我們必須先得認識 bootloader的執行過程,這樣才能對嵌入式系統的整個啟過程有清晰的掌握。
2.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] :

1) 初始化 RAM
因為 Linux 內核一般都會在 RAM 中運行,所以在調用 Linux 內核之前 bootloader 必須設置和初始化 RAM,為調用 Linux內核做好準備。初始化 RAM 的任務包括設置 CPU 的控制寄存器參數,以便能正常使用 RAM 以及檢測RAM 大小等。
2) 初始化串口

????? 串口在 Linux 的啟動過程中有著非常重要的作用,它是 Linux內核和用戶交互的方式之一。Linux 在啟動過程中可以將信息通過串口輸出,這樣便可清楚的了解 Linux 的啟動過程。雖然它并不是 bootloader 必須要完成的工作,但是通過串口輸出信息是調試 bootloader 和Linux 內核的強有力的工具,所以一般的 bootloader 都會在執行過程中初始化一個串口做為調試端口。
3) 檢測處理器類型
Bootloader在調用 Linux內核前必須檢測系統的處理器類型,并將其保存到某個常量中提供給 Linux 內核。Linux 內核在啟動過程中會根據該處理器類型調用相應的初始化程序。
4) 設置 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內核在啟動過程中會根據該啟動參數進行相應的初始化工作。

5) 調用 Linux內核映像
Bootloader完成的最后一項工作便是調用 Linux內核。如果 Linux 內核存放在 Flash 中,并且可直接在上面運行(這里的 Flash 指 Nor Flash),那么可直接跳轉到內核中去執行。但由于在 Flash 中執行代碼會有種種限制,而且速度也遠不及 RAM 快,所以一般的嵌入式系統都是將 Linux內核拷貝到 RAM 中,然后跳轉到 RAM 中去執行。不論哪種情況,在跳到 Linux 內核執行之前 CUP的寄存器必須滿足以下條件:r0=0,r1=處理器類型,r2=標記列表在 RAM中的地址。

3. 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 內核的啟動過程。
3.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”來實現對它的調用。該函數返回時,會將返回結構保存在 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 內核啟動完畢。


4. 結論
Linux 內核是一個非常龐大的工程,經過十多年的發展,它已從從最初的幾百 KB 大小發展到現在的幾百兆。清晰的了解它執行的每一個過程是件非常困難的事。但是在嵌入式開發過程中,我們并不需要十分清楚 linux 的內部工作機制,只要適當修改 linux 內核中那些與硬件相關的部分,就可以將 linux 移植到其它目標平臺上。通過對 linux 的啟動過程的分 析,我們可以看出哪些是和硬件相關的,哪些是 linux 內核內部已實現的功能,這樣在移植linux 的過程中便有所針對。而 linux內核的分層設計將使 linux 的移植變得更加容易。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/253465.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/253465.shtml
英文地址,請注明出處:http://en.pswp.cn/news/253465.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

你有沒有靠譜的基因?一個人靠不靠譜,其實就看這三點:“凡事有交代,件件有著落,事事有回音。”...

你有沒有靠譜的基因?一個人靠不靠譜,其實就看這三點:“凡事有交代,件件有著落,事事有回音。” 故事一、做了就忘了? 一天上班后,我讓小王給上級部門送一個材料。 一個小時過去了,沒…

CMOS圖像傳感器——閃爍(flicker)現象

一、概述 閃爍(Flicker),通常發生在室內場景,曝光時間設置如果不是光源能量周期的整數倍,則圖像不同位置處積累的信號強度不同,并呈周期性變化,這是單幀圖像的情況。在視頻序列上,如果滿足一定條件,視頻會出現條紋模式在垂直方向上緩慢移動。 二、形成原因 1、光源 …

一條命令教你安裝centos下面的pip服務

yum install -y python-pip轉載于:https://blog.51cto.com/12131824/2177874

strcpy,memcpy,memset函數實現

strcpy 實現,只能拷貝字符串 char* strcpy(char* des,const char* source) {char* rdes; assert((des ! NULL) && (source ! NULL));while((*des *source)!\0);return r; } memcpy 實現,注意目的地址和源地址重合的情況,以及強制類…

CMOS圖像傳感器——圖像傳感器噪聲

圖像傳感器噪聲取決于圖像傳感器的制作工藝、內部結構及內部補償技術等原因,噪聲反應了圖像傳感器的內部特性。CMOS圖像傳感器基本原理見: CMOS圖像傳感——概述_滄海一升的博客-CSDN博客_cmos圖像傳感器CMOS圖像傳感器基本介紹https://blog.csdn.net/qq_21842097/article/d…

TI Davinci DM6441嵌入式Linux移植攻略——UBL移植篇

目錄(?)[] 一DM6441的Boot過程簡介二DM6441的UBL移植 CCS文件夾Common文件夾GNU文件夾 移植DDR2移植Nand Flash其它 聲明:本文參考網友zjb_integrated的文章《TI Davinci DM6446開發攻略——UBL移植》和《DAVINCI DM365-DM368開發攻略——U-BOOT-2010.12及UBL的移…

python接口自動化測試(二)-requests.get()

環境搭建好后,接下來我們先來了解一下requests的一些簡單使用,主要包括: requests常用請求方法使用,包括:get,postrequests庫中的Session、Cookie的使用其它高級部分:認證、代理、證書驗證、超時…

從一個Android碼農視角回顧2018GDD大會

兩天的GDD大會結束了,很開心,可以看得出,這次Google真的很用心。不但分享的內容質量很高。而且又有得吃又有得玩,還有許多好看的小姐姐,真不妄我請了兩天年假來參加這個大會。先來幾張圖鎮樓 哈哈,跑題了。…

Python3.x和Python2.x的區別[轉]

Python3.x和Python2.x的區別 1.性能 Py3.0運行 pystone benchmark的速度比Py2.5慢30%。Guido認為Py3.0有極大的優化空間,在字符串和整形操作上可 以取得很好的優化結果。 Py3.1性能比Py2.5慢15%,還有很大的提升空間。 2.編碼 Py3.X源碼文件默認使用utf-8…

數字圖像處理——圖像銳化

圖像增強是圖像處理的一個重要環節,早期的圖像處理就是從圖像增強開始的,人們研究對質量低的圖像進行處理以獲得改善質量后的圖像。現今的圖像增強還為后續的圖像處理,如圖像信息提取、圖像識別等,提供更高識別度的圖像。 從圖像處理技術來看,圖像的攝取、編碼、傳輸和處理…

DAVINCI DM365-DM368開發攻略——U-BOOT-2010.12及UBL的移植

從盛夏走到深秋,我們繼續DAVINCI DM365-DM368的開發。說來慚愧,人家51CTO熱情支持本博客,而本人卻一直沒有像其他博客之星一樣頻繁更新博客,心里確實說不過去。管理公司確實很累,有更急的客戶的項目要做,我…

陳天藝1636050045假設跑步者1小時40分鐘35秒跑了24英里。編寫一個程序顯示每小時以公里為單位的平均速度值...

public class AverageSpeed{ public static void main(String[]args){ double speedkm 60/(45.5/14); double speedm speedkm /1.6; system.out.println(“averagespeed ”speedm "m/h") } }轉載于:https://www.cnblogs.com/Archon-Cty/p/7…

EMVA 1288 測試標準

一、概述 如果要對比兩臺相機的性能,我們應該關注哪些參數呢,是焦距、像素、還是光圈大小?這些參數通常廣為人知,并且很容易做出對比。但在一些專業領域,例如機器視覺、自動駕駛等行業,計算機算法對圖像有著獨特的要求,這些標準有些已經跟不上數字成像的發展步伐,而且其…

Spring Boot - 修改Tomcat默認的8080端口

前言 默認情況下,Spring Boot內置的Tomcat服務會使用8080端口啟動,我們可以使用以下任何技巧去更改默認的Tomcat端口; 注:我們可以通過server.port0配置,去自動配置一個未被占用的http端口,由操作系統實現。…

2017年度目標

語言:python,lisp,js,html5技術:android,hadoop數學:復變函數,代數,概率,數論英語:reading其他:金融/經濟/股票,歷史/史記…

嵌入式系統系統升級內核雙備份的實現方式

1.nand flash MTD分區 kernels/linux-2.6.31.1-cavm1/drivers/mtd/maps/xxxxx-flash.c /* MTD partitions: From CNW5602 32MB * mtd0: 0x000C0000 00020000 "bootloader" * mtd1: 0x00040000 00020000 "factory_config" * mt…

SerDes接口——架構與電路

隨著通信技術的飛速發展,高速串行互連以其結構簡單,不需要傳輸同步時鐘,相比并行傳輸有更高數據傳輸效率的優點,成為現代通信和數據傳輸的重要組成部分。隨著對數據傳輸速率要求的不斷提高,SERDES應運而生。它是一種時…

Springboot分模塊開發詳解(2):建立子工程

1.創建base-entity 選中base工程&#xff0c;右鍵創建一個新的maven工程 自動選擇了base這個目錄存放子工程 創建后&#xff0c;pom.xml修改成如下內容&#xff1a; <?xml version"1.0"?> <projectxsi:schemaLocation"http://maven.apache.org/POM/4…

到天宮做客(2017寒假培訓測試壓軸題)

個人QQ&#xff1a;757394026團隊QQ&#xff1a;466373640個人博客&#xff1a;www.doubleq.winc/noi/信息學奧數博客&#xff1a;http://www.cnblogs.com/zwfymqz 題目描述 有一天&#xff0c;我做了個夢&#xff0c;夢見我很榮幸的接到了豬八戒的邀請&#xff0c;到天宮陪他吃…

NAND FLASH分區規劃

由于BOOTLOADRER、PARAMS以及內核、文件系統都在NAND FLASH上&#xff0c;因此分區就得進行統一規劃。系統的NAND FLASH分區依賴于u-boot和Linux內核兩方面的設置。U-Boot中的NAND分區 文件&#xff1a;include/configs/開發板.h這是Phy3250的參數&#xff0c;Phy3250采用32MB的…