Linux移植之内核启动过程引导阶段分析

Linux移植之make uImage编译过程分析中已经提到了uImage是一个压缩的包并且内含压缩程序,可以进行自解压。自解压完成之后内核代码从物理地址为0x30008000处开始运行。下面分析在进入C之前内核做的一些工作,以下是内核启动过程中打印出来的信息,其中Uncompressing Linux就是在自解压代码。make uImage编译的最后也给出了链接脚本****arch/arm/kernel/vmlinux.lds,以及链接的顺序arch/arm/kernel/head.o 是第一个。

分析arch/arm/kernel/vmlinux.lds可以知道程序入口的地址是stext,并且是.text.head段

1277 OUTPUT_ARCH(arm) 2278 ENTRY(stext) 3 4291 . = (0xc0000000) + 0x00008000; 5292 6293 .text.head : { 7294 _stext = .; 8295 _sinittext = .; 9296 *(.text.head) 10297 }

打开arch/arm/kernel/head.s。可见内核运行的第一条代码就是第79行的代码,从这条开始分析,首先将CPU设置为管理模式,并且关闭所有中断;然后获得CPU的id。

176 .section ".text.head", "ax" //.text.head段 277 .type stext, %function 378 ENTRY(stext) //入口地址stext 479 msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode//确保进入了管理模式 580 @ and irqs disabled //并且禁止中断 681 mrc p15, 0, r9, c0, c0 @ get processor id //获得处理器的CPU id,并且存入 r9中 782 bl __lookup_processor_type @ r5=procinfo r9=cpuid //调用函数,输入参数r9=cpuid。返回值r5=procinfo 883 movs r10, r5 @ invalid processor (r5=0)?//如果不支持当前CPU,则r5=0 984 beq __error_p @ yes, error 'p' //如果r5=0,则打印错误 1085 bl __lookup_machine_type @ r5=machinfo //调用函数,r5=返回值machinfo 1186 movs r8, r5 @ invalid machine (r5=0)? //如果不支持当前单板,则返回r5=0 1287 beq __error_a @ yes, error 'a' //如果r5=0,则打印错误 1388 bl __create_page_tables//创建一级页表以建立虚拟地址到物理地址的映射关系,后面再研究

接着调用__lookup_processor_type,它位于arch\arm\kernel\head-common.S。它的功能是比较当前CPU的id与内核支持的CPU的id是否相符合。这段代码在.proc.info.init段中从__proc_info_begin开始到__proc_info_end结束,寻找符合当前CPU的ID号的proc_info_list结构

1145 .type __lookup_processor_type, %function 2146 __lookup_processor_type: 3147 adr r3, 3f //r3 = 第178行代码的物理地址 4148 ldmda r3, {r5 - r7} //将r3地址开始的3个地址的内容赋给 r5、r6、r7 ;r5=__proc_info_begin,r6=__proc_info_end 5149 sub r3, r3, r7 @ get offset between virt&phys//r3=r3-r7,即物理地址与虚拟地址的差值 6150 add r5, r5, r3 @ convert virt addresses to//r5=__proc_info_begind对应的物理地址 7151 add r6, r6, r3 @ physical address space //r6=__proc_info_end对应的物理地址 8152 1: ldmia r5, {r3, r4} @ value, mask//r3、r4等于proc_info_list结构中的cpu_val、cpu_mask 9153 and r4, r4, r9 @ mask wanted bits//r4=r4&r9=cpu_mask&传入的cpuid 10154 teq r3, r4 //比较 11155 beq 2f //如果相等,则找到对应的proc_info_list结构,跳到160行 12156 add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list)//r5指向下一个proc_info_list结构 13157 cmp r5, r6 //是否已经比较完所有proc_info_list 14158 blo 1b //没有则继续比较 15159 mov r5, #0 @ unknown processor//比较完毕,但是没有找到匹配的proc_info_list结构,r5=0 16160 2: mov pc, lr//返回,返回的值为r5=proc_info_list 17 18176 .long __proc_info_begin 19177 .long __proc_info_end 20178 3: .long .//.表示当前这条代码链接后的虚拟地址 21179 .long __arch_info_begin 22180 .long __arch_info_end

其中__proc_info_begin、__proc_info_end被定义在arch\arm\kernel\vmlinux.lds中,它的意思是内核源码中有被定义为.proc.info.init的内容,它的起始地址是__proc_info_begin,结束地址为__proc_info_end。

1299 .init : { /* Init code and data */ 2230 *(.init.text) 3231 _einittext = .; 4232 __proc_info_begin = .; 5233 *(.proc.info.init) 6234 __proc_info_end = .;

接着看到proc_info_list结构的内容,它被定义在include\asm-arm\Procinfo.h中

129 struct proc_info_list { 230 unsigned int cpu_val; 331 unsigned int cpu_mask; 432 unsigned long __cpu_mm_mmu_flags; /* used by head.S */ 533 unsigned long __cpu_io_mmu_flags; /* used by head.S */ 634 unsigned long __cpu_flush; /* used by head.S */ 735 const char *arch_name; 836 const char *elf_name; 937 unsigned int elf_hwcap; 1038 const char *cpu_name; 1139 struct processor *proc; 1240 struct cpu_tlb_fns *tlb; 1341 struct cpu_user_fns *user; 1442 struct cpu_cache_fns *cache; 1543 };

接着找到对于当前内核支持的proc_info_list 定义,它在arch\arm\mm\proc-arm920.S 中。对于S3C2410、S3C2440芯片来说CPU ID都是0x41129200。cpu_val的值为0x41009200、cpu_mask的值为0xff00fff0,刚好匹配。

1.section ".proc.info.init", #alloc, #execinstr 2 3448 .type __arm920_proc_info,#object 4449 __arm920_proc_info: 5450 .long 0x41009200//cpu_val值 6451 .long 0xff00fff0//cpu_mask值 7452 .long PMD_TYPE_SECT | \ 8453 PMD_SECT_BUFFERABLE | \ 9454 PMD_SECT_CACHEABLE | \ 10455 PMD_BIT4 | \ 11456 PMD_SECT_AP_WRITE | \ 12457 PMD_SECT_AP_READ 13458 .long PMD_TYPE_SECT | \ 14459 PMD_BIT4 | \ 15460 PMD_SECT_AP_WRITE | \ 16461 PMD_SECT_AP_READ 17462 b __arm920_setup 18463 .long cpu_arch_name 19464 .long cpu_elf_name 20465 .long HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB 21466 .long cpu_arm920_name 22467 .long arm920_processor_functions 23468 .long v4wbi_tlb_fns 24469 .long v4wb_user_fns 25470 #ifndef CONFIG_CPU_DCACHE_WRITETHROUGH 26471 .long arm920_cache_fns 27472 #else 28473 .long v4wt_cache_fns 29474 #endif 30475 .size __arm920_proc_info, . - __arm920_proc_info

继续回到arch/arm/kernel/head.s往下分析,看到第83行,调用完__lookup_processor_type后r5的值变为执向找到的proc_info_list 结构的地址。所以第83行与第84行比较r5是否为0,如果为0说明没有找到符合当前CPU的ID号,则打印错误。接着到85行,调用__lookup_machine_type,它同样位于arch\arm\kernel\head-common.S中,它的功能是比较当前单板的id与内核支持的单板的id是否相符合。这段代码在.arch.info.init段中从__arch_info_begin开始到__arch_info_end结束,寻找符合当前单板的ID号的machine_desc结构

1176 .long __proc_info_begin 2177 .long __proc_info_end 3178 3: .long .//.表示当前这条代码链接后的虚拟地址 4179 .long __arch_info_begin 5180 .long __arch_info_end 6 7193 .type __lookup_machine_type, %function 8194 __lookup_machine_type: 9195 adr r3, 3b //r3=第178行的物理地址 10196 ldmia r3, {r4, r5, r6} //r4=r3。r5=__proc_info_end,r6=__proc_info_begin,取得的是虚拟地址 11197 sub r3, r3, r4 @ get offset between virt&phys//r3=r3-r4,取得物理地址与虚拟地址的偏差 12198 add r5, r5, r3 @ convert virt addresses to//r5=r5+r3,取得物理地址__proc_info_end 13199 add r6, r6, r3 @ physical address space //r6=r6+r3,取得物理地址__proc_info_begin 14200 1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type //r3=取得单板的编号 15201 teq r3, r1 @ matches loader number?//比较r3与r1是否相等,即linux是否支持uboot传入的单板 16202 beq 2f @ found //如果相等,则跳到207行,找到支持的单板,返回 17203 add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc//r5执向下一个machine_desc结构 18204 cmp r5, r6 //是否已经比较完machine_desc结构? 19205 blo 1b //如果没有比较完,则跳到200行继续比较 20206 mov r5, #0 @ unknown machine //如果所有machine_desc都比较完了,r5=0 21207 2: mov pc, lr //返回

其中__arch_info_begin、__arch_info_end被定义在arch\arm\kernel\vmlinux.lds中,它的意思是内核源码中有被定义为.arch.info.init的内容,它的起始地址是__arch_info_begin,结束地址为__arch_info_end。

1305 __arch_info_begin = .; 2306 *(.arch.info.init) 3307 __arch_info_end = .;

接着看到machine_desc结构的内容,它被定义在include\asm-arm\mach\Arch.h 中

117 struct machine_desc { 218 /* 319 * Note! The first four elements are used 420 * by assembler code in head-armv.S 521 */ 622 unsigned int nr; /* architecture number */ //单板的编号,是从内核传过来的编号 r1 723 unsigned int phys_io; /* start of physical io */ 824 unsigned int io_pg_offst; /* byte offset for io 925 * page tabe entry */ 1026 1127 const char *name; /* architecture name */ 1228 unsigned long boot_params; /* tagged list *///boo传过来的tag标记的位置,也是从内核传过来的 r2 1329 1430 unsigned int video_start; /* start of video RAM */ 1531 unsigned int video_end; /* end of video RAM */ 1632 1733 unsigned int reserve_lp0 :1; /* never has lp0 */ 1834 unsigned int reserve_lp1 :1; /* never has lp1 */ 1935 unsigned int reserve_lp2 :1; /* never has lp2 */ 2036 unsigned int soft_reboot :1; /* soft reboot */ 2137 void (*fixup)(struct machine_desc *, 2238 struct tag *, char **, 2339 struct meminfo *); 2440 void (*map_io)(void);/* IO mapping function *///IO映射函数,移植时需要关注 2541 void (*init_irq)(void); 2642 struct sys_timer *timer; /* system tick timer */ 2743 void (*init_machine)(void); 2844 };

接着需要找到对于当前内核支持的machine_desc定义,在include\asm-arm\mach\Arch.h 中有如下宏定义,它表示在.arch.info.init段存入一个machine_desc 的结构体,名称为

1__mach_desc_type,结构体内.nr、.name初始化为MACH_TYPE_type、_name 2 350 #define MACHINE_START(_type,_name) \ 451 static const struct machine_desc __mach_desc_##_type \ 552 __used \ 653 __attribute__((__section__(".arch.info.init"))) = { \ 754 .nr = MACH_TYPE_##_type, \ 855 .name = _name, 956 1057 #define MACHINE_END \ 1158 };

接着找调用MACHINE_START这个宏的文件,在arch\arm\mach-s3c2440\Mach-smdk2440.c 找到了,所以单板的ID为MACH_TYPE_S3C2440,它被定义在include\asm-arm\Mach-types.h中

#define MACH_TYPE_S3C2440              362。与UBOOT传入的参数相符合。

1339 MACHINE_START(S3C2440, "SMDK2440") 2340 /* Maintainer: Ben Dooks <ben@fluff.org> */ 3341 .phys_io = S3C2410_PA_UART, 4342 .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc, 5343 .boot_params = S3C2410_SDRAM_PA + 0x100, 6344 7345 .init_irq = s3c24xx_init_irq, 8346 .map_io = smdk2440_map_io, 9347 .init_machine = smdk2440_machine_init, 10348 .timer = &s3c24xx_timer, 11349 MACHINE_END

继续来看MACHINE_START(S3C2440, "SMDK2440")这个宏,在里面有许多和开发板相关的设置,比如说smdk2440_map_io,它被定义在arch\arm\mach-s3c2440\Mach-smdk2440.c中,在Linux移植之移植步骤中提到过想要移植成功,必须修改327行代码,将晶振的设置改为12000000。还有其它的一些配置就不一一列举了。

1324 static void __init smdk2440_map_io(void) 2325 { 3326 s3c24xx_init_io(smdk2440_iodesc, ARRAY_SIZE(smdk2440_iodesc)); 4327 s3c24xx_init_clocks(12000000);//根据开发板合适的晶振配置 5328 s3c24xx_init_uarts(smdk2440_uartcfgs, ARRAY_SIZE(smdk2440_uartcfgs)); 6329 }

回到arch/arm/kernel/head.s接着往下看,86、87行判断__lookup_machine_type是否成功找到支持单板的machine_desc结构,如果没找到则打印错误,88行是用来创建一级页表以建立虚拟地址到物理地址的映射关系,这里不详细分析。

继续往下看,看到100行,其中r10的值为__arm920_proc_info所在地址,PROCINFO_INITFUNC为proc_info_list结构体的偏移量,具体为__cpu_flush,对应到__arm920_proc_info结构体内,pc的值就是b __arm920_setup这条语句所在地址,即执行b __arm920_setup这条指令,__arm920_setup做一些MMU相关的初始化,在arch\arm\mm\proc-arm920.S中,这里不做细究。

197 ldr r13, __switch_data @ address to jump to after//r13是堆栈寄存器sp 298 @ mmu has been enabled 399 adr lr, __enable_mmu @ return (PIC) address //100行设置完成之后在使能MMU 4100 add pc, r10, #PROCINFO_INITFUNC//调用__arm920_setup函数,应该跟MMU相关,后面再研究

b __arm920_setup执行完毕返回之后执行的是arch/arm/kernel/head.s下的__enable_mmu 。

1152 .type __enable_mmu, %function 2153 __enable_mmu: 3..... 4174 b __turn_mmu_on 5 6 7187 .align 5 8188 .type __turn_mmu_on, %function 9189 __turn_mmu_on: 10190 mov r0, r0 11191 mrc p15, 0, r3, c0, c0, 0 @ read id reg 12192 mov r3, r3 13193 mov r3, r3 14194 mov pc, r13//设置完MMU之后跳转到__switch_data执行

__enable_mmu 执行完之后进入__switch_data执行,注意这时候的运行地址已经是初始化MMU之后的虚拟地址了。从15-24行可以看出pc=__mmap_switched,__mmap_switched的主要工作是将processor_id与__machine_arch_type初始化为当前MCU的编号与单板的编号

114 .type __switch_data, %object 215 __switch_data: 316 .long __mmap_switched 417 .long __data_loc @ r4 518 .long __data_start @ r5 619 .long __bss_start @ r6 720 .long _end @ r7 821 .long processor_id @ r4//之前找到的符合当前MCU的__arm920_proc_info结构体 922 .long __machine_arch_type @ r5//之前找到的符合单板的__mach_desc_S3C2440结构体 1023 .long cr_alignment @ r6 1124 .long init_thread_union + THREAD_START_SP @ sp 1225 1326 /* 1427 * The following fragment of code is executed with the MMU on in MMU mode, 1528 * and uses absolute addresses; this is not position independent. 1629 * 1730 * r0 = cp#15 control register 1831 * r1 = machine ID 1932 * r9 = processor ID 2033 */ 2134 .type __mmap_switched, %function 2235 __mmap_switched://虚拟地址已经可以使用 2336 adr r3, __switch_data + 4//r3=__data_loc所在的地址 2437 2538 ldmia r3!, {r4, r5, r6, r7}//r4=__data_loc所在地址;r5=__data_start所在地址依次类推 r3=__switch_data+4*4 2639 cmp r4, r5 @ Copy data segment if needed //检查是否有__data_loc段r4=r5说明没有__data_loc 2740 1: cmpne r5, r6 2841 ldrne fp, [r4], #4 2942 strne fp, [r5], #4 3043 bne 1b 3144 3245 mov fp, #0 @ Clear BSS (and zero fp)//清0BSS段 3346 1: cmp r6, r7 3447 strcc fp, [r6],#4 3548 bcc 1b 3649 3750 ldmia r3, {r4, r5, r6, sp}//r4=processor_id、r5=__machine_arch_type、r6=cr_alignment、sp=init_thread_union + THREAD_START_SP 3851 str r9, [r4] @ Save processor ID//processor_id=r9 = proc_info_list.cpu_val = 0x41009200 3952 str r1, [r5] @ Save machine type//__machine_arch_type=r1 = machine_desc .nr = MACH_TYPE_S3C2440 = 362 4053 bic r4, r0, #CR_A @ Clear 'A' bit 4154 stmia r6, {r0, r4} @ Save control register values 4255 b start_kernel//跳转到start_kernel C函数

最终执行b start_kernel,跳到C函数,这是第二阶段的内容。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )

Linux移植之内核启动过程引导阶段分析 - HelloWorld