head.S是linux启动后的第一个文件,主要完成以下功能:
1、检查处理器信息,并保存;
2、检查平台号,并保存;
3、创建页表,并开启MMU功能;
4、对内核data section、bbs section作调整和初始化,保存必要的变量,设置栈指针跳到start_kernel;
实现过程:
//定义进程0的页表基地址,位于内核代码前16k,注意这是一个虚拟地址。
.globl swapper_pg_dir
.equ swapper_pg_dir, TEXTADDR - 0x4000
//这个宏用于计算内核页表的基地址,是物理地址。
.macro pgtbl, rd, phys
adr \rd, stext
sub \rd, \rd, #0x4000
.endm
//定义所属为init段
__INIT
//定义个函数地址
.type stext, %function
ENTRY(stext)
//设置处理器SVC模式,禁止IRQ中断、FIQ中断。
msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | MODE_SVC
//查找处理器类型并判断是否有效。
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10, r5 @ invalid processor (r5=0)?
beq __error_p @ yes, error 'p'
//查找平台类型并判断是否有效。
bl __lookup_machine_type @ r5=machinfo
movs r8, r5 @ invalid machine (r5=0)?
beq __error_a @ yes, error 'a'
//创建页表。
bl __create_page_tables
//把__switch_data地址处的内容放到r13,也就是r13=_mmap_swithced,在__enable_mmu之后会返回到这里执行。要注意这个r13放的可以虚拟地址,在打开MMU之后跳到这。
ldr r13, __switch_data
//在PROCINFO_INITFUNC被调用之后执行_enable_mmu
adr lr, __enable_mmu @ return (PIC) address
//调用处理器相关的初始化函数(arch/arm/mm/proc-arm920.S -arm920_setup)。
add pc, r10, #PROCINFO_INITFUNC
//
.type __switch_data, %object
__switch_data:
.long __mmap_switched
.long __data_loc @ r4
.long __data_start @ r5
.long __bss_start @ r6
.long _end @ r7
.long processor_id @ r4
.long __machine_arch_type @ r5
.long cr_alignment @ r6
.long init_thread_union + THREAD_START_SP @ sp
/*
* The following fragment of code is executed with the MMU on, and uses
* absolute addresses; this is not position independent.
*
* r0 = cp#15 control register
* r1 = machine ID
* r9 = processor ID
*/
.type __mmap_switched, %function
__mmap_switched:
//r4=_data_loc,r5=_data_start,r6=_bss_start,r7=_end
adr r3, __switch_data + 4
ldmia r3!, {r4, r5, r6, r7}
//_data_loc==_data_start,不用移动。
cmp r4, r5 @ Copy data segment if needed
1: cmpne r5, r6
ldrne fp, [r4], #4
strne fp, [r5], #4
bne 1b
//清除BSS段。
mov fp, #0 @ Clear BSS (and zero fp)
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b
ldmia r3, {r4, r5, r6, sp}
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
从下面开始说上面的子调用:
1、CPU信息和平台信息的检查
/*
* 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:
* r3, r4, r6 corrupted
* r5 = proc_info pointer in physical address space
* r9 = cpuid
*/
.type __lookup_processor_type, %function
__lookup_processor_type:
//把下面红色的3位置处的地址放到r3,是物理地址。
adr r3, 3f
//把红3放到r9,r5=__proc_info_begin,r6=__proc_info_end
ldmda r3, {r5, r6, r9}
//计算物理地址与虚拟地址的偏移放到r3.
sub r3, r3, r9 @ get offset between virt&phys
//把r5 r6转化为物理地址。
add r5, r5, r3 @ convert virt addresses to
add r6, r6, r3 @ physical address space
//从协处理器读出cpu的ID放到r9.
mrc p15, 0, r9, c0, c0 @ get processor id
//把proc_info_list里的cpu_val和cpu_mask读到r3和r4.处理器信息存放在(arch/arm/mm/arm/proc-arm920.S cpu_val=0x41009200,cpu_mask=0xff00fff0).判断是否cpu_val==cpu_mask&r9)如果失败r5=0。
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
/*
* This provides a C-API version of the above function.
*/
//这个是C函数调用的API,如此学一下如何写被C调用的汇编函数。
ENTRY(lookup_processor_type)
stmfd sp!, {r4 - r6, r9, lr}
bl __lookup_processor_type
mov r0, r5
ldmfd sp!, {r4 - r6, r9, pc}
/*
* Look in include/asm-arm/procinfo.h and arch/arm/kernel/arch.[ch] for
* more information about the __proc_info and __arch_info structures.
*/
.long __proc_info_begin
.long __proc_info_end
3: .long .
.long __arch_info_begin
.long __arch_info_end
/*
* 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
* Returns:
* r3, r4, r6 corrupted
* r5 = mach_info pointer in physical address space
*/
//这个和上面那个__lookup_processor_type语法格式是完全一样的,把loader传过来的机器号和从内核里定义的相比较看是否相等,如果相等会把这个mach_info存放到r5里,mach_info在文件arch/arm/mach-s3c2410/mach-smdk2410.c里,而机器号在include/asm/mach-type.h里。
.type __lookup_machine_type, %function
__lookup_machine_type:
adr r3, 3b
ldmia r3, {r4, r5, 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: ldr r3, [r5, MACHINFO_TYPE] @ get machine type
teq r3, r1 @ matches loader number?
beq 2f @ found
add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc
cmp r5, r6
blo 1b
mov r5, #0 @ unknown machine
2: mov pc, lr
/*
* This provides a C-API version of the above function.
*/
ENTRY(lookup_machine_type)
stmfd sp!, {r4 - r6, lr}
mov r1, r0
bl __lookup_machine_type
mov r0, r5
ldmfd sp!, {r4 - r6, pc}
2、创建面表
/*
* Setup the initial page tables. We only setup the barest
* amount which are required to get the kernel running, which
* generally means mapping in the kernel code.
*
* r8 = machinfo
* r9 = cpuid
* r10 = procinfo
*
* Returns:
* r0, r3, r5, r6, r7 corrupted
* r4 = physical page table address
*/
.type __create_page_tables, %function
__create_page_tables:
//把SDRAM的物理地址放到r5.
ldr r5, [r8, #MACHINFO_PHYSRAM] @ physram
//用pgtbl宏计算出面表的物理地址,在内核代码前16k
pgtbl r4, r5 @ page table address
/*
* Clear the 16K level 1 swapper page table
*/
//把16K页表内容清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
//从处理器信息结构读出MMU参数。
ldr r7, [r10, #PROCINFO_MMUFLAGS] @ mmuflags
/*
* Create identity mapping for first MB of kernel to
* cater for the MMU enable. This identity mapping
* will be removed by paging_init(). We use our current program
* counter to determine corresponding section base address.
*/
//为了打开MMU功能时不出问题,把当前物理地址的1Mb范围内与虚拟地址做相等映射。
mov r6, pc, lsr #20 @ start of kernel section
orr r3, r7, r6, lsl #20 @ flags + kernel base
//[r4, r6, lsl #2]代表页表的项所在的地址,这个#2是因为每个页表项占用4个字节,r3代表向相应的页表项地址所填写的内容,也就是要映射的虚拟地址。
str r3, [r4, r6, lsl #2] @ identity mapping
/*
* Now setup the pagetables for our kernel direct
* mapped region. We round TEXTADDR down to the
* nearest megabyte boundary. It is assumed that
* the kernel fits within 4 contigous 1MB sections.
*/
//把内核的前4MB虚拟地址映射到相应的物理地址。
add r0, r4, #(TEXTADDR & 0xff000000) >> 18
str r3, [r0, #(TEXTADDR & 0x00f00000) >> 18]!
add r3, r3, #1 << 20
str r3, [r0, #4]! @ KERNEL + 1MB
add r3, r3, #1 << 20
str r3, [r0, #4]! @ KERNEL + 2MB
add r3, r3, #1 << 20
str r3, [r0, #4] @ KERNEL + 3MB
/*
* Then map first 1MB of ram in case it contains our boot params.
*/
//把内核开始地址映射到物理ram的开始处。
add r0, r4, #VIRT_OFFSET >> 18//页表项的偏移。
orr r6, r5, r7//物理ram的开如地址。
str r6, [r0]
mov pc, lr
3、调用处理器相关的初始化函数
__arm920_setup:
mov r0, #0
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
//读出cp15的c1寄存器到r0,清除并设置,最终目录如下,使能MMU,禁止内存地址对齐检查功能,使能cache,禁止写入缓存,控制中断向量表的地址为高端。
mrc p15, 0, r0, c1, c0 @ get control register v4
ldr r5, arm920_cr1_clear
bic r0, r0, r5
ldr r5, arm920_cr1_set
orr r0, r0, r5
mov pc, lr
//这里的arm920_cr1_clear=0x3f3f, arm920_cr1_set=0x3135
4、打开MMU
/*
* Setup common bits before finally enabling the MMU. Essentially
* this is just loading the page table pointer and domain access
* registers.
*/
.type __enable_mmu, %function
__enable_mmu:
#ifdef CONFIG_ALIGNMENT_TRAP
//设置地址对齐检查功能。
orr r0, r0, CR_A
#else
bic r0, r0, #CR_A
#endif
#ifdef CONFIG_CPU_DCACHE_DISABLE
bic r0, r0, #CR_C
#endif
#ifdef CONFIG_CPU_BPREDICT_DISABLE
bic r0, r0, #CR_Z
#endif
#ifdef CONFIG_CPU_ICACHE_DISABLE
bic r0, r0, #CR_I
#endif
//设置MMU中的域
mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \
domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \
domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \
domain_val(DOMAIN_IO, DOMAIN_CLIENT))
mcr p15, 0, r5, c3, c0, 0 @ load domain access register
//把页表基址设置MMU到MMU的C2。
mcr p15, 0, r4, c2, c0, 0 @ load page table pointer
b __turn_mmu_on
/*
* Enable the MMU. This completely changes the structure of the visible
* memory space. You will not be able to trace execution through this.
* If you have an enquiry about this, *please* check the linux-arm-kernel
* mailing list archives BEFORE sending another post to the list.
*
* r0 = cp#15 control register
* r13 = *virtual* address to jump to upon completion
*
* other registers depend on the function called upon completion
*/
.align 5
.type __turn_mmu_on, %function
__turn_mmu_on:
mov r0, r0
//打开MMU
mcr p15, 0, r0, c1, c0, 0 @ write control reg
mrc p15, 0, r3, c0, c0, 0 @ read id reg
mov r3, r3
mov r3, r3
mov pc, r13
asmlinkage void __init start_kernel(void)
{
char * command_line;
extern struct kernel_param __start___param[], __stop___param[];
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
lock_kernel();
page_address_init();
printk(KERN_NOTICE);
printk(linux_banner);
//平台相关初始化,SDRAM、CPU。
setup_arch(&command_line);
//smp
setup_per_cpu_areas();
smp_prepare_boot_cpu();
//进程调度队列初始化。
sched_init();
preempt_disable();
//设置每个节点的zonelist。
build_all_zonelists();
//smp
page_alloc_init();
printk(KERN_NOTICE "Kernel command line: %s\n", saved_command_line);
parse_early_param();
parse_args("Booting kernel", command_line, __start___param,
__stop___param - __start___param,
&unknown_bootoption);
sort_main_extable();
//copy 中断向量表。
trap_init();
rcu_init();
//初始化中断向量表,调用平台中断初始化函数。
init_IRQ();
//进程PID哈希表。
pidhash_init();
//定时器软中断。
init_timers();
//软中断tasklet.
softirq_init();
//初始化时钟中断。
time_init();
//控制台初始化,开始打印。
console_init();
if (panic_later)
panic(panic_later, panic_param);
profile_init();
//打开CPU的中断。
local_irq_enable();
#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start && !initrd_below_start_ok &&
initrd_start < min_low_pfn << PAGE_SHIFT) {
printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - "
"disabling it.\n",initrd_start,min_low_pfn << PAGE_SHIFT);
initrd_start = 0;
}
#endif
//虚拟文件系统数据结构分配。
vfs_caches_init_early();
//把bootmem不用的内存回收到页框分配器。
mem_init();
//slab分配器初化。
kmem_cache_init();
//NUMA
setup_per_cpu_pageset();
numa_policy_init();
if (late_time_init)
late_time_init();
calibrate_delay();
//PID位图初始化。
pidmap_init();
//空
pgtable_cache_init();
prio_tree_init();
anon_vma_init();
#ifdef CONFIG_X86
if (efi_enabled)
efi_enter_virtual_mode();
#endif
//进程创建数据结构初始化。
fork_init(num_physpages);
//多种SLAB分配器的分配。
proc_caches_init();
buffer_init();
unnamed_dev_init();
//空。
key_init();
security_init();
//文件系统相关数据初始化。
vfs_caches_init(num_physpages);
//??
radix_tree_init();
//信号。
signals_init();
/* rootfs populating might need page-writeback */
page_writeback_init();
//PROC文件系统。
#ifdef CONFIG_PROC_FS
proc_root_init();
#endif
cpuset_init();
check_bugs();
acpi_early_init();
//启动INIT进程作剩余部分初始化。
rest_init();
}