`
isiqi
  • 浏览: 16045668 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

ARM-Linux内核启动的分析

阅读更多

1.介绍..........................................................................................2

2.相关定义介绍..........................................................................3

21TEXTADDR......................................................3

22stext....................................................................3

23swapper_pg_dir..................................................4

24(M)pgtbl.............................................................4

25(M)krnladr..........................................................5

26.proc.info.......................................................5

27__proc_info_begin/__proc_info_end.................6

28.arch.info.......................................................7

29__arch_info_begin/__arch_info_end.................8

3.代码分析..................................................................................9

31KERNEL ENTRY..............................................9

32__arm920_setup...............................................11

33__ret.................................................................13

34__mmap_switched...........................................14

35__lookup_processor_type................................16

36__lookup_architecture_type.............................16

37__create_page_tables.......................................19

1.介绍

这是一篇对armlinux内核启动的分析,主要是arch/arm/kernel/head-armv.S文件, head-armv.S文件是整个内核的入口,也就是说bootloader执行完毕后将跳转到head-armv.S的第一条指令,head-armv.S执行完后将跳转到start_kernel(),在head-armv.S的执行过程中也用到了其他一些文件,包括arch/arm/kernel/debug-armv.S、arch/arm/mm/proc-arm920.S等等

由于此分析基于MX1的内核启动过程,因此除了通用代码,只有定义在CONFIG_ARCH_MX1ADS下的代码和proc-arm920.S(arm920是MX1的CPU)的代码被分析

在下面程序流程的说明中,MX1板子启动过程中的寄存器值将会用绿色字体表示出来,而对于专门针对MX1的代码则会用下划线字体表示

2.相关定义介绍

21TEXTADDR

TEXTADDR是内核Image的映像地址,也是内核Image所处的虚拟地址,它在系统内核空间起始地址——通常是0xC0000000(这相对应于物理内存开始的地方)+32K的位置,也就是0xC0008000处TEXTADDR的赋值在arch/arm/Makefile文件中:(0xC0008000)

ifeq ($(CONFIG_CPU_32),y)

PROCESSOR = armv

TEXTADDR = 0xC0008000

LDSCRIPT = arch/arm/vmlinux-armv.lds.in

endif

在内核映像之前的16K空间用来存放内核的页目录表,这就是为什么TEXTADDR要在系统要放在0xC0008000的缘故——它必须留出足够的物理空间来放页表

在head-armv.S中TEXTADDR将被检测:

#if (TEXTADDR & 0xffff) != 0x8000 //TEXTADDR必须为0xXXXX8000

#error TEXTADDR must start at 0xXXXX8000

#endif

22stext

stext是TEXTADDR相对应的物理地址,由于内核空间的起始地址(0xC0000000)相对应的物理地址就是最低物理内存的地址,因此stext就是最低物理内存+32K处(虽然这并不是有内核指定,而是由bootloader指定),它的赋值在连接脚本vmlinux-armv.lds.in中实现:(0x08008000)

ENTRY(stext)

SECTIONS

{

. = TEXTADDR; //此处的映像地址为TEXTADDR

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

_stext = .; //stext为此处的物理地址

23swapper_pg_dir

swapper_pg_dir是页表的映像地址,由于启动页表在内核Image之前的16K处,因此它等于0xC0004000,它的定义在head-armv.S文件中

.globl SYMBOL_NAME(swapper_pg_dir) //设置swapper_pg_dir

.equ SYMBOL_NAME(swapper_pg_dir), TEXTADDR - 0x4000

在定义init进程的mm_struct结构的宏INIT_MM中,swapper_pg_dir作为页表基址被赋给init_mm,INIT_MM定义在include/linux/sched.h文件中: #define INIT_MM(name) \

{ \

pgd: swapper_pg_dir, \

}

24(M)pgtbl

pgtbl是一个用于获得启动页表物理地址的宏,它将stext减去16K给reg,它也在head-armv.S中定义:

.macro pgtbl, reg, rambase

adr \reg, stext

sub \reg, \reg, #0x4000 //reg=stext-0x4000

.endm

25(M)krnladr

这个宏用于由pgtable获得内核空间的起始物理地址的所在的段(MB),将pgtable,也就是页表地址(和内核空间的起始物理地址在同一个段内)和0x000FFFFF相与,因为页表地址后12位为零,所以将其和0x000FF000相与

/*

* Since the page table is closely related to the kernel start address, we

* can convert the page table base address to the base address of the section

* containing both.

*/

.macro krnladr, rd, pgtable, rambase

bic \rd, \pgtable, #0x000ff000

.endm

26.proc.info

.proc.info段中存放的是各种处理器的信息,每个处理器的信息用一个proc_info_list结构来表示,这个结构在include/asm/procinfo.h文件中声明:

struct proc_info_list {

unsigned int cpu_val; //处理器类型

unsigned int cpu_mask; //处理器类型掩码

unsigned long __cpu_mmu_flags; /* used by head-armv.S */

unsigned long __cpu_flush; /* used by head-armv.S */

const char *arch_name;

const char *elf_name;

unsigned int elf_hwcap;

struct proc_info_item *info;

#ifdef MULTI_CPU

struct processor *proc;

#else

void *unused;

#endif

};

虽然结构是在这里声明,但是真正的定义却是在proc-arm920.S文件的最后:

.section ".proc.info", #alloc, #execinstr //声明以下代码在.proc.info段中

.type __arm920_proc_info,#object

__arm920_proc_info:

.long 0x41009200 //cpu_val

.long 0xff00fff0 //cpu_mask

.long 0x00000c1e @ mmuflags

b __arm920_setup //是的!这是一条跳转指令

.long cpu_arch_name

.long cpu_elf_name

.long HWCAP_SWP | HWCAP_HALF | HWCAP_26BIT

.long cpu_arm920_info

.long arm920_processor_functions

27__proc_info_begin/__proc_info_end

__proc_info_begin是.proc.info段的起始地址,而__proc_info_end是终止地址,他们存放在head-armv.S中:

2: .long __proc_info_end

.long __proc_info_begin

在连接脚本vmlinux.lds.in文件中,他们被赋值:

__proc_info_begin = .;

*(.proc.info)

__proc_info_end = .;

28.arch.info

.arch.info段类似于.proc.info段,不过它是用来存放板子信息的,它的定义是在include/asm/mach/arch.h文件中:

struct machine_desc {

/*

* Note! The first four elements are used

* by assembler code in head-armv.S

*/

unsigned int nr; /* architecture number */ //板子ID

unsigned int phys_ram; /* start of physical ram */ //物理内存起始地址

unsigned int phys_io; /* start of physical io */ //IO空间起始地址

unsigned int io_pg_offst; /* byte offset for io

* page tabe entry */

//IO空间起始地址的虚拟地址在启动页表中的偏移

……

};

而具体MX1板子的machine_desc定义则通过宏在arch/arm/mach-XXXX/arch.c文件(或者该目录下的其他文件)中,这些宏的定义在include/asm/mach/arch.h文件中实现,通过这些宏定义了一个machine_desc:

MACHINE_START(MX1ADS, "Motorola MX1ADS")

MAINTAINER("WBSG SPS Motorola")

#ifdef CONFIG_ARCH_MX1ADS_SRAM

BOOT_MEM(0x12000000, 0x00200000, 0xf0200000)

#else

BOOT_MEM(0x08000000, 0x00200000, 0xf0200000)

//phys_ram=0x08000000phys_io=0x00200000

//io_pg_offset = ((0xf0200000)>>18)&0xfffc=0x00003c08

//右移20位获得IO空间虚拟地址在页表内偏移再乘以四(4个字节

#endif

FIXUP(mx1ads_fixup)

MAPIO(mx1ads_map_io)

INITIRQ(mx1ads_init_irq)

MACHINE_END

29__arch_info_begin/__arch_info_end

__arch_info_begin是.arch.info段的起始地址,而__arch_info_end是终止地址,他们存放在head-armv.S中:

.long __arch_info_begin

.long __arch_info_end

在连接脚本vmlinux.lds.in文件中,他们被赋值:

__arch_info_begin = .;

*(.arch.info)

__arch_info_end = .;

3.代码分析

31KERNEL ENTRY

下面是整个内核Image的入口,进入后必须满足以下条件,即r0为0,r1为板子ID,MMU和D-cache关闭,这其中r1的architecture ID是由bootloader传进来的

/*

* Kernel startup entry point.

*

* The rules are:

* r0 - should be 0

* r1 - unique architecture number

* MMU - off

* I-cache - on or off

* D-cache - off

*

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

* for r1.

*/

.section ".text.init",#alloc,#execinstr //以下代码属于“.text.init”段

.type stext, #function

ENTRY(stext)

// mov r12, r0

mov r12, #0 // r12=0

#if defined(CONFIG_ARCH_MX1ADS)

mov fp, r1 @ r1 contain pointer to cmdline from bootloader

#endif //r1中包含的内核命令行指针移到fp

// for MX1ADS, we don't pass this from bootloader, so we'll set it here

#if defined(CONFIG_ARCH_MX1ADS)

mov r1, #MACH_TYPE_MX1ADS

#endif //此时,r1=0x000000a0

mov r0, #F_BIT | I_BIT | MODE_SVC @ make sure svc mode

msr cpsr_c, r0 @ and all irqs disabled

//将模式设置为svc模式,并禁止IRQ和FIQ

bl __lookup_processor_type //检查处理器类型

//如果处理器有效:

//r8 = __cpu_mmu_flags, r8 = 0x00000c1e

//r9 =处理器ID

r10指向处理器结构

teq r10, #0 @ invalid processor? //如果是无效处理器

moveq r0, #'p' @ yes, error 'p' //打印“p”和出错信息

beq __error

bl __lookup_architecture_type //检查板子类型

//如果板子有效:

// r5=物理内存的起始地址, r5 = 0x08000000

// r6=IO空间的起始物理地址 r6=0x00200000

// r7=IO空间虚拟地址在页表中的偏移 r7=0x00003c08

teq r7, #0 @ invalid architecture? //如果是无效板子

moveq r0, #'a' @ yes, error 'a' //打印“a”和出错信息

beq __error

bl __create_page_tables //建立页表

//此时页表建立完毕,

//r1=板子ID,

//r4=页表地址 (stext-16K)

//r9=处理器ID,

//r10指向处理器结构

adr lr, __ret @ return address //将返回地址存放在lr中

add pc, r10, #12 @ initialise processor

@ (return control reg)

//跳转到处理器结构+12的位置,参看.proc.info段可以知

//道,这里是一条跳转指令“b __arm920_setup”,因此再

//跳转到proc-arm920.S中的__arm920_setup函数入口处

32__arm920_setup

__arm920_setup函数在proc-arm920.S文件中,在页表建立起来之后,此函数进行一些开启MMU之前的初始化操作

.section ".text.init", #alloc, #execinstr

__arm920_setup:

mov r0, #0

mcr p15, 0, r0, c7, c7 @ invalidate I,D caches on v4

//使无效整个I-cache和整个D-cache

mcr p15, 0, r0, c7, c10, 4 @ drain write buffer on v4

//将Write Buffer中的数据写进内存

mcr p15, 0, r0, c8, c7 @ invalidate I,D TLBs on v4

//使无效整个I-TLB和整个D-TLB

mcr p15, 0, r4, c2, c0 @ load page table pointer

//将r4(页表地址)写进c2(页表基址寄存器)

mov r0, #0x1f @ Domains 0, 1 = client

mcr p15, 0, r0, c3, c0 @ load domain access register

//设置Domain0、Domain1和Domain2的访问权限

mrc p15, 0, r0, c1, c0 @ get control register v4

//将控制寄存器(control register)的值送给r0

/*

* Clear out 'unwanted' bits (then put them in if we need them)

*/

@ VI ZFRS BLDP WCAM

bic r0, r0, #0x0e00

bic r0, r0, #0x0002

bic r0, r0, #0x000c

bic r0, r0, #0x1000 @ ...0 000. .... 000.

/*

* Turn on what we want

*/

orr r0, r0, #0x0031

orr r0, r0, #0x2100 @ ..1. ...1 ..11 ...1

#ifdef CONFIG_CPU_ARM920_D_CACHE_ON

orr r0, r0, #0x0004 @ .... .... .... .1..

#endif

#ifdef CONFIG_CPU_ARM920_I_CACHE_ON

orr r0, r0, #0x1000 @ ...1 .... .... ....

#endif

//修改r0的某些位,使它的后16位为:XX1I 0001 XX11 0D01

//其中I表示根据CONFIG_CPU_ARM920_I_CACHE_ON设定

//D表示根据CONFIG_CPU_ARM920_D_CACHE_ON设定

//X表示不变,1表示置位,0表示清位

//具体含义如下:

//M(bit0)=1,打开MMU

//A(bit1)=0,关闭Alignment checking

//C(bit2)=D,D-cache打开/关闭

//W(bit3)=0,关闭Write Buffer

//P(bit4)=1,Exception handler进入32-bit模式

//D(bit5)=1,关闭32-bit address exception checking

//L(bit6)=X,选择Early Abort模式或者Late Abort模式

//B(bit7)=X,Little Endian/Big Endian模式

//S(bit8)=1,System Protection Bit

//R(bit9)=0,Rom Protection Bit

//F(bit10)=0,Implementation Defined

//Z(bit11)=0,关闭Branch prediction

//I(bit12)=I,I-cache打开/关闭

//V(bit13)=1,选择High exception vector

mov pc, lr //返回,跳转到head-armv.S的__ret处

33__ret

在proc-arm920.S中的__arm920_setup函数进行过一些启动MMU之前的初始化工作后,根据lr寄存器中的值跳转到__ret处执行,这里做了三件事:

1. 首先将__switch_data处的值(即__mmap_switched)作为返回值存放在lr寄存器中

2. 开启MMU

3. 最后返回(即跳转到__mmap_switched处)

请注意!__switch_data中保存的值(也就是__mmap_switched)是一个映像地址(也就是虚拟地址),也就是说,PC的值从此处由物理地址的值跳到内核空间(0xCXXXXXXX)

.type __switch_data, %object

__switch_data: .long __mmap_switched

.long SYMBOL_NAME(compat)

.long SYMBOL_NAME(__bss_start)

.long SYMBOL_NAME(_end)

.long SYMBOL_NAME(processor_id)

.long SYMBOL_NAME(__machine_arch_type)

.long SYMBOL_NAME(cr_alignment)

.long SYMBOL_NAME(init_task_union)+8192

#ifdef CONFIG_ARCH_MX1ADS

.long SYMBOL_NAME(cmdline_from_bootloader)

//这是为MX1板子定义的从bootloader传来的参数地址

#endif

.type __ret, %function

__ret: ldr lr, __switch_data //这里保存的是内核空间地址!

mcr p15, 0, r0, c1, c0 //将r0中的值送回c1,开启MMU!!

mov r0, r0 //执行三次NOP操作,清空流水线

mov r0, r0

mov r0, r0

mov pc, lr //跳到__mmap_switched处执行

34__mmap_switched

__ret开启MMU之后,通过将__switch_data中保存的__map_switched的值跳转到此处执行,也就是从此处开始PC值转为0xCXXXXXXX

/*

* This code follows on after the page

* table switch and jump above.

*

* r0 = processor control register

* r1 = machine ID

* r9 = processor ID

*/

.align 5

__mmap_switched:

adr r3, __switch_data + 4 //r3=__switch_data+4

ldmia r3, {r2, r4, r5, r6, r7, r8, sp}@ r2 = compat

@ sp = stack pointer

//r2=compat

//r4=bss_start=.bss段的起始地址

//r5=_end=.bss段的终止地址

//r6=processor_id=保存处理器ID的地址

//r7=__machine_arch_type=保存板子ID的地址

//r8=cr_alignment

//sp=initial_task+8192,由task_union结构可知,这是init进程的堆栈

str r12, [r2] //将r12中的值(0)存进compat

#ifdef CONFIG_ARCH_MX1ADS

mov r12, fp @ fp/r11 gets used below (it originally contain @ pointer to cmdline from bootloader)

#endif

//在内核入口的一开始,r1中的包含指向内核命令行的指针被送到fp寄存器中,//现在将它送给r12

mov fp, #0 @ Clear BSS (and zero fp)

1: cmp r4, r5 //将整个.bss段清零

strcc fp, [r4],#4

bcc 1b

str r9, [r6] @ Save processor ID //保存处理器ID

str r1, [r7] @ Save machine type //保存板子ID

#ifdef CONFIG_ARCH_MX1ADS

/* now save a pointer to the cmdline_from_bootloader */

adr r3, __switch_data + 32 @ cmdline_from_bootloader

//r3=__switch_data+32=cmdline_from_bootloader的地址

ldmia r3, {r4} @ r4 = address of above

//r4=[r3]=cmdline_from_bootloader

str r12, [r4] // [r4]=r12

//将指向内核命令行的指针赋给cmdline_from_bootloader

#endif

#ifdef CONFIG_ALIGNMENT_TRAP

orr r0, r0, #2 @ ...........A.

#endif

bic r2, r0, #2 @ Clear 'A' bit

stmia r8, {r0, r2} @ Save control register values

b SYMBOL_NAME(start_kernel) //跳到start_kernel处执行

35__lookup_processor_type

36__lookup_architecture_type

__lookup_processor_type例程用于检查当前的处理器是否有效,它没有输入,使用了r5,r6,r7三个寄存器,返回r8=__cpu_mmu_flags,r9=处理器ID,r10指向处理器结构

__lookup_architecture_type用于检查当前的板子是否有效,它需要输入r1为板子ID,使用了r2, r3, r4三个寄存器,返回r5=物理内存的起始地址,r6=IO空间的起始地址,r7=IO空间虚拟地址的段号

/*

* Read processor ID register (CP#15, CR0), and look up in the linker-built

* supported processor list. Note that we can't use the absolute addresses

* for the __proc_info lists since we aren't running with the MMU on

* (and therefore, we are not in the correct address space). We have to

* calculate the offset.

*

* Returns:

* r5, r6, r7 corrupted

* r8 = page table flags

* r9 = processor ID

* r10

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics