- 相關(guān)推薦
ARM Linux啟動過程分析
畢業(yè)論文
摘 要: 嵌入式 Linux 的可移植性使得我們可以在各種電子產(chǎn)品上看到它的身影。對于不
同體系結(jié)構(gòu)的處理器來說Linux的啟動過程也有所不同。本文以S3C2410 ARM處理器為例,
詳細(xì)分析了系統(tǒng)上電后 bootloader的執(zhí)行流程及 ARM Linux的啟動過程。
關(guān)鍵詞:ARM Linux bootloader 啟動過程
中圖分類號:TP316
1. 引 言
Linux 最初是由瑞典赫爾辛基大學(xué)的學(xué)生 Linus Torvalds在1991 年開發(fā)出來的,之后在
GNU的支持下,Linux 獲得了巨大的發(fā)展。雖然 Linux 在桌面 PC 機(jī)上的普及程度遠(yuǎn)不及微
軟的 Windows 操作系統(tǒng),但它的發(fā)展速度之快、用戶數(shù)量的日益增多,也是微軟所不能輕
視的。而近些年來 Linux 在嵌入式領(lǐng)域的迅猛發(fā)展,更是給 Linux 注入了新的活力。
1個嵌入式 Linux 系統(tǒng)從軟件角度看可以分為4個部分[1]
:引導(dǎo)加載程序(bootloader),
Linux 內(nèi)核,文件系統(tǒng),應(yīng)用程序。
其中 bootloader是系統(tǒng)啟動或復(fù)位以后執(zhí)行的第1段代碼,它主要用來初始化處理器及
外設(shè),然后調(diào)用 Linux 內(nèi)核。Linux 內(nèi)核在完成系統(tǒng)的初始化之后需要掛載某個文件系統(tǒng)做
為根文件系統(tǒng)(Root Filesystem)。根文件系統(tǒng)是 Linux 系統(tǒng)的核心組成部分,它可以做為
Linux 系統(tǒng)中文件和數(shù)據(jù)的存儲區(qū)域,通常它還包括系統(tǒng)配置文件和運(yùn)行應(yīng)用軟件所需要的
庫。應(yīng)用程序可以說是嵌入式系統(tǒng)的“靈魂”,它所實現(xiàn)的功能通常就是設(shè)計該嵌入式系統(tǒng)
所要達(dá)到的目標(biāo)。如果沒有應(yīng)用程序的支持,任何硬件上設(shè)計精良的嵌入式系統(tǒng)都沒有實用
意義。
從以上分析我們可以看出 bootloader 和 Linux 內(nèi)核在嵌入式系統(tǒng)中的關(guān)系和作用。
Bootloader在運(yùn)行過程中雖然具有初始化系統(tǒng)和執(zhí)行用戶輸入的命令等作用,但它最根本的
功能就是為了啟動 Linux 內(nèi)核。在嵌入式系統(tǒng)開發(fā)的過程中,很大1部分精力都是花在
bootloader 和 Linux 內(nèi)核的開發(fā)或移植上。如果能清楚的了解 bootloader 執(zhí)行流程和 Linux
的啟動過程,將有助于明確開發(fā)過程中所需的工作,從而加速嵌入式系統(tǒng)的開發(fā)過程。而這
正是本文的所要研究的內(nèi)容。
2. Bootloader
2.1 Bootloader的概念和作用
Bootloader是嵌入式系統(tǒng)的引導(dǎo)加載程序,它是系統(tǒng)上電后運(yùn)行的第1段程序,其作用
類似于 PC 機(jī)上的 BIOS。在完成對系統(tǒng)的初始化任務(wù)之后,它會將非易失性存儲器(通常
是 Flash或 DOC 等)中的Linux 內(nèi)核拷貝到 RAM 中去,然后跳轉(zhuǎn)到內(nèi)核的第1條指令處繼
續(xù)執(zhí)行,從而啟動 Linux 內(nèi)核。
由此可見,bootloader 和 Linux 內(nèi)核有著密不可分的聯(lián)系,要想清楚的了解 Linux內(nèi)核
的啟動過程,我們必須先得認(rèn)識 bootloader的執(zhí)行過程,這樣才能對嵌入式系統(tǒng)的整個啟動
過程有清晰的掌握。
2.2 Bootloader的執(zhí)行過程
不同的處理器上電或復(fù)位后執(zhí)行的第1條指令地址并不相同,對于 ARM 處理器來說,
該地址為 0x00000000。對于1般的嵌入式系統(tǒng),通常把 Flash 等非易失性存儲器映射到這個
地址處,而 bootloader就位于該存儲器的最前端,所以系統(tǒng)上電或復(fù)位后執(zhí)行的第1段程序
便是 bootloader。而因為存儲 bootloader的存儲器不同,bootloader的執(zhí)行過程也并不相同,
下面將具體分析。
嵌入式系統(tǒng)中廣泛采用的非易失性存儲器通常是 Flash,而 Flash 又分為 Nor Flash 和
Nand Flash 兩種。 它們之間的不同在于: Nor Flash 支持芯片內(nèi)執(zhí)行(XIP, eXecute In Place),
這樣代碼可以在Flash上直接執(zhí)行而不必拷貝到RAM中去執(zhí)行。而Nand Flash并不支持XIP,
所以要想執(zhí)行 Nand Flash 上的代碼,必須先將其拷貝到 RAM中去,然后跳到 RAM 中去執(zhí)
行。
實際應(yīng)用中的 bootloader根據(jù)所需功能的不同可以設(shè)計得很復(fù)雜,除完成基本的初始化
系統(tǒng)和調(diào)用 Linux 內(nèi)核等基本任務(wù)外,還可以執(zhí)行很多用戶輸入的命令,比如設(shè)置 Linux 啟
動參數(shù),給 Flash 分區(qū)等;也可以設(shè)計得很簡單,只完成最基本的功能。但為了能達(dá)到啟動
Linux 內(nèi)核的目的,所有的 bootloader都必須具備以下功能[2]
:
1) 初始化 RAM
因為 Linux 內(nèi)核1般都會在 RAM 中運(yùn)行,所以在調(diào)用 Linux 內(nèi)核之前 bootloader 必須
設(shè)置和初始化 RAM,為調(diào)用 Linux內(nèi)核做好準(zhǔn)備。初始化 RAM 的任務(wù)包括設(shè)置 CPU 的控
制寄存器參數(shù),以便能正常使用 RAM 以及檢測RAM 大小等。
2) 初始化串口
串口在 Linux 的啟動過程中有著非常重要的作用,它是 Linux內(nèi)核和用戶交互的方式之
1。Linux 在啟動過程中可以將信息通過串口輸出,這樣便可清楚的了解 Linux 的啟動過程。
雖然它并不是 bootloader 必須要完成的工作,但是通過串口輸出信息是調(diào)試 bootloader 和
Linux 內(nèi)核的強(qiáng)有力的工具,所以1般的 bootloader 都會在執(zhí)行過程中初始化1個串口做為
調(diào)試端口。
3) 檢測處理器類型
Bootloader在調(diào)用 Linux內(nèi)核前必須檢測系統(tǒng)的處理器類型,并將其保存到某個常量中
提供給 Linux 內(nèi)核。Linux 內(nèi)核在啟動過程中會根據(jù)該處理器類型調(diào)用相應(yīng)的初始化程序。
4) 設(shè)置 Linux啟動參數(shù)
Bootloader在執(zhí)行過程中必須設(shè)置和初始化 Linux 的內(nèi)核啟動參數(shù)。目前傳遞啟動參數(shù)
主要采用兩種方式:即通過 struct param_struct 和struct tag(標(biāo)記列表,tagged list)兩種結(jié)
構(gòu)傳遞。struct param_struct 是1種比較老的參數(shù)傳遞方式,在 2.4 版本以前的內(nèi)核中使用較
多。從 2.4 版本以后 Linux 內(nèi)核基本上采用標(biāo)記列表的方式。但為了保持和以前版本的兼容
性,它仍支持 struct param_struct 參數(shù)傳遞方式,只不過在內(nèi)核啟動過程中它將被轉(zhuǎn)換成標(biāo)
記列表方式。
標(biāo)記列表方式是種比較新的參數(shù)傳遞方式,它必須以 ATAG_CORE 開始,并以
ATAG_NONE 結(jié)尾。中間可以根據(jù)需要加入其他列表。Linux內(nèi)核在啟動過程中會根據(jù)該啟
動參數(shù)進(jìn)行相應(yīng)的初始化工作。
[NextPage]
5) 調(diào)用 Linux內(nèi)核映像
Bootloader完成的最后1項工作便是調(diào)用 Linux內(nèi)核。如果 Linux 內(nèi)核存放在 Flash 中,
并且可直接在上面運(yùn)行(這里的 Flash 指 Nor Flash),那么可直接跳轉(zhuǎn)到內(nèi)核中去執(zhí)行。
但由于在 Flash 中執(zhí)行代碼會有種種限制,而且速度也遠(yuǎn)不及 RAM 快,所以1般的嵌
入式系統(tǒng)都是將 Linux內(nèi)核拷貝到 RAM 中,然后跳轉(zhuǎn)到 RAM 中去執(zhí)行。
不論哪種情況,在跳到 Linux 內(nèi)核執(zhí)行之前 CUP的寄存器必須滿足以下條件:r0=0,
r1=處理器類型,r2=標(biāo)記列表在 RAM中的地址。
3. Linux內(nèi)核的啟動過程
在 bootloader將 Linux 內(nèi)核映像拷貝到 RAM 以后,可以通過下例代碼啟動 Linux 內(nèi)核:
call_linux(0, machine_type, kernel_params_base)。
其中,machine_tpye 是 bootloader檢測出來的處理器類型, kernel_params_base 是啟動參
數(shù)在 RAM 的地址。通過這種方式將 Linux 啟動需要的參數(shù)從 bootloader傳遞到內(nèi)核。
Linux 內(nèi)核有兩種映像:1種是非壓縮內(nèi)核,叫 Image,另1種是它的壓縮版本,叫
zImage。根據(jù)內(nèi)核映像的不同,Linux 內(nèi)核的啟動在開始階段也有所不同。zImage 是 Image
經(jīng)過壓縮形成的,所以它的大小比 Image 小。但為了能使用 zImage,必須在它的開頭加上
解壓縮的代碼,將 zImage 解壓縮之后才能執(zhí)行,因此它的執(zhí)行速度比 Image 要慢。但考慮
到嵌入式系統(tǒng)的存儲空容量1般比較小,采用 zImage 可以占用較少的存儲空間,因此犧牲
1點(diǎn)性能上的代價也是值得的。所以1般的嵌入式系統(tǒng)均采用壓縮內(nèi)核的方式。
對于 ARM 系列處理器來說,zImage 的入口程序即為 arch/arm/boot/compressed/head.S。
它依次完成以下工作:開啟 MMU 和 Cache,調(diào)用 decompress_kernel()解壓內(nèi)核,最后通過
調(diào)用 call_kernel()進(jìn)入非壓縮內(nèi)核 Image 的啟動。下面將具體分析在此之后 Linux 內(nèi)核的啟
動過程。
3.1 Linux內(nèi)核入口
Linux 非壓縮內(nèi)核的入口位于文件/arch/arm/kernel/head-armv.S 中的 stext 段。該段的基
地址就是壓縮內(nèi)核解壓后的跳轉(zhuǎn)地址。如果系統(tǒng)中加載的內(nèi)核是非壓縮的 Image,那么
bootloader將內(nèi)核從 Flash中拷貝到 RAM 后將直接跳到該地址處,從而啟動 Linux 內(nèi)核。
不同體系結(jié)構(gòu)的 Linux 系統(tǒng)的入口文件是不同的,而且因為該文件與具體體系結(jié)構(gòu)有
關(guān),所以1般均用匯編語言編寫[3]
。對基于 ARM 處理的 Linux 系統(tǒng)來說,該文件就是
head-armv.S。該程序通過查找處理器內(nèi)核類型和處理器類型調(diào)用相應(yīng)的初始化函數(shù),再建
立頁表,最后跳轉(zhuǎn)到 start_kernel()函數(shù)開始內(nèi)核的初始化工作。
檢測處理器內(nèi)核類型是在匯編子函數(shù)__lookup_processor_type中完成的。通過以下代碼
可實現(xiàn)對它的調(diào)用:
bl __lookup_processor_type。
__lookup_processor_type調(diào)用結(jié)束返回原程序時,會將返回結(jié)果保存到寄存器中。其中
r8 保存了頁表的標(biāo)志位,r9 保存了處理器的 ID 號,r10 保存了與處理器相關(guān)的 stru
proc_info_list 結(jié)構(gòu)地址。
檢測處理器類型是在匯編子函數(shù) __lookup_architecture_type 中完成的。與
__lookup_processor_type類似,它通過代碼:“bl __lookup_processor_type”來實現(xiàn)對它的調(diào)
用。該函數(shù)返回時,會將返回結(jié)構(gòu)保存在 r5、r6 和 r7 3個寄存器中。其中 r5 保存了 RAM
的起始基地址,r6 保存了 I/O基地址,r7 保存了 I/O的頁表偏移地址。
當(dāng)檢測處理器內(nèi)核和處理器類型結(jié)束后,將調(diào)用__create_page_tables 子函數(shù)來建立頁
表,它所要做的工作就是將 RAM 基地址開始的 4M 空間的物理地址映射到 0xC0000000 開
始的虛擬地址處。對筆者的 S3C2410 開發(fā)板而言,RAM 連接到物理地址 0x30000000 處,
當(dāng)調(diào)用 __create_page_tables 結(jié)束后 0x30000000 ~ 0x30400000 物理地址將映射到
0xC0000000~0xC0400000 虛擬地址處。
當(dāng)所有的初始化結(jié)束之后,使用如下代碼來跳到 C 程序的入口函數(shù) start_kernel()處,開
始之后的內(nèi)核初始化工作:
b SYMBOL_NAME(start_kernel)
3.2 start_kernel函數(shù)
start_kernel是所有 Linux 平臺進(jìn)入系統(tǒng)內(nèi)核初始化后的入口函數(shù),它主要完成剩余的與
硬件平臺相關(guān)的初始化工作,在進(jìn)行1系列與內(nèi)核相關(guān)的初始化后,調(diào)用第1個用戶進(jìn)程-
init 進(jìn)程并等待用戶進(jìn)程的執(zhí)行,這樣整個 Linux 內(nèi)核便啟動完畢。該函數(shù)所做的具體工作
有[4][5]
:
1) 調(diào)用 setup_arch()函數(shù)進(jìn)行與體系結(jié)構(gòu)相關(guān)的第1個初始化工作;
對不同的體系結(jié)構(gòu)來說該函數(shù)有不同的定義。對于 ARM 平臺而言,該函數(shù)定義在
arch/arm/kernel/Setup.c。它首先通過檢測出來的處理器類型進(jìn)行處理器內(nèi)核的初始化,然后
通過 bootmem_init()函數(shù)根據(jù)系統(tǒng)定義的 meminfo 結(jié)構(gòu)進(jìn)行內(nèi)存結(jié)構(gòu)的初始化,最后調(diào)用
paging_init()開啟 MMU,創(chuàng)建內(nèi)核頁表,映射所有的物理內(nèi)存和 IO空間。
2) 創(chuàng)建異常向量表和初始化中斷處理函數(shù);
3) 初始化系統(tǒng)核心進(jìn)程調(diào)度器和時鐘中斷處理機(jī)制;
4) 初始化串口控制臺(serial-console);
ARM-Linux 在初始化過程中1般都會初始化1個串口做為內(nèi)核的控制臺,這樣內(nèi)核在
啟動過程中就可以通過串口輸出信息以便開發(fā)者或用戶了解系統(tǒng)的啟動進(jìn)程。
5) 創(chuàng)建和初始化系統(tǒng) cache,為各種內(nèi)存調(diào)用機(jī)制提供緩存,包括;動態(tài)內(nèi)存分配,虛擬文
件系統(tǒng)(VirtualFile System)及頁緩存。
6) 初始化內(nèi)存管理,檢測內(nèi)存大小及被內(nèi)核占用的內(nèi)存情況;
7) 初始化系統(tǒng)的進(jìn)程間通信機(jī)制(IPC);
當(dāng)以上所有的初始化工作結(jié)束后,start_kernel()函數(shù)會調(diào)用 rest_init()函數(shù)來進(jìn)行最后的
初始化,包括創(chuàng)建系統(tǒng)的第1個進(jìn)程-init 進(jìn)程來結(jié)束內(nèi)核的啟動。Init 進(jìn)程首先進(jìn)行1系
列的硬件初始化,然后通過命令行傳遞過來的參數(shù)掛載根文件系統(tǒng)。最后 init 進(jìn)程會執(zhí)行用
戶傳遞過來的“init=”啟動參數(shù)執(zhí)行用戶指定的命令,或者執(zhí)行以下幾個進(jìn)程之1:
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)。
當(dāng)所有的初始化工作結(jié)束后,cpu_idle()函數(shù)會被調(diào)用來使系統(tǒng)處于閑置(idle)狀態(tài)并
等待用戶程序的執(zhí)行。至此,整個 Linux 內(nèi)核啟動完畢。
[NextPage]
4. 結(jié)論
Linux 內(nèi)核是1個非常龐大的工程,經(jīng)過10多年的發(fā)展,它已從從最初的幾百 KB 大小
發(fā)展到現(xiàn)在的幾百兆。清晰的了解它執(zhí)行的每1個過程是件非常困難的事。但是在嵌入式開
發(fā)過程中,我們并不需要10分清楚 linux 的內(nèi)部工作機(jī)制,只要適當(dāng)修改 linux 內(nèi)核中那些
與硬件相關(guān)的部分,就可以將 linux 移植到其它目標(biāo)平臺上。通過對 linux 的啟動過程的分
析,我們可以看出哪些是和硬件相關(guān)的,哪些是 linux 內(nèi)核內(nèi)部已實現(xiàn)的功能,這樣在移植
linux 的過程中便有所針對。而 linux內(nèi)核的分層設(shè)計將使 linux 的移植變得更加容易。
參考文獻(xiàn)
[1] 詹榮開.嵌入式系統(tǒng)bootloader技術(shù)內(nèi)幕[EB/OL]. 2003.12.
[2] Russell King.Booting ARM Linux[Z].Linux Documentation.May 2002
[3] 劉淼.嵌入式系統(tǒng)接口設(shè)計與Linux驅(qū)動程序開發(fā)[M].北京航空航天大學(xué)出版社.2006.6
[4] William Gatliff. The Linux 2.4 Kernel’s Startup Procedure[DB/CD]. 2002 Embedded System Conference San
Francisco,March..2002
[5] Claudia Salzberg Rodriguez,Gordon Fischer,Steven Smolski.Linux內(nèi)核編程[M].陳莉君,賀炎,劉霞林.機(jī)
械工業(yè)出版社.2006.7
ARM Linux Start-up Procedure Analysis
Yao Chengqiang Sun Wensheng
School of Telecommunication Engineering of Beijing University of Posts and
Telecommunications
Abstract
We can see embedded Linux in kinds of electronic products because of its portability. Linux’s
start-up procedure for different processors is also different. This paper provides the analysis of
bootloader execution process and Linux kernel start-up procedure - taking the S3C2410 ARM
processor as ex
[1]
【ARM Linux啟動過程分析】相關(guān)文章:
嵌入式ARM-Linux水產(chǎn)養(yǎng)殖監(jiān)控系統(tǒng)的設(shè)計03-07
ARM嵌入式在通信領(lǐng)域的應(yīng)用分析03-03
ARM7在嵌入式應(yīng)用中啟動程序的實現(xiàn)03-19
基于ARM-μCLinux嵌入式系統(tǒng)啟動引導(dǎo)的實現(xiàn)03-20
基于ARM9微處理器與Linux操作系統(tǒng)的SD驅(qū)動開發(fā)03-07
職務(wù)分析—過程與方法初探03-18
職務(wù)分析―過程與方法初探12-07
最新SCI論文發(fā)表過程分析12-03