- 论坛徽章:
- 0
|
kernel编译过程:
1. ld 所有.o vmlinux.o
2. ld -T arch/arm/kernel/vmlinux.lds vmlinux.o 到 vmlinux (elf)
3. objcpy生成Image 二进制kernel镜像。这个Image很关键,kernel的所有可执行代码和数据都在这个里面。并且文件开始部分为内核的入口代码stext。
4. 将Image压缩,并增加解压代码。生成elf格式的compressed/vmlinux (elf)
5. objcpy生成zImage,最终的压缩过的包含自解压代码的二进制kernel镜像。
bootloader流程:
一般情况下,arm平台的kernel Image(或者zImage)存放在存储中(emmc,sd卡,硬盘等)的某个分区中(没有文件系统),一般是zImage。
bootloader作用初始化硬件,显示启动画面等等。最终要的是从存储中加载kernel zImage并且跳到zImage执行。
ps:x86的bootloader一般是grub,功能比较强大,kernel image也是作为文件存放的。
zImage解压:
zImage会解压kernel,并且拷贝到zreladdr物理地址,然后跳转过去执行kernel。
zreladdr是平台相关的,一般的平台是0x8000,这样内核的物理地址和虚拟地址刚好是3G偏移,后面建页表会用到。
当然假设现在我们的平台的物理内存前1M是保留给其他用途的,所以这里的地址是0x108000.
下面kernel从0x108000开始执行了。
kernel启动流程:
1. Image开始代码为什么是stext。这个是通过vmlinux.lds链接而来的。
vmlinux.lds:
SECTIONS
{
.init : {
_stext = .;
_sinitext = .;
HEAD_TEXT
INIT_TEXT
...
INIT_CALLS
}
...
}
#define HEAD_TEXT *(.head.text)
#define INIT_CALLS \
__initcall_start = .;
*(.initcallearly.init) \
__early_initcall_end = .;
*(.initcall0.init)\
*(.initcall0s.init)\
...
*(.initcall7.init)\
*(.initcall7s.init)\
__initcall_end = .;
_stext,_sinittext, __initcall_start,__initcall_end不是变量,它们不占用代码段或者数据段的空间。它们是一个符号表示一个地址,链接的时候,连接器把kernel用到这些标记的代码进行重定向。从vmlinux的readelf -s中可以看到。
2.arm/kernel/head.S
.section ".head.text", "ax"
ENTRY(stext)
设置arm svc mode,关中断
读取cpuid。调用__lookup_processor_type查找struct proc_info结构体(比如arm v7)
调用__create_page_tables创建临时页表
调用proc_info的__cpu_flush方法。一般就是初始化tlb,cache,mmu state为enable mmu做准备
调用__enable_mmu使能mmu
调用__map_switched,建立c运行环境,跳转到start_kernel函数。
ENDPROC(stext)
由于一开始bootloader不一定会加载image到对应的链接地址,并且mmu没有使能。所以kernel开始的代码一定要是位置无关的。
/*
* 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,
* r1 = machine nr, r2 = atags or dtb pointer.
*/
ENTRY(stext)
setmode PSR_F_BIT | PSR_I_BIT | SVC_MODE, r9 @ ensure svc mode
@ and irqs disabled
mrc p15, 0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10, r5 @ invalid processor (r5=0)?
THUMB( it eq ) @ force fixup-able long branch encoding
beq __error_p @ yes, error 'p'
//PLAT_PHYS_OFFSET是平台相关的kernel可以使用的物理地址。
//根据我们的假设前1M内存是保留用途的话,这里PLAT_PHYS_OFFSET就必须设置为0x100000
ldr r8, =PLAT_PHYS_OFFSET
/*
* r1 = machine no, r2 = atags or dtb,
* r8 = phys_offset, r9 = cpuid, r10 = procinfo
*/
bl __vet_atags
bl __create_page_tables
/*
* The following calls CPU specific code in a position independent
* manner. See arch/arm/mm/proc-*.S for details. r10 = base of
* xxx_proc_info structure selected by __lookup_processor_type
* above. On return, the CPU will be ready for the MMU to be
* turned on, and r0 will hold the CPU control register value.
*/
ldr r13, =__mmap_switched @ address to jump to after
@ mmu has been enabled
adr lr, BSYM(1f) @ return (PIC) address
mov r8, r4 @ set TTBR1 to swapper_pg_dir
ARM( add pc, r10, #PROCINFO_INITFUNC ) //先执行proinfo initfunc,然后跳到lr=__enable_mmu执行。
THUMB( add r12, r10, #PROCINFO_INITFUNC )
THUMB( mov pc, r12 )
1: b __enable_mmu //然后打开mmu,代码最后会跳转到r13=__mmap_switched执行。
ENDPROC(stext)
r1=machine nr, r2=atags
bootloader需要设置r1=machine nr,定义在arch/arm/tools/mach-types文件中,每个板子都有相应的machine nr。
kernel启动中,会把r1保存到__machine_arch_type全局变量中。并且#define machine_arch_type __machine_arch_type
setup_arch()方法中,板级相关的machinde_desc *mdesc = setup_machine_tags(machine_arch_type);
实际上就是根据这个machine nr同kernel中所有编译进去的machine desc进行匹配
所以,传给kernel的machine nr一定要是正确的否则找不到相应的machine desc。
atags是一个地址,bootloader要在里面保存一组atag格式的信息,一般都有core,ramdisk,cmdline
struct tag_header {
__u32 size; //这个tag的大小(4个字节单位)
__u32 tag; //tag类型,core?ramdisk?cmdline?
};
struct tag {
struct tag_header hdr;
union {
struct tag_core core;
struct tag_mem32 mem;
struct tag_videotext videotext;
struct tag_ramdisk ramdisk;
struct tag_initrd initrd;
struct tag_serialnr serialnr;
struct tag_revision revision;
struct tag_videolfb videolfb;
struct tag_cmdline cmdline;
struct tag_acorn acorn;
struct tag_memclk memclk;
} u;
};
Setup.c (linux-3.0.49\arch\arm\kernel):__tagtable(ATAG_CORE, parse_tag_core);
Setup.c (linux-3.0.49\arch\arm\kernel):__tagtable(ATAG_MEM, parse_tag_mem32);
Setup.c (linux-3.0.49\arch\arm\kernel):__tagtable(ATAG_VIDEOTEXT, parse_tag_videotext);
Setup.c (linux-3.0.49\arch\arm\kernel):__tagtable(ATAG_RAMDISK, parse_tag_ramdisk);
Setup.c (linux-3.0.49\arch\arm\kernel):__tagtable(ATAG_SERIAL, parse_tag_serialnr);
Setup.c (linux-3.0.49\arch\arm\kernel):__tagtable(ATAG_REVISION, parse_tag_revision);
Setup.c (linux-3.0.49\arch\arm\kernel):__tagtable(ATAG_CMDLINE, parse_tag_cmdline);
一个典型的atags是:
core tag(8字节,size=2) + ramdisk tag (16字节, size=4) + cmdline tag(n字节)
kernel在setup_arch()->setup_machine_tags()中,会对atags的每个atag分别进行处理,获取ramdisk,cmdline等信息。
获取cpu id相关的体系的proc_info_list结构。比如armV7。里面主要保存了体系相关的mmu,tlb,cache等方法。
/*
* 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.
*
* r9 = cpuid
* Returns:
* r3, r4, r6 corrupted
* r5 = proc_info pointer in physical address space
* r9 = cpuid (preserved)
*/
__CPUINIT
__lookup_processor_type:
//adr指令将__lookup_processor_type_data的物理地址存到r3中。(实际上一条操作pc的add,sub指令)
adr r3, __lookup_processor_type_data
//r4存放__lookup_processor_type_data的虚拟地址。r5是__proc_info_begin变量虚拟地址。同理r6
//然后根据__lookup_processor_type_data的虚实地址的偏移计算出__proc_info_begin,__pproc_info_end的物理地址。
//这是内核启动前处理地址相关代码的常用方法
//然后就会在所有的proc_info中根据当前cpuid查找匹配的proc_info。
ldmia r3, {r4 - r6}
sub r3, r3, r4 @ get offset between virt&phys
add r5, r5, r3 @ convert virt addresses to
add r6, r6, r3 @ physical address space
1: ldmia r5, {r3, r4} @ value, mask
and r4, r4, r9 @ mask wanted bits
teq r3, r4
beq 2f
add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list)
cmp r5, r6
blo 1b
mov r5, #0 @ unknown processor
2: mov pc, lr
ENDPROC(__lookup_processor_type)
//.long __proc_info_begin表示,编译后这里存放__proc_info_begin符号指向的地址。
//__proc_info_begin不是一个变量并不占用数据段内存,链接脚本链接使用。
//链接完成后,这4个字节的内存就存放了 proc.init段开始的虚拟地址。
__lookup_processor_type_data:
.long .
.long __proc_info_begin
.long __proc_info_end
.size __lookup_processor_type_data, . - __lookup_processor_type_data
.macro pgtbl, rd, phys
add \rd, \phys, #TEXT_OFFSET - 0x4000
.endm
//这里建的页表是1M的页表。
__create_page_tables:
//0x104000 到 0x108000的16KB存放page table。
pgtbl r4, r8 @ page table address
//将这16KB页表清0,必须地。
mov r0, r4
mov r3, #0
add r6, r0, #0x4000
1: str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
str r3, [r0], #4
teq r0, r6
bne 1b
ldr r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags
//为__enable_mmu代码创建物理地址虚拟地址一致的页表映射。
//假设代码在0x123450,则建立映射关系0x123450->0x123450.
//否则就要飞了。
/*
* Create identity mapping to cater for __enable_mmu.
* This identity mapping will be removed by paging_init().
*/
adr r0, __enable_mmu_loc
ldmia r0, {r3, r5, r6}
sub r0, r0, r3 @ virt->phys offset
add r5, r5, r0 @ phys __enable_mmu
add r6, r6, r0 @ phys __enable_mmu_end
mov r5, r5, lsr #20
mov r6, r6, lsr #20
1: orr r3, r7, r5, lsl #20 @ flags + kernel base
str r3, [r4, r5, lsl #2] @ identity mapping
teq r5, r6
addne r5, r5, #1 @ next section
bne 1b
//建立映射0xc0000000开始总共kernel大小的映射。
//映射到r4开始指定的物理地址。这里是1M。
//所以zImage必须加载kernel到0x108000处,而且PLAT_PHYS_OFFSET必须设置为0x100000。
/*
* Now setup the pagetables for our kernel direct
* mapped region.
*/
mov r3, pc
mov r3, r3, lsr #20
orr r3, r7, r3, lsl #20
add r0, r4, #(KERNEL_START & 0xff000000) >> 18
str r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!
ldr r6, =(KERNEL_END - 1)
add r0, r0, #4
add r6, r4, r6, lsr #18
1: cmp r0, r6
add r3, r3, #1 << 20
strls r3, [r0], #4
bls 1b
/*
* Then map boot params address in r2 or
* the first 1MB of ram if boot params address is not specified.
*/
mov r0, r2, lsr #20
movs r0, r0, lsl #20
moveq r0, r8
sub r3, r0, r8
add r3, r3, #PAGE_OFFSET
add r3, r4, r3, lsr #18
orr r6, r7, r0
str r6, [r3]
mov pc, lr
ENDPROC(__create_page_tables)
这里没有建立ramdisk的对应的页表。假设ramdisk的物理地址是16M 0x1000000, 200KB大小。
atag解析的时候会把这个信息存放发到phys_initrd_start中去。
static int __init parse_tag_initrd(const struct tag *tag)
{
phys_initrd_start = __virt_to_phys(tag->u.initrd.start);
phys_initrd_size = tag->u.initrd.size;
return 0;
}
start_kernel()->setup_arch()->arm_memblock_init()
预留了这一段物理内存,那么这段物理内存就不会被kernel使用了。
计算物理内存对应的虚拟地址。虽然这个时候还没有建立页表映射。
但是这段物理内存必须落在直接映射区。PLAT_Phys_offset+896M内。
页表建立之后,这段虚拟地址就是ramdisk了。
if (phys_initrd_size) {
memblock_reserve(phys_initrd_start, phys_initrd_size);
/* Now convert initrd to virtual addresses */
initrd_start = __phys_to_virt(phys_initrd_start);
initrd_end = initrd_start + phys_initrd_size;
}
rootfs_initcall(populate_rootfs);
把ramdisk解压到rootfs中。
unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start);
之后ramdisk内容就在rootfs中了。之后一般就是启动rootfs的init进程了。
__data_loc -> _data ?
清除bbs。
保存processor_id, machine nr, atags到相应的全局变量给kernel使用。
设置sp=init_thread_union + THREAD_START_SP为0号进程的堆栈。
执行start_kernel(), ohyeah~~~:)大功告成
/*
* The following fragment of code is executed with the MMU on in MMU mode,
* and uses absolute addresses; this is not position independent.
*
* r0 = cp#15 control register
* r1 = machine ID
* r2 = atags/dtb pointer
* r9 = processor ID
*/
__INIT
__mmap_switched:
adr r3, __mmap_switched_data
ldmia r3!, {r4, r5, r6, r7}
cmp r4, r5 @ Copy data segment if needed
1: cmpne r5, r6
ldrne fp, [r4], #4
strne fp, [r5], #4
bne 1b
mov fp, #0 @ Clear BSS (and zero fp)
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b
ARM( ldmia r3, {r4, r5, r6, r7, sp})
THUMB( ldmia r3, {r4, r5, r6, r7} )
THUMB( ldr sp, [r3, #16] )
str r9, [r4] @ Save processor ID
str r1, [r5] @ Save machine type
str r2, [r6] @ Save atags pointer
bic r4, r0, #CR_A @ Clear 'A' bit
stmia r7, {r0, r4} @ Save control register values
b start_kernel
ENDPROC(__mmap_switched)
.align 2
.type __mmap_switched_data, %object
__mmap_switched_data:
.long __data_loc @ r4
.long _sdata @ r5
.long __bss_start @ r6
.long _end @ r7
.long processor_id @ r4
.long __machine_arch_type @ r5
.long __atags_pointer @ r6
.long cr_alignment @ r7
.long init_thread_union + THREAD_START_SP @ sp //堆栈地址。
.size __mmap_switched_data, . - __mmap_switched_data
|
|