Chinaunix首页 | 论坛 | 博客
  • 博客访问: 74610
  • 博文数量: 22
  • 博客积分: 1475
  • 博客等级: 上尉
  • 技术积分: 260
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-04 23:39
文章分类

全部博文(22)

文章存档

2013年(1)

2011年(6)

2010年(15)

我的朋友

分类: LINUX

2010-04-29 13:29:10

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();
}
 
阅读(1162) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~