新聞中心

EEPW首頁 > 嵌入式系統(tǒng) > 設計應用 > 嵌入式 arm平臺kernel啟動第二階段分析

嵌入式 arm平臺kernel啟動第二階段分析

作者: 時間:2016-11-09 來源:網(wǎng)絡 收藏
接著上面的分析,第一階段的代碼跳轉后,會進入第二階段的代碼。

第二階段的代碼是從archarmkernelhead.S開始的。

本文引用地址:http://butianyuan.cn/article/201611/317682.htm

內(nèi)核啟動第二階段主要完成的工作有,cpuID檢查,machineID(也就是開發(fā)板ID)檢查,創(chuàng)建初始化頁表,設置C代碼運行環(huán)境,跳轉到內(nèi)核第一個真正的C函數(shù)startkernel開始執(zhí)行。

這一階段涉及到兩個重要的結構體:

(1)一個是structproc_info_list主要描述CPU相關的信息,定義在文件archarmincludeasmprocinfo.h中,與其相關的函數(shù)及變量在文件arch/arm/mm/proc_arm920.S中被定義和賦值。

(2)另一個結構體是描述開發(fā)板或者說機器信息的結構體structmachine_desc,定義在archarmincludeasmmacharch.h文件中,其函數(shù)的定義和變量的賦值在板極相關文件arch/arm/mach-s3c2410/mach-smdk2410.c中實現(xiàn),這也是內(nèi)核移植非常重要的一個文件。

該階段一般由前面的解壓縮代碼調用,進入該階段要求:

MMU=off,D-cache=off,I-cache=dontcare,r0=0,r1=machineid.

所有的機器ID列表保存在arch/arm/tools/mach-types文件中,在編譯時會將這些機器ID按照統(tǒng)一的格式鏈接到基本內(nèi)核映像文件vmlinux的__arch_info_begin和__arch_info_end之間的段中。存儲格式定義在include/asm-arm/mach/arch.h文件中的結構體structmachine_desc{}。這兩個結構體的內(nèi)容最終會被連接到基本內(nèi)核映像vmlinux中的兩個段內(nèi),分別是*(.proc.info.init)和*(.arch.info.init),可以參考下面的連接腳本。

鏈接腳本:arch/arm/kernel/vmlinux.lds

*鏈接腳本

SECTIONS

