1. 程式人生 > >第3階段——內核啟動分析之創建si工程和啟動內核分析(3)

第3階段——內核啟動分析之創建si工程和啟動內核分析(3)

otl cin 由於 noi 找到 常用工具 isa maintain inf

目標:

(1)創建Source Insight 工程,方便後面分析如何啟動內核的

(2)分析uboot傳遞參數,鏈接腳本如何進入stext的

(3) 分析stext函數如何啟動內核

1 創建內核source sight 工程

1.1 點擊 “add all” 添加所有文件,後面再慢慢刪去Arch目錄和Include目錄中與2440芯片沒用的文件。

1.2 點擊Remove Tree 刪除Arch文件夾,再添加與2440相關的硬件核心代碼以及其它公用的代碼

  Arch:包含了平臺,處理器相關的代碼,並包括boot文件夾。

1.2.1 點擊Add Tree添加以下子目錄:

linux-2.6.22.6/arch/arm/boot (啟動配置文件)

linux-2.6.22.6/arch/arm/common (公共文件)

linux-2.6.22.6/arch/arm/configs (配置文件)

linux-2.6.22.6/arch/arm/kernel (內核文件)

linux-2.6.22.6/arch/arm/lib (固件庫)

linux-2.6.22.6/arch/arm/mach-s3c2440 (machine 設備,2440設備庫)

linux-2.6.22.6/arch/arm/mach-s3c2410 (2440中部分調用了2410設備庫)

linux-2.6.22.6/arch/arm/Mm (內存管理文件)

linux-2.6.22.6/arch/arm/nwfpe

linux-2.6.22.6/arch/arm/oprofile (性能分析工具文件)

linux-2.6.22.6/arch/arm/plat-s3c24xx (s3c24系列平臺文件)

linux-2.6.22.6/arch/arm/tools (常用工具文件)

linux-2.6.22.6/arch/arm/vfp (浮點運算文件)

1.3 點擊Remove Tree 刪除Include文件夾,再添加與2440相關的頭文件

Include: 包括了核心的大多數include文件,另外對於每種支持的體系結構分別有一個子目錄

1.3.1 點擊Add All 添加 linux-2.6.22.6/include/asm-arm目錄下文件(不包含子目錄所有文件),如下圖所示:

1.3.2 點擊Add Tree添加以下子目錄:

linux-2.6.22.6/include/asm-arm/arch-s3c2410 (2410處理器架構)

linux-2.6.22.6/include/asm-arm/hardware (硬件相關頭文件)

linux-2.6.22.6/include/asm-arm/mach (具體的設備文件)

linux-2.6.22.6/include/asm-arm/plat-s3c24xx (s3c24系列平臺頭文件)

1.3.3返回到 linux-2.6.22.6/include目錄下,點擊Add Tree添加除了asm-xx開頭的其它通用文件:

linux-2.6.22.6/include/acpi (高級配置與電源接口文件)

linux-2.6.22.6/include/config

linux-2.6.22.6/include/crypto

linux-2.6.22.6/include/keys

linux-2.6.22.6/include/linux

linux-2.6.22.6/include/math-emu

linux-2.6.22.6/include/mtd

linux-2.6.22.6/include/net

linux-2.6.22.6/include/pcmcia

linux-2.6.22.6/include/rdma

linux-2.6.22.6/include/rxrpc

linux-2.6.22.6/include/scsi

linux-2.6.22.6/include/sound

linux-2.6.22.6/include/video

1.4 最後點擊synchronize files 創建source insight工程

2.內核啟動之分析uboot傳遞參數和鏈接腳本

2.1 內核在uboot啟動之前是進入do_boom_linux函數

(do_boom_linux函數啟動內核詳解:http://www.cnblogs.com/lifexy/p/7310279.html)

do_boom_linux代碼如下:

theKernel = (void (*)(int, int, unsigend int))0x30008000;

// 設置theKernel地址=0x30008000,用於後面啟動內核

/*設置atag參數*/

setup_start_tag (void); //從0X30000100地址處開始保存start_tag數據,

setup_memory_tags (void); //保存memory_tag數據,讓內核知道內存多大 setup_commandline_tag (“boottargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0”);

/*保存命令行bootargs參數,讓內核知道根文件系統位置在/dev/mtdblock3,指定開機運行第一個腳本/linuxrc,指定打印串口0*/

setup_end_tag (void); //初始化tag結構體結束

theKernel(0,362,0x300000100); //362:機器ID, 0x300000100: params(atag)參數地址

/*傳遞參數跳轉執行到0x30008000啟動內核, */

/*相當於: mov r0,#0 */

/*ldr r1,=362 */

/*ldr r2,= 0x300000100 */

/*mov pc,#0x30008000 */

TAG參數內存布局圖如下:

2.2然後來分析鏈接腳本arm/arm/kernel/vmlinux.lds

OUTPUT_ARCH(arm) //設置輸出文件的體系架構

ENTRY(stext) //設置stext全局符號為入口地址

jiffies = jiffies_64;

SECTIONS

{

. = (0xc0000000) + 0x00008000;

/*設置內核虛擬地址=0xc0000000+0x00008000 */

.text.head : {

_stext = .;

_sinittext = .;

*(.text.head) //添加所有.text.head段

}

.init : { /* Init code and data */

*(.init.text)

_einittext = .;

__proc_info_begin = .;

*(.proc.info.init) //存放處理器相關的信息初始化

__proc_info_end = .;

__arch_info_begin = .;

*(.arch.info.init) //存放與架構(arch)相關的信息(info)初始化

__arch_info_end = .;

... ...

得出linux內核啟動第一步是進入stext入口函數

那麽stext入口函數又在哪裏定義的呢?

搜索ENTRY(stext)得出,它在arch/arm/kernel/head.S中,

stext函數的在前置條件是:MMU, D-cache, 關閉; r0 = 0, r1 = machine nr, r2 = atags prointer.代碼如下:

/*

* Kernel startup entry point. //內核 啟動 入口 點

* ---------------------------

*

* This is normally called from the decompressor code. The requirements

* are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,

/* 前置條件是:MMU, D-cache, 關閉; r0 = 0, r1 =機器ID, r2 =atag參數地址.*/

* r1 = machine nr.

* This code is mostly position independent, so if you link the kernel at

* 0xc0008000, you call this at __pa(0xc0008000).

* See linux/arch/arm/tools/mach-types for the complete list of machine

* numbers for r1.

*

* We‘re trying to keep crap to a minimum; DO NOT add any machine specific

* crap here - that‘s what the boot loader (or in extreme, well justified

* circumstances, zImage) is for.

*/

section ".text.head", "ax" /* 定義一個.text.head,段的屬性a是允許段,x可 執行 */

.type stext, %function /*定義了由bootloader進入內核的入口stext */

ENTRY(stext)

... ...

它的功能是獲取處理器類型和機器類型信息,並創建臨時的頁表,然後開啟MMU功能(因為內核代碼中全是0XCxxxxxxx地址),並跳進第一個C語言函數start_kernel。

所以,內核啟動後第一步是 進入arch/arm/kernel/head.S的stext函數中.

3內核啟動之stext函數分析(arch/arm/kernel/head.S)

stext函數內容,如下圖:

(1) 關閉irq和fiq,設置svc管理模式

(2)判斷是或支持這個CPU

(3)判斷是否支持這個單板(通過uboot傳入的機器ID判斷)

(4)創建頁表,為後面的MMU做準備

(5) 使能MMU並跳到__switch_data處,復制數據段,清除bss段,設置棧,調用start_kernel第一個C函數

stext函數代碼如下:

section ".text.head", "ax" /* 定義一個.text.head,段的屬性a是允許段,x可 執行 */

.type stext, %function /*定義了由bootloader進入內核的入口stext */

ENTRY(stext) //入口地址stext函數

/*msr cpsr_c,0xD3 關閉irq和fiq,設置svc管理模式 */

msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode

@ and irqs disabled

/*獲取cpu ID */

mrc p15, 0, r9, c0, c0 @ get processor id

/*查找內核是否支持r9這個cpuID,若不支持r5=0,支持r5=處理器ID*/

bl __lookup_processor_type @ r5=procinfo r9=cpuid

movs r10, r5 @ invalid processor (r5=0)?

/*不支持則跳轉到__error_p,死循環*/

beq __error_p @ yes, error ‘p‘

/*查找內核是否支持uboot傳入的r1機器ID(362),若不支持r5=0,支持r5=機器ID*/

bl __lookup_machine_type @ r5=machinfo

movs r8, r5 @ invalid machine (r5=0)?

/*不支持則跳轉到__error_a,死循環*/

beq __error_a @ yes, error ‘a‘

/*跳轉到__create_page_tables 創建頁表,為後面的MMU做準備*/

bl __create_page_tables

3.1 分析上面”__lookup_machine_type函數”是如何通過查找r1機器ID(362)是或等於單板機器ID的,代碼如下(位於arch/arm/kernel):

3: .long .

.long __arch_info_begin

.long __arch_info_end

__lookup_machine_type:

/*(b:bank)r3=後面的符號3處. 虛擬地址,由於mmu未啟動,所以=物理地址*/

adr r3, 3b

ldmia r3, {r4, r5, r6}/* r4=3b處的虛擬地址 ,r5=__arch_info_begin處的虛擬地址,r6=__arch_info_end處的虛擬地址 */

sub r3, r3, r4 @ get offset between virt&phys //得到虛擬地址(virtual)與物理地址(physical)的偏移值

add r5, r5, r3 @ convert virt addresses to //找到arch_info_begin處的物理地址

add r6, r6, r3 @ physical address space //找到__arch_info_end處的物理地址

1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type //r3=r5+偏移地址裏內容= 單板機器ID

teq r3, r1 //判斷r1(365)和單板機器ID是否相等,相等說明內核支持該單板

beq 2f @ found //相等則直接返回到stext函數繼續執行

add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc

cmp r5, r6

blo 1b

mov r5, #0 @ unknown machine //r5=0,不支持該單板

2: mov pc, lr //退出

其中__arch_info_begin和__arch_info_end是在鏈接腳本arm/arm/kernel/vmlinux.lds中定義:

305 __arch_info_begin = .; //__arch_info_begin=信息開始地址

306 *(.arch.info.init) //存放架構相關的信息初始化

307 __arch_info_end = .; //__arch_info_end =信息結束地址

通過grep “.arch.info.init” -nR其中.arch.info.init段在include/asm-ram/mach/arch.h中53行處定義:

代碼如下:

#define MACHINE_START(_type,_name) //定義了一個 MACHINE_START宏, _type:CPU名字,_name:開發板名字

static const struct machine_desc __mach_desc_##_type \ //##:連詞符號

__used \

__attribute__((__section__(".arch.info.init"))) = { \ //強制將 MACHINE_START宏裏的成員組成.arch.info.init段

.nr = MACH_TYPE_##_type, \

.name = _name,

#define MACHINE_END \ //定義宏MACHINE_END= };

};

搜索MACHINE_START宏發現arch/arm目錄下每個文件都使用了這個宏定義,由於我們選用的是S3C2440和SMDKs3c2440

所以得出使用宏#define MACHINE_START(_type,_name)的是:

1 MACHINE_START(S3C2440, "SMDK2440")

2 /* Maintainer: Ben Dooks <[email protected]> */

3 .phys_io = S3C2410_PA_UART,

4 .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,

5 .boot_params = S3C2410_SDRAM_PA + 0x100,

6

7 .init_irq = s3c24xx_init_irq,

8 .map_io = smdk2440_map_io,

9 .init_machine = smdk2440_machine_init,

10 .timer = &s3c24xx_timer,

11 MACHINE_END

其中上面第1段使用的宏就是之前在arch.h中定義的MACHINE_START(_type,_name),其中_type替換成S3C2440, _name替換成"SMDK2440".

第11段的MACHINE_END在被arch.h中定義為等於“};”

最終

將宏定義代入上面MACHINE_START(S3C2440, "SMDK2440")處的11段代碼中,展開如下所示:

static const struct machine_desc __mach_desc_ S3C2440 //定義一個machine_desc型結構體,名字為__mach_desc_ S3C2440

__used \

__attribute__((__section__(".arch.info.init"))) = { //強制將MACHINE_START宏裏的成員組成.arch.info.init段

.nr = MACH_TYPE_ S3C2440, // __mach_desc_ S3C2440.nr= MACH_TYPE_ S3C2440 機器ID

.name = "SMDK2440", //__mach_desc_ S3C2440. name = "SMDK2440" 機器ID名字

/* Maintainer: Ben Dooks <[email protected]> */

/*.phys_io =0X50000000,存放物理IO基地址*/

.phys_io = S3C2410_PA_UART,

/* .io_pg_offst存放物理IO偏移地址*/

.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,

/*其中S3C2410_SDRAM_PA=0X30000000, .boot_params= 0X30000100,所以我們uboot

傳入的atag參數地址必須是0X30000100*/

.boot_params = S3C2410_SDRAM_PA + 0x100,

.init_irq = s3c24xx_init_irq,

.map_io = smdk2440_map_io,

.init_machine = smdk2440_machine_init,

.timer = &s3c24xx_timer,

}; // MACHINE_END替換成 };

從上面可以看出主要是初始化了machine_desc結構體,然後將其放在.arch.info.init段上,讓 內核啟動時將uboot傳遞進來的ID與這個段上的ID進行比較是否吻合,支不支持該單板初始化。

因為不同的單板都有不同MACHINE_START(_type,_name)以及硬件上可能有差別,所以需要初始化的內容也不同

返回stext函數中繼續往下看:

ldr r13, __switch_data @ address to jump to after

//MMU使能之後會跳轉(jump)到__switch_data

@ mmu has been enabled

adr lr, __enable_mmu @ return (PIC) address//使能MMU

add pc, r10, #PROCINFO_INITFUNC

為什麽使能MMU後會跳轉到__switch_data?

在__enable_mmu函數中最後面可以看到使能MMU後,會將r13賦給PC,跳轉到了__switch_data:

... ....

mov r3, r3

mov r3, r3

mov pc, r13

跳轉到了__switch_data中,代碼如下, __switch_data 是__mmap_switched的虛擬地址,然後跳轉到__mmap_switched中:

__switch_data:

.long __mmap_switched //進入__mmap_switched函數

.long __data_loc @ r4

.long __data_start @ r5

.long __bss_start @ r6

.long _end @ r7

.long @ r4

.long __machine_arch_type @ r5

.long cr_alignment @ r6

.long init_thread_union + THREAD_START_SP @ sp

__mmap_switched:

adr r3, __switch_data + 4 //r3=__data_loc段內容

/*其中

__data_loc 是數據存放的位置

__data_start 是數據開始的位置

__bss_start 是bss開始的位置

_end 是bss結束的位置, 也是內核結束的位置

這幾個符號都在arch/arm/kernel/vmlinux.lds中定義的變量

*/

ldmia r3!, {r4, r5, r6, r7} //r4=__data_loc , r5=__data_start , r6=__bss_start ,r7=_end , r3= processor_id

cmp r4, r5 // __data_loc段不等於__data_start段則執行下面1處的內容

1: cmpne r5, r6 // 比較r5(__data_start段)和r6(__bss_start段)

ldrne fp, [r4], #4

strne fp, [r5], #4 //str r4,[r5] 將整個段裏內容從 __data_loc段 復制到__data_start段

bne 1b //r5不等於r6,則繼續復制

mov fp, #0 @ Clear BSS (and zero fp) //清除bss段

1: cmp r6, r7 //比較r6(__bss_start t段)和r7(_end段)

strcc fp, [r6],#4 // 清除bss段

bcc 1b //(cc:小於)r6<r7,繼續清除bss段

ldmia r3, {r4, r5, r6, sp} //r4=r3= processor_id, r5=__machine_arch_type,r6= cr_alignment,

//設置棧sp= init_thread_union + THREAD_START_SP,方便執行C函數

str r9, [r4] @ Save processor ID

str r1, [r5] @ Save machine type

bic r4, r0, #CR_A @ Clear ‘A‘ bit

stmia r6, {r0, r4} @ Save control register values

b start_kernel

最終跳到start_kernel函數,此函數代碼用純C來實現,它會調用各個平臺的相關初始化函數,接下來分析start_kernel函數

第3階段——內核啟動分析之創建si工程和啟動內核分析(3)