1.介绍..........................................................................................2 |
2.相关定义介绍..........................................................................3 |
2.1.TEXTADDR......................................................3 |
2.2.stext....................................................................3 |
2.3.swapper_pg_dir..................................................4 |
2.4.(M)pgtbl.............................................................4 |
2.5.(M)krnladr..........................................................5 |
2.6..proc.info段.......................................................5 |
2.7.__proc_info_begin/__proc_info_end.................6 |
2.8..arch.info段.......................................................7 |
2.9.__arch_info_begin/__arch_info_end.................8 |
3.代码分析..................................................................................9 |
3.1.KERNEL ENTRY..............................................9 |
3.2.__arm920_setup...............................................11 |
3.3.__ret.................................................................13 |
3.4.__mmap_switched...........................................14 |
3.5.__lookup_processor_type................................16 |
3.6.__lookup_architecture_type.............................16 |
3.7.__create_page_tables.......................................19 |
这是一篇对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的代码则会用下划线字体表示 |
TEXTADDR是内核Image的映像地址,也是内核Image所处的虚拟地址,它在系统内核空间起始地址——通常是0xC0000000(这相对应于物理内存开始的地方)+32K的位置,也就是0xC0008000处TEXTADDR的赋值在arch/arm/Makefile文件中:(0xC0008000) |
ifeq ($(CONFIG_CPU_32),y) |
LDSCRIPT = arch/arm/vmlinux-armv.lds.in |
在内核映像之前的16K空间用来存放内核的页目录表,这就是为什么TEXTADDR要在系统要放在0xC0008000的缘故——它必须留出足够的物理空间来放页表 |
在head-armv.S中TEXTADDR将被检测: |
#if (TEXTADDR & 0xffff) != 0x8000 //TEXTADDR必须为0xXXXX8000 |
#error TEXTADDR must start at 0xXXXX8000 |
stext是TEXTADDR相对应的物理地址,由于内核空间的起始地址(0xC0000000)相对应的物理地址就是最低物理内存的地址,因此stext就是最低物理内存+32K处(虽然这并不是有内核指定,而是由bootloader指定),它的赋值在连接脚本vmlinux-armv.lds.in中实现:(0x08008000) |
. = TEXTADDR; //此处的映像地址为TEXTADDR |
.init : { /* Init code and data */ |
_stext = .; //stext为此处的物理地址 |
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) / |
pgtbl是一个用于获得启动页表物理地址的宏,它将stext减去16K给reg,它也在head-armv.S中定义: |
.macro pgtbl, reg, rambase |
sub /reg, /reg, #0x4000 //reg=stext-0x4000 |
这个宏用于由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 |
.macro krnladr, rd, pgtable, rambase |
bic /rd, /pgtable, #0x000ff000 |
.proc.info段中存放的是各种处理器的信息,每个处理器的信息用一个proc_info_list结构来表示,这个结构在include/asm/procinfo.h文件中声明: |
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 */ |
struct proc_info_item *info; |
虽然结构是在这里声明,但是真正的定义却是在proc-arm920.S文件的最后: |
.section ".proc.info", #alloc, #execinstr //声明以下代码在.proc.info段中 |
.type __arm920_proc_info,#object |
.long 0x41009200 //cpu_val |
.long 0xff00fff0 //cpu_mask |
.long 0x00000c1e @ mmuflags |
b __arm920_setup //是的!这是一条跳转指令 |
.long HWCAP_SWP | HWCAP_HALF | HWCAP_26BIT |
.long arm920_processor_functions |
2.7.__proc_info_begin/__proc_info_end |
__proc_info_begin是.proc.info段的起始地址,而__proc_info_end是终止地址,他们存放在head-armv.S中: |
在连接脚本vmlinux.lds.in文件中,他们被赋值: |
.arch.info段类似于.proc.info段,不过它是用来存放板子信息的,它的定义是在include/asm/mach/arch.h文件中: |
* 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 |
而具体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) |
BOOT_MEM(0x08000000, 0x00200000, 0xf0200000) |
//phys_ram=0x08000000,phys_io=0x00200000, |
//io_pg_offset = ((0xf0200000)>>18)&0xfffc=0x00003c08 |
//右移20位获得IO空间虚拟地址在页表内偏移再乘以四(4个字节) |
2.9.__arch_info_begin/__arch_info_end |
__arch_info_begin是.arch.info段的起始地址,而__arch_info_end是终止地址,他们存放在head-armv.S中: |
在连接脚本vmlinux.lds.in文件中,他们被赋值: |
下面是整个内核Image的入口,进入后必须满足以下条件,即r0为0,r1为板子ID,MMU和D-cache关闭,这其中r1的architecture ID是由bootloader传进来的 |
* Kernel startup entry point. |
* r1 - unique architecture number |
* See linux/arch/arm/tools/mach-types for the complete list of numbers |
.section ".text.init",#alloc,#execinstr //以下代码属于“.text.init”段 |
#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 |
bl __lookup_processor_type //检查处理器类型 |
//r8 = __cpu_mmu_flags, r8 = 0x00000c1e |
teq r10, #0 @ invalid processor? //如果是无效处理器 |
moveq r0, #'p' @ yes, error 'p' //打印“p”和出错信息 |
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”和出错信息 |
bl __create_page_tables //建立页表 |
adr lr, __ret @ return address //将返回地址存放在lr中 |
add pc, r10, #12 @ initialise processor |
//跳转到处理器结构+12的位置,参看.proc.info段可以知 |
//道,这里是一条跳转指令“b __arm920_setup”,因此再 |
//跳转到proc-arm920.S中的__arm920_setup函数入口处 |
__arm920_setup函数在proc-arm920.S文件中,在页表建立起来之后,此函数进行一些开启MMU之前的初始化操作 |
.section ".text.init", #alloc, #execinstr |
mcr p15, 0, r0, c7, c7 @ invalidate I,D caches on v4 |
mcr p15, 0, r0, c7, c10, 4 @ drain write buffer on v4 |
mcr p15, 0, r0, c8, c7 @ invalidate I,D TLBs on v4 |
mcr p15, 0, r4, c2, c0 @ load page table pointer |
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) |
bic r0, r0, #0x1000 @ ...0 000. .... 000. |
orr r0, r0, #0x2100 @ ..1. ...1 ..11 ...1 |
#ifdef CONFIG_CPU_ARM920_D_CACHE_ON |
orr r0, r0, #0x0004 @ .... .... .... .1.. |
#ifdef CONFIG_CPU_ARM920_I_CACHE_ON |
orr r0, r0, #0x1000 @ ...1 .... .... .... |
//修改r0的某些位,使它的后16位为:XX1I 0001 XX11 0D01 |
//其中I表示根据CONFIG_CPU_ARM920_I_CACHE_ON设定 |
//D表示根据CONFIG_CPU_ARM920_D_CACHE_ON设定 |
//A(bit1)=0,关闭Alignment checking |
//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处 |
在proc-arm920.S中的__arm920_setup函数进行过一些启动MMU之前的初始化工作后,根据lr寄存器中的值跳转到__ret处执行,这里做了三件事: |
1. 首先将__switch_data处的值(即__mmap_switched)作为返回值存放在lr寄存器中 |
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(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传来的参数地址 |
__ret: ldr lr, __switch_data //这里保存的是内核空间地址! |
mcr p15, 0, r0, c1, c0 //将r0中的值送回c1,开启MMU!! |
mov r0, r0 //执行三次NOP操作,清空流水线 |
mov pc, lr //跳到__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 |
adr r3, __switch_data + 4 //r3=__switch_data+4 |
ldmia r3, {r2, r4, r5, r6, r7, r8, sp}@ r2 = compat |
//r4=bss_start=.bss段的起始地址 |
//r6=processor_id=保存处理器ID的地址 |
//r7=__machine_arch_type=保存板子ID的地址 |
//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) |
//在内核入口的一开始,r1中的包含指向内核命令行的指针被送到fp寄存器中,//现在将它送给r12 |
mov fp, #0 @ Clear BSS (and zero fp) |
1: cmp r4, r5 //将整个.bss段清零 |
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 |
#ifdef CONFIG_ALIGNMENT_TRAP |
orr r0, r0, #2 @ ...........A. |
bic r2, r0, #2 @ Clear 'A' bit |
stmia r8, {r0, r2} @ Save control register values |
b SYMBOL_NAME(start_kernel) //跳到start_kernel处执行 |
3.5.__lookup_processor_type |
3.6.__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 |
* r10 = pointer to processor structure |
__lookup_processor_type: //__lookup_processor_type函数入口 |
adr r5, 2f // r5=__proc_info_end的物理地址,此处伪指令adr会被 |
//编译成add r5, pc , #xx获得运行时地址 |
ldmia r5, {r7, r9, r10} // r7=__proc_info_end |
// r10=__proc_info_end的映像地址 |
sub r5, r5, r10 @ convert addresses // r5=物理地址减映像地址的差 |
add r7, r7, r5 @ to our address space |
add r10, r9, r5 //r7=__proc_info_end对应的物理地址 |
//r10=__proc_info_start对应的物理地址 |
mrc p15, 0, r9, c0, c0 @ get processor id //r9=处理器ID |
1: ldmia r10, {r5, r6, r8} @ value, mask, mmuflags |
// r5=cpu_val,r6=cpu_mask,r8=__cpu_mmu_flags |
// r5=0x41009200, r6=0xff00fff0, r8=0x00000c1e |
and r6, r6, r9 @ mask wanted bits |
teq r5, r6 //比较两个处理器ID是否相同,如果相同返回 |
add r10, r10, #36 @ sizeof(proc_info_list) |
cmp r10, r7 //如果不同尝试下一个处理器结构 |
blt 1b //直到__proc_info_end |
mov r10, #0 @ unknown processor |
* Look in include/asm-arm/procinfo.h and arch/arm/kernel/arch.[ch] for |
* more information about the __proc_info and __arch_info structures. |
2: .long __proc_info_end //.proc.info段的终止映像地址 |
.long __proc_info_begin //.proc.info段的起始映像地址 |
.long 2b //__proc_info_end的映像地址 |
.long __arch_info_begin //.arch.info段的起始映像地址 |
.long __arch_info_end //.arch.info段的终止映像地址 |
* Lookup machine architecture in the linker-build list of architectures. |
* Note that we can't use the absolute addresses for the __arch_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. |
* r1 = machine architecture number |
* r5 = physical start address of RAM |
* r6 = physical address of IO |
* r7 = byte offset into page tables for IO |
__lookup_architecture_type: //__lookup_architecture_type函数入口 |
adr r4, 2b // r4=__proc_info_end的物理地址 |
ldmia r4, {r2, r3, r5, r6, r7} @ throw away r2, r3 |
// r3=__proc_info_start,丢弃 |
// r5=__proc_info_end的映像地址 |
sub r5, r4, r5 @ convert addresses // r5=物理地址减映像地址的差 |
add r4, r6, r5 @ to our address space |
add r7, r7, r5 //r7=__proc_info_end对应的物理地址 |
//r10=__proc_info_start对应的物理地址 |
//同__lookup_processor_type一样,将映像地址转化为物理地址 |
1: ldr r5, [r4] @ get machine type // r5=板子ID r5=0x000000a0 |
add r4, r4, #SIZEOF_MACHINE_DESC |
mov r7, #0 @ unknown architecture |
2: ldmib r4, {r5, r6, r7} @ found, get results |
// r5=物理内存的起始地址, r5=0x08000000 |
// r6=IO空间的起始地址 r6=0x00200000 |
// r7=IO空间虚拟地址的段号 r7=0x00003c08 |
__create_page_tables用来建立初始化一级页表,此时各个寄存器的情况如下: |
r5=物理内存的起始地址 r5=0x08000000 |
r6=IO空间的起始地址 r6=0x00200000 |
r7=IO空间虚拟地址的段号 r7=0x00003c08 |
r8 = __cpu_mmu_flags r8=0x00000c1e |
pgtbl r4, r5 @ page table address // r4=stext-16K(即页表地址) |
* Clear the 16K level 1 swapper page table |
*/ //将页表空间(stext-16K到stext)的16K空间清零 |
mov r0, r4 // r0 = r4 = stext-16K ,r4=0x08004000 |
add r2, r0, #0x4000 // r2 = r0+16K = stext,r2=0x08008000 |
1: str r3, [r0], #4 //循环清零页表空间 |
//此时:r0 = r2 = stext ,r4 = stext-16K,r3 = 0 |
* Create identity mapping for first MB of kernel to |
* cater for the MMU enable. This identity mapping |
* will be removed by paging_init() |
krnladr r2, r4, r5 @ start of kernel |
add r3, r8, r2 @ flags + kernel base |
str r3, [r4, r2, lsr #18] @ identity mapping |
//建立恒等映射(为了开启MMU前后地址空间的一致性),即: |
//映射虚拟空间:0x08000000-0x08100000 |
//到物理空间:0x08000000-0x08100000 |
// r2= stext&0xfff00fff =内核物理空间起始地址所在的段的起始地址 |
// r3 = r2 + r8 =内核物理空间所在段的一级描述符 |
//将r2(内核空间起始物理地址)右移20位得到内核起始地址在页//表内的索引,再将它左移两位(即乘以4,页表内每个描述符占四//个字节)后加上页表基址就得到了内核空间起始地址所在段的一级//描述符的地址,r3存入此处 |
// [r4, r2, lsr #18]=0x08004200 |
* Now setup the pagetables for our kernel direct |
* mapped region. We round TEXTADDR down to the |
* nearest megabyte boundary. |
// r4=页表物理地址,r4=0x08004000 |
//r2=内核空间起始物理地址,r2 = 0x08000000 |
//r3=物理内核空间所在段的一级描述符,r3= 0x08000c1e |
add r0, r4, #(TEXTADDR & 0xff000000) >> 18 @ start of kernel |
str r2, [r0] @ PAGE_OFFSET + 0MB |
//映射虚拟空间:0xC0000000-0xC0100000 |
//到物理空间:0x0800000-0x08100000 |
// r0=0x08004000+0x00003000=0x08007000 |
建立此段映射的原因是某些内核空间起始地址非16M对齐的板子将一些数据(比如内核参数之类)存放在内核空间之前但在16M对齐之后的1MB内的位置,比如内核空间起始地址为0x00200000,而在0x0000000-0x00100000处有数据。这段映射的目的就是在启动MMU之后使内核能够找到这些数据,而在内核空间起始地址16M对齐的板子中,这段映射将在下一步映射内核空间第一个MB时被覆盖 |
add r0, r0, #(TEXTADDR & 0x00f00000) >> 18 |
str r3, [r0], #4 @ KERNEL + 0MB |
//映射虚拟空间:0xC0000000-0xC0100000 |
//到物理空间:0x0800000-0x08100000 (覆盖上一次映射) |
str r3, [r0], #4 @ KERNEL + 1MB |
//映射虚拟空间:0xC0100000-0xC0200000 |
//到物理空间:0x0810000-0x08200000 |
str r3, [r0], #4 @ KERNEL + 2MB |
str r3, [r0], #4 @ KERNEL + 3MB |
* Ensure that the first section of RAM is present. |
* 1. the RAM is aligned to a 32MB boundary |
* 2. the kernel is executing in the same 32MB chunk |
bic r0, r0, #0x01f00000 >> 18 @ round down |
and r2, r5, #0xfe000000 @ round down |
add r3, r8, r2 @ flags + rambase |
//映射虚拟空间:0xC0000000-0xC0100000 |
//到物理空间:0x08000000-0x08100000 |
bic r8, r8, #0x0c @ turn off cacheable |
//更改一级描述符的标志位,去除cacheble和bufferable位 |
//CONFIG_DEBUG_LL宏的定义表明low level debugging的开启 |
//开启了ll表明可以使用printch,printascii和printhexX等直接操作 |
//串口的调试函数,为此,必须在内核正式页表建立之前建立临时的 |
//IO空间映射以满足在MMU开启之后这些函数能正常的工作,这 |
* Map in IO space for serial debugging. |
* This allows debug messages to be output |
* via a serial console before paging_init. |
rsb r3, r7, #0x4000 @ PTRS_PER_PGD*sizeof(long) |
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e
added ecosystem file for PM2 4 年前
5def40a3
Add host customization support for the NodeJS version 4 年前
旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。
所有评论(0)