{

.=TEXTADDR;

.init:{/*初始化代碼段*/

_stext=.;

_sinittext=.;

*(.init.text)

_einittext=.;

__proc_info_begin=.;

*(.proc.info.init)

__proc_info_end=.;

__arch_info_begin=.;

*(.arch.info.init)

__arch_info_end=.;

__tagtable_begin=.;

*(.taglist.init)

__tagtable_end=.;

.=ALIGN(16);

__setup_start=.;

*(.init.setup)

__setup_end=.;

__early_begin=.;

*(.early_param.init)

__early_end=.;

__initcall_start=.;

*(.initcall1.init)

*(.initcall2.init)

*(.initcall3.init)

*(.initcall4.init)

*(.initcall5.init)

*(.initcall6.init)

*(.initcall7.init)

__initcall_end=.;

__con_initcall_start=.;

*(.con_initcall.init)

__con_initcall_end=.;

__security_initcall_start=.;

*(.security_initcall.init)

__security_initcall_end=.;

.=ALIGN(32);

__initramfs_start=.;

usr/built-in.o(.init.ramfs)

__initramfs_end=.;

.=ALIGN(64);

__per_cpu_start=.;

*(.data.percpu)

__per_cpu_end=.;

#ifndefCONFIG_XIP_KERNEL

__init_begin=_stext;

*(.init.data)

.=ALIGN(4096);

__init_end=.;

#endif

}

*鏈接腳本

下面開始代碼archarmkernelhead.S的注釋:

開始分析前先看下一點基礎知識:

1.kernel運行的史前時期和內(nèi)存布局

arm平臺下,zImage.bin壓縮鏡像是由bootloader加載到物理內(nèi)存,然后跳到zImage.bin里一段程序,它專門于將被壓縮的kernel解壓縮到KERNEL_RAM_PADDR開始的一段內(nèi)存中,接著跳進真正的kernel去執(zhí)行。該kernel的執(zhí)行起點是stext函數(shù),定義于arch/arm/kernel/head.S。此時內(nèi)存的布局如下圖所示

在開發(fā)板3c2410中,SDRAM連接到內(nèi)存控制器的Bank6中,它的開始內(nèi)存地址是0x30000000,大小為64M,即0x20000000。ARMLinuxkernel將SDRAM的開始地址定義為PHYS_OFFSET。經(jīng)bootloader加載kernel并由自解壓部分代碼運行后,最終kernel被放置到KERNEL_RAM_PADDR(=PHYS_OFFSET+TEXT_OFFSET,即0x30008000)地址上的一段內(nèi)存,經(jīng)此放置后,kernel代碼以后均不會被移動。

在進入kernel代碼前,即bootloader和自解壓縮階段,ARM未開啟MMU功能。因此kernel啟動代碼一個重要功能是設置好相應的頁表,并開啟MMU功能。為了支持MMU功能,kernel鏡像中的所有符號,包括代碼段和數(shù)據(jù)段的符號,在鏈接時都生成了它在開啟MMU時,所在物理內(nèi)存地址映射到的虛擬內(nèi)存地址。

以armkernel第一個符號(函數(shù))stext為例,在編譯鏈接,它生成的虛擬地址是0xc0008000,而放置它的物理地址為0x30008000(還記得這是PHYS_OFFSET+TEXT_OFFSET嗎?)。實際上這個變換可以利用簡單的公式進行表示:va=pa–PHYS_OFFSET+PAGE_OFFSET。Armlinux最終的kernel空間的頁表,就是按照這個關系來建立。

之所以較早提及armlinux的內(nèi)存映射,原因是在進入kernel代碼,里面所有符號地址值為清一色的0xCXXXXXXX地址,而此時ARM未開啟MMU功能,故在執(zhí)行stext函數(shù)第一條執(zhí)行時,它的PC值就是stext所在的內(nèi)存地址(即物理地址,0x30008000)。因此,下面有些代碼,需要使用地址無關技術。

__HEAD/*該宏定義了下面的代碼位于".head.text"段內(nèi)*/

.typestext,%function/*聲明stext為函數(shù)*/

ENTRY(stext)/*第二階段的入口地址*/

setmodePSR_F_BIT|PSR_I_BIT|SVC_MODE,r9@ensuresvcmodeandirqsdisabled進入超級權限模式,關中斷

/*從協(xié)處理器CP15,C0讀取CPUID,然后在__proc_info_begin開始的段中進行查找,如果找到,則返回對應處理器相關結構體在物理地址空間的首地址到r5,最后保存在r10中*/

mrcp15,0,r9,c0,c0@getprocessorid取出cpuid

bl__lookup_processor_type@r5=procinfor9=cpuid

//

__lookup_processor_type函數(shù)的具體解析開始(archarmkernelhead-common.S)

//

在講解該程序段之前先來看一些相關知識,內(nèi)核所支持的每一種CPU類型都由結構體proc_info_list來描述。

該結構體在文件arch/arm/include/asm/procinfo.h中定義:

structproc_info_list{

unsignedintcpu_val;

unsignedintcpu_mask;

unsignedlong__cpu_mm_mmu_flags;/*usedbyhead.S*/

unsignedlong__cpu_io_mmu_flags;/*usedbyhead.S*/

unsignedlong__cpu_flush;/*usedbyhead.S*/

constchar*arch_name;

constchar*elf_name;

unsignedintelf_hwcap;

constchar*cpu_name;

structprocessor*proc;

structcpu_tlb_fns*tlb;

structcpu_user_fns*user;

structcpu_cache_fns*cache;

};

對于arm920來說,其對應結構體在文件linux/arch/arm/mm/proc-arm920.S中初始化。

.section".proc.info.init",#alloc,#execinstr/*定義了一個段,下面的結構體存放在該段中*/

.type__arm920_proc_info,#object/*聲明一個結構體對象*/

__arm920_proc_info:/*為該結構體賦值*/

.long0x41009200

.long0xff00fff0

.longPMD_TYPE_SECT|

PMD_SECT_BUFFERABLE|

PMD_SECT_CACHEABLE|

PMD_BIT4|

PMD_SECT_AP_WRITE|

PMD_SECT_AP_READ

.longPMD_TYPE_SECT|

PMD_BIT4|

PMD_SECT_AP_WRITE|

PMD_SECT_AP_READ

b__arm920_setup

…………………………………

.section".proc.info.init"表明了該結構在編譯后存放的位置。在鏈接文件arch/arm/kernel/vmlinux.lds中:

SECTIONS

{

#ifdefCONFIG_XIP_KERNEL

.=XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);

#else

.=PAGE_OFFSET+TEXT_OFFSET;

#endif

.text.head:{

_stext=.;

_sinittext=.;

*(.text.head)

}

.init:{/*Initcodeanddata*/

INIT_TEXT

_einittext=.;

__proc_info_begin=.;

*(.proc.info.init)

__proc_info_end=.;

__arch_info_begin=.;

*(.arch.info.init)

__arch_info_end=.;

__tagtable_begin=.;

*(.taglist.init)

__tagtable_end=.;

………………………………

所有CPU類型對應的被初始化的proc_info_list結構體都放在__proc_info_begin和__proc_info_end之間。

/*

*r9=cpuid

*Returns:

*r5=proc_infopointerinphysicaladdressspace

*r9=cpuid(preserved)

*/

__lookup_processor_type:

adrr3,3f@r3存儲的是標號3的物理地址(由于沒有啟用mmu,所以當前肯定是物理地址)

ldmiar3,{r5-r7}@R5=__proc_info_begin,r6=__proc_info_end,r7=標號4處的虛擬地址,即4:.long.處的地址

addr3,r3,#8@得到4處的物理地址,剛好是跳過兩條指令

subr3,r3,r7@getoffsetbetweenvirt&phys得到虛擬地址和物理地址之間的offset

/*利用offset,將r5和r6中保存的虛擬地址轉變?yōu)槲锢淼刂?/

addr5,r5,r3@convertvirtaddressesto

addr6,r6,r3@physicaladdressspace

1:ldmiar5,{r3,r4}@value,maskr3=cpu_val,r4=cpu_mask

andr4,r4,r9@maskwantedbits;r9中存放的是先前讀出的processorID,此處屏蔽不需要的位

teqr3,r4@查看代碼和CPU硬件是否匹配(比如想在arm920t上運行為cortex-a8編譯的內(nèi)核?不讓)

beq2f@如果相等則跳轉到標號2處,執(zhí)行返回指令

addr5,r5,#PROC_INFO_SZ@sizeof(proc_info_list結構的長度,在這等于48)如果沒找到,跳到下一個proc_info_list處

cmpr5,r6@判斷是不是到了該段的結尾

blo1b@如果沒有,繼續(xù)跳到標號1處,查找下一個

movr5,#0@unknownprocessor,如果到了結尾,沒找到匹配的,就把0賦值給r5,然后返回

2:movpc,lr@找到后返回,r5指向找到的結構體

ENDPROC(__lookup_processor_type)

.align2

3:.long__proc_info_begin

.long__proc_info_end

4:.long.@“.”表示當前這行代碼編譯連接后的虛擬地址

.long__arch_info_begin

.long__arch_info_end

//

__lookup_processor_type函數(shù)的具體解析結束(archarmkernelhead-common.S)

//

movsr10,r5@invalidprocessor(r5=0)?

beq__error_p@yes,errorp

/*機器ID是由u-boot引導內(nèi)核是通過thekernel第二個參數(shù)傳遞進來的,現(xiàn)在保存在r1中,在__arch_info_begin開始的段中進行查找,如果找到,則返回machine對應相關結構體在物理地址空間的首地址到r5,最后保存在r8中。

bl__lookup_machine_type@r5=machinfo

//

__lookup_machine_type函數(shù)的具體解析開始(archarmkernelhead-common.S)

//

每一個CPU平臺都可能有其不一樣的結構體,描述這個平臺的結構體是machine_desc。

這個結構體在文件arch/arm/include/asm/mach/arch.h中定義:

structmachine_desc{

unsignedintnr;/*architecturenumber*/

unsignedintphys_io;/*startofphysicalio*/

………………………………

};

對于平臺smdk2410來說其對應machine_desc結構在文件linux/arch/arm/mach-s3c2410/mach-smdk2410.c中初始化:

MACHINE_START(SMDK2410,"SMDK2410")

.phys_io=S3C2410_PA_UART,

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

.boot_params=S3C2410_SDRAM_PA+0x100,

.map_io=smdk2410_map_io,

.init_irq=s3c24xx_init_irq,

.init_machine=smdk2410_init,

.timer=&s3c24xx_timer,

MACHINE_END

對于宏MACHINE_START在文件arch/arm/include/asm/mach/arch.h中定義:

#defineMACHINE_START(_type,_name)/

staticconststructmachine_desc__mach_desc_##_type/

__used/

__attribute__((__section__(".arch.info.init")))={/

.nr=MACH_TYPE_##_type,/

.name=_name,

#defineMACHINE_END/

};

__attribute__((__section__(".arch.info.init")))表明該結構體在并以后存放的位置。

在鏈接文件鏈接腳本文件arch/arm/kernel/vmlinux.lds中

SECTIONS

{

#ifdefCONFIG_XIP_KERNEL

.=XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR);

#else

.=PAGE_OFFSET+TEXT_OFFSET;

#endif

.text.head:{

_stext=.;

_sinittext=.;

*(.text.head)

}

.init:{/*Initcodeanddata*/

INIT_TEXT

_einittext=.;

__proc_info_begin=.;

*(.proc.info.init)

__proc_info_end=.;

__arch_info_begin=.;

*(.arch.info.init)

__arch_info_end=.;

………………………………

在__arch_info_begin和__arch_info_end之間存放了linux內(nèi)核所支持的所有平臺對應的machine_desc結構體。

/*

*r1=machinearchitecturenumber

*Returns:

*r5=mach_infopointerinphysicaladdressspace

*/

__lookup_machine_type:

adrr3,4b@把標號4處的地址放到r3寄存器里面

ldmiar3,{r4,r5,r6}@R4=標號4處的虛擬地址,r5=__arch_info_begin,r6=__arch_info_end

subr3,r3,r4@getoffsetbetweenvirt&phys計算出虛擬地址與物理地址的偏移

/*利用offset,將r5和r6中保存的虛擬地址轉變?yōu)槲锢淼刂?/

addr5,r5,r3@convertvirtaddressesto

addr6,r6,r3@physicaladdressspace

/*讀取machine_desc結構的nr參數(shù),對于smdk2410來說該值是MACH_TYPE_SMDK2410,這個值在文件linux/arch/arm/tools/mach-types中:

smdk2410ARCH_SMDK2410SMDK2410193*/

1:ldrr3,[r5,#MACHINFO_TYPE]@getmachinetype

teqr3,r1@matchesloadernumber?把取到的machineid和從uboot中傳過來的machineid(存放r1中)相比較

beq2f@found如果相等,則跳到標號2處,返回

addr5,r5,#SIZEOF_MACHINE_DESC@nextmachine_desc沒有找到,則繼續(xù)找下一個,加上該結構體的長度

cmpr5,r6@判斷是否已經(jīng)到該段的末尾

blo1b@如果沒有,則跳轉到標號1處,繼續(xù)查找

movr5,#0@unknownmachine如果已經(jīng)到末尾,并且沒找到,則返回值r5寄存器賦值為0

2:movpc,lr@返回原函數(shù),且r5作為返回值

ENDPROC(__lookup_machine_type)

.align2

3:.long__proc_info_begin

.long__proc_info_end

4:.long.@“.”表示當前這行代碼編譯連接后的虛擬地址

.long__arch_info_begin

.long__arch_info_end

//

__lookup_machine_type函數(shù)的具體解析結束(archarmkernelhead-common.S)

//

movsr8,r5@invalidmachine(r5=0)?

beq__error_a@yes,errora

/*檢查bootloader傳入的參數(shù)列表atags的合法性*/

bl__vet_atags

//

__vet_atags函數(shù)的具體解析開始(archarmkernelhead-common.S)

//

關于參數(shù)鏈表:

內(nèi)核參數(shù)鏈表的格式和說明可以從內(nèi)核源代碼目錄樹中的archarmincludeasmsetup.h中找到,參數(shù)鏈表必須以ATAG_CORE開始,以ATAG_NONE結束。這里的ATAG_CORE,ATAG_NONE是各個參數(shù)的標記,本身是一個32位值,例如:ATAG_CORE=0x54410001。其它的參數(shù)標記還包括:ATAG_MEM32,ATAG_INITRD,ATAG_RAMDISK,ATAG_COMDLINE等。每個參數(shù)標記就代表一個參數(shù)結構體,由各個參數(shù)結構體構成了參數(shù)鏈表。參數(shù)結構體的定義如下:

structtag{
structtag_headerhdr;
union{
structtag_corecore;
structtag_mem32mem;
structtag_videotextvideotext;
structtag_ramdiskramdisk;
structtag_initrdinitrd;
structtag_serialnrserialnr;
structtag_revisionrevision;
structtag_videolfbvideolfb;
structtag_cmdlinecmdline;
structtag_acornacorn;
structtag_memclkmemclk;
}u;
};

參數(shù)結構體包括兩個部分,一個是tag_header結構體,一個是u聯(lián)合體。

tag_header結構體的定義如下:

structtag_header{

u32size;

u32tag;

};

其中size:表示整個tag結構體的大小(用字的個數(shù)來表示,而不是字節(jié)的個數(shù)),等于tag_header的大小加上u聯(lián)合體的大小,例如,參數(shù)結構體ATAG_CORE的size=(sizeof(tag->tag_header)+sizeof(tag->u.core))>>2,一般通過函數(shù)tag_size(struct*tag_xxx)來獲得每個參數(shù)結構體的size。其中tag:表示整個tag結構體的標記,如:ATAG_CORE等。

/*r8=machinfo

*Returns:

*r2eithervalidatagspointer,orzero

*/

__vet_atags:

tstr2,#0x3@aligned?r2指向該參數(shù)鏈表的起始位置,此處判斷它是否字對齊

bne1f@如果沒有對齊,跳到標號1處直接返回,并且把r2的值賦值為0,作為返回值

ldrr5,[r2,#0]@isfirsttagATAG_CORE?獲取第一個tag結構的size

cmpr5,#ATAG_CORE_SIZE@判斷該tag的長度是否合法

cmpner5,#ATAG_CORE_SIZE_EMPTY

bne1f@如果不合法,異常返回

ldrr5,[r2,#4]@獲取第一個tag結構體的標記

ldrr6,=ATAG_CORE@取出標記ATAG_CORE的內(nèi)容

cmpr5,r6@判斷該標記是否等于ATAG_CORE

bne1f@如果不等,異常返回

movpc,lr@atagpointerisok,如果都相等,則正常返回

1:movr2,#0@異常返回值

movpc,lr@異常返回

ENDPROC(__vet_atags)

//

__vet_atags函數(shù)的具體解析結束(archarmkernelhead-common.S)

//

/*創(chuàng)建內(nèi)核初始化頁表*/

bl__create_page_tables

//

__create_page_tables函數(shù)的具體解析開始(archarmkernelhead.S)

//

/*

*r8=machinfo

*r9=cpuid

*r10=procinfo

*Returns:

*r4=physicalpagetableaddress

*/

/*在該文件的開頭有如下宏定義*/

#defineKERNEL_RAM_PADDR(PHYS_OFFSET+TEXT_OFFSET)

.macropgtbl,rd

ldrrd,=(KERNEL_RAM_PADDR-0x4000)

.endm

其中:PHYS_OFFSET在arch/arm/mach-s3c2410/include/mach/memory.h定義,為UL(0x30000000),而TEXT_OFFSET在arch/arm/Makefile中定義,為內(nèi)核鏡像在內(nèi)存中到內(nèi)存開始位置的偏移(字節(jié)),為$(textofs-y)textofs-y也在文件arch/arm/Makefile中定義,為textofs-y:=0x00008000,r4=30004000為臨時頁表的起始地址,首先即是初始化16K的頁表,高12位虛擬地址為頁表索引,每個頁表索引占4個字節(jié),所以為4K*4=16K,大頁表,每一個頁表項,映射1MB虛擬地址.

__create_page_tables:

/*為內(nèi)核代碼存儲區(qū)域創(chuàng)建頁表,首先將內(nèi)核起始地址-0x4000到內(nèi)核起始地址之間的16K存儲器清0,將創(chuàng)建的頁表存于此處*/

pgtblr4@r4中存放的為頁表的基地址,最終該地址會寫入cp15的寄存器c2,這個值必須是16K對齊的

movr0,r4@把頁表的基地址存放到r0中

movr3,#0@把r3清0

addr6,r0,#0x4000@r6指向16K的末尾

1:strr3,[r0],#4@把16K的頁表空間清0

strr3,[r0],#4

strr3,[r0],#4

strr3,[r0],#4

teqr0,r6

bne1b

/*從proc_info_list結構中獲取字段__cpu_mm_mmu_flags,該字段包含了存儲空間訪問權限等,此處指令執(zhí)行之后r7=0x00000c1e*/

ldrr7,[r10,#PROCINFO_MM_MMUFLAGS]@mm_mmuflags

/*為內(nèi)核的第一MB創(chuàng)建一致的映射,以為打開MMU做準備,這個映射將會被paging_init()移除,這里使用程序計數(shù)器來獲得相應的段的基地址*/

movr6,pc

movr6,r6,lsr#20@startofkernelsection

orrr3,r7,r6,lsl#20@flags+kernelbase

strr3,[r4,r6,lsl#2]@identitymapping

/*MMU是通過C2中基地址(高18位)與虛擬地址的高12位組合成物理地址,在轉換表中查找地址條目。R4中存放的就是這個基地址0x30004000*/

addr0,r4,#(KERNEL_START&0xff000000)>>18@r0=0x30007000r0存放的是轉換表的起始位置

strr3,[r0,#(KERNEL_START&0x00f00000)>>18]!@r3存放的是內(nèi)核鏡像代碼段的起始地址

ldrr6,=(KERNEL_END-1)@獲取內(nèi)核的尾部虛擬地址存于r6中

addr0,r0,#4@第一個地址條目存放在0x30007004處,以后依次遞增

addr6,r4,r6,lsr#18@計算最后一個地址條目存放的位置

1:cmpr0,r6@填充這之間的地址條目

/*每一個地址條目代表了1MB空間的地址映射。物理地址將從0x30100000開始映射。0X30000000開始的1MB空間將在下面映射*/

addr3,r3,#1<<20

strlsr3,[r0],#4

bls1b

…………………………………

…………………………………………

/*為了使用啟動參數(shù),將物理內(nèi)存的第一MB映射到內(nèi)核虛擬地址空間的第一個MB,r4存放的是頁表的地址。映射0X30000000開始的1MB空間PAGE_OFFSET=0XC0000000,PHYS_OFFSET=0X30000000,r0=0x30007000,上面是從0x30007004開始存放地址條目的*/

addr0,r4,#PAGE_OFFSET>>18

orrr6,r7,#(PHYS_OFFSET&0xff000000)@r6=0x30000c1e

.if(PHYS_OFFSET&0x00f00000)

orrr6,r6,#(PHYS_OFFSET&0x00f00000)

.endif

strr6,[r0]@將0x30000c1e存于0x30007000處。

………………………

………………………………

movpc,lr@子程序返回

ENDPROC(__create_page_tables)

//

__create_page_tables函數(shù)的具體解析結束(archarmkernelhead.S)

//

/*把__switch_data標號處的地址放入r13寄存器,當執(zhí)行完__enable_mmu函數(shù)時會把r13寄存器的值賦值給pc,跳轉到__switch_data處執(zhí)行*/

ldrr13,__switch_data@addresstojumptoaftermmuhasbeenenabled

/*把__enable_mmu函數(shù)的地址值,賦值給lr寄存器,當執(zhí)行完__arm920_setup時,返回后執(zhí)行__enable_mmu*/

adrlr,BSYM(__enable_mmu)@return(PIC)address

//

__enable_mmu函數(shù)的具體解析開始(archarmkernelhead.S)

//

__enable_mmu:

#ifdefCONFIG_ALIGNMENT_TRAP

orrr0,r0,#CR_A//使能地址對齊錯誤檢測

#else

bicr0,r0,#CR_A

#endif

#ifdefCONFIG_CPU_DCACHE_DISABLE

bicr0,r0,#CR_C//禁止數(shù)據(jù)cache

#endif

#ifdefCONFIG_CPU_BPREDICT_DISABLE

bicr0,r0,#CR_Z

#endif

#ifdefCONFIG_CPU_ICACHE_DISABLE

bicr0,r0,#CR_I//禁止指令cache

#endif//配置相應的訪問權限并存入r5中

movr5,#(domain_val(DOMAIN_USER,DOMAIN_MANAGER)|/

domain_val(DOMAIN_KERNEL,DOMAIN_MANAGER)|/

domain_val(DOMAIN_TABLE,DOMAIN_MANAGER)|/

domain_val(DOMAIN_IO,DOMAIN_CLIENT))

mcrp15,0,r5,c3,c0,0//將訪問權限寫入?yún)f(xié)處理器

mcrp15,0,r4,c2,c0,0//將頁表基地址寫入基址寄存器C2,0X30004000

b__turn_mmu_on//跳轉到程序段去打開MMU

ENDPROC(__enable_mmu)

文件linux/arch/arm/kernel/head.S中

__turn_mmu_on:

movr0,r0

mcrp15,0,r0,c1,c0,0//打開MMU同時打開cache等。

mrcp15,0,r3,c0,c0,0@readidreg讀取id寄存器

movr3,r3

movr3,r3//兩個空操作,等待前面所取的指令得以執(zhí)行。

movpc,r13//程序跳轉

ENDPROC(__turn_mmu_on)

//

__enable_mmu函數(shù)的具體解析結束(archarmkernelhead.S)

//

/*執(zhí)行__arm920_setup函數(shù)(archarmmmproc-arm920.S),該函數(shù)完成對數(shù)據(jù)cache,指令cache,writebuffer等初始化操作*/

ARM(addpc,r10,#PROCINFO_INITFUNC)

//

__arm920_setup函數(shù)的具體解析開始(archarmmmproc-arm920.S)

//

在上面程序段.section".text.head","ax"的最后有這樣幾行:

addpc,r10,#PROCINFO_INITFUNC

R10中存放的是在函數(shù)__lookup_processor_type中成功匹配的結構體proc_info_list。對于arm920來說在文件linux/arch/arm/mm/proc-arm920.S中有:

.section".proc.info.init",#alloc,#execinstr

.type__arm920_proc_info,#object

__arm920_proc_info:

.long0x41009200

.long0xff00fff0

.longPMD_TYPE_SECT|/

PMD_SECT_BUFFERABLE|/

PMD_SECT_CACHEABLE|/

PMD_BIT4|/

PMD_SECT_AP_WRITE|/

PMD_SECT_AP_READ

.longPMD_TYPE_SECT|/

PMD_BIT4|/

PMD_SECT_AP_WRITE|/

PMD_SECT_AP_READ

b__arm920_setup

………………………………

addpc,r10,#PROCINFO_INITFUNC的意思跳到函數(shù)__arm920_setup去執(zhí)行。

.type__arm920_setup,#function//表明這是一個函數(shù)

__arm920_setup:

movr0,#0//設置r0為0。

mcrp15,0,r0,c7,c7//使數(shù)據(jù)cahche,指令cache無效。

mcrp15,0,r0,c7,c10,4//使writebuffer無效。

#ifdefCONFIG_MMU

mcrp15,0,r0,c8,c7//使數(shù)據(jù)TLB,指令TLB無效。

#endif

adrr5,arm920_crval//獲取arm920_crval的地址,并存入r5。

ldmiar5,{r5,r6}//獲取arm920_crval地址處的連續(xù)8字節(jié)分別存入r5,r6。

mrcp15,0,r0,c1,c0//獲取CP15下控制寄存器的值,并存入r0。

bicr0,r0,r5//通過查看arm920_crval的值可知該行是清除r0中相關位,為以后對這些位的賦值做準備

orrr0,r0,r6//設置r0中的相關位,即為mmu做相應設置。

movpc,lr//上面有操作adrlr,__enable_mmu,此處將跳到程序段__enable_mmu處。

.size__arm920_setup,.-__arm920_setup

.typearm920_crval,#object

arm920_crval:

crvalclear=0x00003f3f,mmuset=0x00003135,ucset=0x00001130

//

__arm920_setup函數(shù)的具體解析結束(archarmmmproc-arm920.S)

//

ENDPROC(stext)

接著往下分析linux/arch/arm/kernel/head-common.S中:

.type__switch_data,%object@定義__switch_data為一個對象

__switch_data:

.long__mmap_switched

.long__data_loc@r4

.long_data@r5

.long__bss_start@r6

.long_end@r7

.longprocessor_id@r4

.long__machine_arch_type@r5

.long__atags_pointer@r6

.longcr_alignment@r7

.longinit_thread_union+THREAD_START_SP@sp

/*

*ThefollowingfragmentofcodeisexecutedwiththeMMUoninMMUmode,

*andusesabsoluteaddresses;thisisnotpositionindependent.

*r0=cp#15controlregister

*r1=machineID

*r2=atagspointer

*r9=processorID

*/

/*其中上面的幾個段的定義是在文件arch/arm/kernel/vmlinux.lds中指定*/

vmlinux.lds開始*

SECTIONS

{

……………………

#ifdefCONFIG_XIP_KERNEL

__data_loc=ALIGN(4);/*locationinbinary*/

.=PAGE_OFFSET+TEXT_OFFSET;

#else

.=ALIGN(THREAD_SIZE);

__data_loc=.;

#endif

.data:AT(__data_loc){//此處數(shù)據(jù)存儲在上面__data_loc處。

_data=.;/*addressinmemory*/

*(.data.init_task)

…………………………

.bss:{

__bss_start=.;/*BSS*/

*(.bss)

*(COMMON)

_end=.;

}

………………………………

init_thread_union是init進程的基地址.在arch/arm/kernel/init_task.c中:

unionthread_unioninit_thread_union__attribute__((__section__(".init.task")))={INIT_THREAD_INFO(init_task)};

對照vmlnux.lds.S中,我們可以知道inittask是存放在.data段的開始8k,并且是THREAD_SIZE(8k)對齊的*/

vmlinux.lds結束*

__mmap_switched:

adrr3,__switch_data+4

ldmiar3!,{r4,r5,r6,r7}

……………………

………………………………

movfp,#0@清除bss段

1:cmpr6,r7

strccfp,[r6],#4

bcc1b

ARM(ldmiar3,{r4,r5,r6,r7,sp})/*把__machine_arch_type變量值放入r5中,把__atags_pointer變量的值放入r6中*/

strr9,[r4]@SaveprocessorID保存處理器id到processor_id所在的地址中

strr1,[r5]@Savemachinetype保存machineid到__machine_arch_type中

strr2,[r6]@Saveatagspointer保存參數(shù)列表首地址到__atags_pointer中

bicr4,r0,#CR_A@ClearAbit

stmiar7,{r0,r4}@Savecontrolregistervalues

bstart_kernel@程序跳轉到函數(shù)start_kernel進入C語言部分。

ENDPROC(__mmap_switched)

到處我們的啟動的第二階段分析完畢。

后面會接著分析第三階段。第三階段完全是C語言代碼,從start_kernel函數(shù)開始。



評論


技術專區(qū)

關閉