ARM linux kernel启动流程 head.S(一)

kernel启动流程-head.S的执行_2.总体流程 1.前言 kernel版本:5.10 平台:arm64 本专题主要基于《arm64_linux head.S的执行流程》系列文章,前者是基于3.18,本专题针对的是内核5.10。主要分析head.S的执行过程。本文主要记录head.S的总体执行过程。 2. head.S执行前 bootloader在跳转到kernel前,需要确保如下设置: MMU = off, D-cache = off, I-cache = on or off x0 = physical address to the FDT blob k 阅读详情

1. kernel运行的史前时期和内存布局

在arm平台下,zImage.bin压缩镜像是由bootloader加载到物理内存,然后跳到zImage.bin里一段程序,它专门于将被压缩的kernel解压缩到KERNEL_RAM_PADDR开始的一段内存中,接着跳进真正的kernel去执行。该kernel的执行起点是stext函数,定义于arch/arm/kernel/head.S。

在分析stext函数前,先介绍此时内存的布局如下图所示 

 

在开发板tqs3c2440中,SDRAM连接到内存控制器的Bank6中,它的开始内存地址是0x30000000,大小为64M,即0x20000000。 ARM Linux kernel将SDRAM的开始地址定义为PHYS_OFFSET。经bootloader加载kernel并由自解压部分代码运行后,最终kernel被放置到KERNEL_RAM_PADDR(=PHYS_OFFSET + TEXT_OFFSET,即0x30008000)地址上的一段内存,经此放置后,kernel代码以后均不会被移动。

在进入kernel代码前,即bootloader和自解压缩阶段,ARM未开启MMU功能。因此kernel启动代码一个重要功能是设置好相应的页表,并开启MMU功能。为了支持MMU功能,kernel镜像中的所有符号,包括代码段和数据段的符号,在链接时都生成了它在开启MMU时,所在物理内存地址映射到的虚拟内存地址。

以arm kernel第一个符号(函数)stext为例,在编译链接,它生成的虚拟地址是0xc0008000,而放置它的物理地址为0x30008000(还记得这是PHYS_OFFSET+TEXT_OFFSET吗?)。实际上这个变换可以利用简单的公式进行表示:va = pa – PHYS_OFFSET + PAGE_OFFSET。Arm linux最终的kernel空间的页表,就是按照这个关系来建立。

之所以较早提及arm linux 的内存映射,原因是在进入kernel代码,里面所有符号地址值为清一色的0xCXXXXXXX地址,而此时ARM未开启MMU功能,故在执行stext函数第一条执行时,它的PC值就是stext所在的内存地址(即物理地址,0x30008000)。因此,下面有些代码,需要使用地址无关技术。

2. 一览stext函数

这里的启动流程指的是解压后kernel开始执行的一部分代码,这部分代码和ARM体系结构是紧密联系在一起的,所以最好是将ARM ARCHITECTURE REFERENCE MANUL仔细读读,尤其里面关于控制寄存器啊,MMU方面的内容~

stext函数定义在Arch/arm/kernel/head.S,它的功能是获取处理器类型和机器类型信息,并创建临时的页表,然后开启MMU功能,并跳进第一个C语言函数start_kernel。

stext函数的在前置条件是:MMU, D-cache, 关闭; r0 = 0, r1 = machine nr, r2 = atags prointer.

      前面说过解压以后,代码会跳到解压完成以后的vmlinux开始执行,具体从什么地方开始执行我们可以看看生成的vmlinux.lds(arch/arm/kernel/)这个文件:

[cpp]  view plain  copy
 print ?
  1. 1. OUTPUT_ARCH(arm)    
  2. 2. ENTRY(stext)    
  3. 3. jiffies = jiffies_64;    
  4. 4. SECTIONS    
  5. 5. {    
  6. 6.  . = 0x80000000 + 0x00008000;    
  7. 7.  .text.head : {     
  8. 8.   _stext = .;    
  9. 9.   _sinittext = .;    
  10. 0.   *(.text.h    

很明显我们的vmlinx最开头的section是.text.head,这里我们不能看ENTRY的内容,以为这时候我们没有操作系统,根本不知道如何来解析这里的入口地址,我们只能来分析他的section(不过一般来说这里的ENTRY和我们从seciton分析的结果是一样的),这里的.text.head section我们很容易就能在arch/arm/kernel/head.S里面找到,而且它里面的第一个符号就是我们的stext:

[cpp]  view plain  copy
 print ?
  1. # .section ".text.head", "ax"    
  2. #     
  3. # ENTRY(stext)   
  4. #     
  5. #  /* 设置CPU运行模式为SVC,并关中断 */    
  6. #     
  7. #   msr  cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode    
  8. #     
  9. #                                      @ and irqs disabled    
  10. #     
  11. #   mrc p15, 0, r9, c0, c0        @ get processor id    
  12. #     
  13. #   bl    __lookup_processor_type         @ r5=procinfo r9=cupid    
  14. #     
  15. # /* r10指向cpu对应的proc_info记录 */    
  16. #     
  17. #    movs  r10, r5                         @ invalid processor (r5=0)?    
  18. #     
  19. #   beq __error_p                    @ yes, error 'p'    
  20. #     
  21. #   bl    __lookup_machine_type            @ r5=machinfo    
  22. #     
  23. # /* r8 指向开发板对应的arch_info记录 */    
  24. #     
  25. #    movs  r8, r5                           @ invalid machine (r5=0)?    
  26. #     
  27. #   beq __error_a                    @ yes, error 'a'    
  28. #     
  29. # /* __vet_atags函数涉及bootloader造知kernel物理内存的情况,我们暂时不分析它。 */    
  30. #     
  31. #   bl    __vet_atags    
  32. #     
  33. # /*  创建临时页表 */    
  34. #     
  35. #   bl    __create_page_tables    
  36.     
  37. #   /*   
  38. #    
  39. #    * The following calls CPU specific code in a position independent   
  40. #    
  41. #    * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of   
  42. #    
  43. #    * xxx_proc_info structure selected by __lookup_machine_type   
  44. #    
  45. #    * above.  On return, the CPU will be ready for the MMU to be   
  46. #    
  47. #    * turned on, and r0 will hold the CPU control register value.   
  48. #    
  49. #    */    
  50. #     
  51. #  /* 这里的逻辑关系相当复杂,先是从proc_info结构中的中跳进__arm920_setup函数,   
  52. #    
  53. #   * 然后执__enable_mmu 函数。最后在__enable_mmu函数通过mov pc, r13来执行__switch_data,   
  54. #    
  55. #   * __switch_data函数在最后一条语句,鱼跃龙门,跳进第一个C语言函数start_kernel。   
  56. #    */    
  57. #     
  58. #   ldr   r13, __switch_data             @ address to jump to after    
  59. #     
  60. #                                      @ mmu has been enabled    
  61. #     
  62. #   adr  lr, __enable_mmu        @ return (PIC) address    
  63. #     
  64. #   add pc, r10, #PROCINFO_INITFUNC    
  65. #     
  66. # ENDPROC(stext)  

 这里的ENTRY这个宏实际我们可以在include/linux/linkage.h里面找到,可以看到他实际上就是声明一个GLOBAL Symbol,后面的ENDPROC和END唯一的区别是前面的声明了一个函数,可以在c里面被调用。

[cpp]  view plain  copy
 print ?
  1.  1. #ifndef ENTRY    
  2.  2. #define ENTRY(name) /    
  3.  3.   .globl name; /    
  4.  4.   ALIGN; /    
  5.  5.   name:    
  6.  6. #endif    
  7.  7. #ifndef WEAK    
  8.  8. #define WEAK(name)     /    
  9.  9.     .weak name;    /    
  10. 10.     name:    
  11. 11. #endif    
  12. 12. #ifndef END    
  13. 13. #define END(name) /    
  14. 14.   .size name, .-name    
  15. 15. #endif    
  16. 16. /* If symbol 'name' is treated as a subroutine (gets called, and returns)  
  17. 17.  * then please use ENDPROC to mark 'name' as STT_FUNC for the benefit of  
  18. 18.  * static analysis tools such as stack depth analyzer.  
  19. 19.  */    
  20. 20. #ifndef ENDPROC    
  21. 21. #define ENDPROC(name) /    
  22. 22.   .type name, @function; /    
  23. 23.   END(name)    
  24. 24. #endif    

找到了vmlinux的起始代码我们就来进行分析了,先总体概括一下这部分代码所完成的功能,head.S会首先检查proc和arch以及atag的有效性,然后会建立初始化页表,并进行CPU必要的处理以后打开MMU,并跳转到start_kernel这个symbol开始执行后面的C代码。这里有很多变量都是我们进行kernel移植时需要特别注意的,下面会一一讲到。

      在这里我们首先看看这段汇编开始跑的时候的寄存器信息,这里的寄存器内容实际上是同bootloader跳转到解压代码是一样的,就是r1=arch  r2=atag addr。下面我们就具体来看看这个head.S跑的过程:

[cpp]  view plain  copy
 print ?
  1. 1. msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode    
  2. 2.                     @ and irqs disabled    
  3. 3. mrc p15, 0, r9, c0, c0      @ get processor id    

 首先进入SVC模式并关闭所有中断,并从arm协处理器里面读到CPU ID,这里的CPU主要是指arm架构相关的CPU型号,比如ARM9,ARM11等等。

3 __lookup_processor_type 函数

然后跳转到__lookup_processor_type,这个函数定义在head-common.S里面,这里的bl指令会保存当前的pc在lr里面

 __lookup_processor_type 函数是一个非常讲究技巧的函数,如果你将它领会,也将领会kernel了一些魔法。

Kernel 代码将所有CPU信息的定义都放到.proc.info.init段中,因此可以认为.proc.info.init段就是一个数组,每个元素都定义了一个或一种CPU的信息。目前__lookup_processor_type使用该元素的前两个字段cpuid和mask来匹配当前CPUID,如果满足 CPUID & mask == cpuid,则找到当前cpu的定义并返回。 

下面是tqs3c2440开发板,CPU的定义信息,cpuid = 0x41009200,mask = 0xff00fff0。如果是码是运行在tqs3c2440开发板上,那么函数返回下面的定义:

最后__lookup_processor_type会从这个函数返回,我们具体看看这个函数:    

[cpp]  view plain  copy
 print ?
  1. # __lookup_processor_type:    
  2. #       /* adr 是相对寻址,它的寻计算结果是将当前PC值加上3f符号与PC的偏移量,   
  3. #        * 而PC是物理地址,因此r3的结果也是3f符号的物理地址 */    
  4. #     
  5. #        adr  r3, 3f    
  6. #     
  7. #       /* r5值为__proc_info_bein, r6值为__proc_ino_end,而r7值为.,   
  8. #        * 也即3f符号的链接地址。请注意,在链接期间,__proc_info_begin和   
  9. #        * __proc_info_end以及.均是链接地址,也即虚执地址。   
  10. #        */    
  11. #     
  12. #        ldmda     r3, {r5 - r7}    
  13. #     
  14. #      /* r3为3f的物理地址,而r7为3f的虚拟地址。结果是r3为虚拟地址与物理地址的差值,即PHYS_OFFSET - PAGE_OFFSET。*/    
  15. #     
  16. #        sub  r3, r3, r7                     @ get offset between virt&phys    
  17. #     
  18. #      /* r5为__proc_info_begin的物理地址, 即r5指针__proc_info数组的首地址 */    
  19. #     
  20. #        add r5, r5, r3                     @ convert virt addresses to    
  21. #     
  22. #      /* r6为__proc_info_end的物理地址 */    
  23. #     
  24. #        add r6, r6, r3                     @ physical address space    
  25. #     
  26. #      /* 读取r5指向的__proc_info数组元素的CPUID和mask值 */    
  27. #     
  28. # 1:    ldmia      r5, {r3, r4}                  @ value, mask    
  29. #     
  30. #      /* 将当前CPUID和mask相与,并与数组元素中的CPUID比较是否相同   
  31. #       * 若相同,则找到当前CPU的__proc_info定义,r5指向访元素并返回。   
  32. #       */    
  33. #     
  34. #        and  r4, r4, r9                     @ mask wanted bits    
  35. #     
  36. #        teq  r3, r4    
  37. #     
  38. #        beq 2f    
  39. #     
  40. #      /* r5指向下一个__proc_info元素 */    
  41. #     
  42. #        add r5, r5, #PROC_INFO_SZ        @ sizeof(proc_info_list)    
  43. #     
  44. #      /* 是否遍历完所有__proc_info元素 */    
  45. #     
  46. #        cmp r5, r6    
  47. #     
  48. #        blo  1b    
  49. #     
  50. #      /* 找不到则返回NULL */    
  51. #     
  52. #        mov r5, #0                          @ unknown processor    
  53. #     
  54. # 2:    mov pc, lr    
  55. #     
  56. # ENDPROC(__lookup_processor_type)    
  57. #     
  58. #        .long       __proc_info_begin    
  59. #        .long       __proc_info_end    
  60. # 3:    .long       .    
  61. #        .long       __arch_info_begin    
  62. #        .long       __arch_info_end  


他这里的执行过程其实比较简单就是在__proc_info_begin和__proc_info_end这个段里面里面去读取我们注册在里面的proc_info_list这个结构体,这个结构体的定义在arch/arm/include/asm/procinfo.h,具体实现根据你使用的cpu的架构在arch/arm/mm/里面找到具体的实现,这里我们使用的ARM11是proc-v6.S,我们可以看看这个结构体:

[cpp]  view plain  copy
 print ?
  1.  1. .section ".proc.info.init", #alloc, #execinstr    
  2.  2. /*   
  3.  3.  * Match any ARMv6 processor core.  
  4.  4.  */    
  5.  5. .type   __v6_proc_info, #object    
  6.  6. _proc_info:    
  7.  7. .long   0x0007b000    
  8.  8. .long   0x0007f000    
  9.  9. .long   PMD_TYPE_SECT | /    
  10. 10.     PMD_SECT_BUFFERABLE | /            
  11. 11.     PMD_SECT_CACHEABLE | /    
  12. 12.     PMD_SECT_AP_WRITE | /    
  13. 13.     PMD_SECT_AP_READ    
  14. 14. .long   PMD_TYPE_SECT | /    
  15. 15.     PMD_SECT_XN | /    
  16. 16.     PMD_SECT_AP_WRITE | /    
  17. 17.     PMD_SECT_AP_READ    
  18. 18. b   __v6_setup    
  19. 19. .long   cpu_arch_name    
  20. 20. .long   cpu_elf_name    
  21. 21. .long   HWCAP_SWP|HWCAP_HALF|HWCAP_THUMB|HWCAP_FAST_MULT|HWCAP_EDSP|HWCAP_JAVA    
  22. 22. .long   cpu_v6_name    
  23. 23. .long   v6_processor_functions     
  24. 24. .long   v6wbi_tlb_fns    
  25. 25. .long   v6_user_fns    
  26. 26. .long   v6_cache_fns    
  27. 27. .size   __v6_proc_info, . - __v6_proc_info    

对着.h我们就知道各个成员变量的含义了,他这里lookup的过程实际上是先求出这个proc_info_list的实际物理地址,并将其内容读出,然后将其中的mask也就是我们这里的0x007f000与寄存器与之后与0x007b00进行比较,如果一样的话呢就校验成功了,如果不一样呢就会读下一个proc_info的信息,因为proc一般都是只有一个的,所以这里一般不会循环,如果检测正确寄存器就会将正确的proc_info_list的物理地址赋给寄存器,如果检测不到就会将寄存器值赋0,然后通过LR返回

[cpp]  view plain  copy
 print ?
  1. 1. bl  __lookup_machine_type       @ r5=machinfo    
  2. 2. movs    r8, r5              @ invalid machine (r5=0)?          
  3. 3. beq __error_a           @ yes, error 'a'    

检测完proc_info_list以后就开始检测machine_type了,这个函数的实现也在head-common.S里面,__lookup_machine_type 和__lookup_processor_type像对孪生兄弟,它们的行为都是很类似的:__lookup_machine_type根据r1寄存器的机器编号到.arch.info.init段的数组中依次查找机器编号与r1相同的记录。它使了与它孪生兄弟同样的手法进行虚拟地址到物理地址的转换计算。

我们看看它具体的实现:

[cpp]  view plain  copy
 print ?
  1.  1. __lookup_machine_type:    
  2.  2.     adr r3, 3b    
  3.  3.     ldmia   r3, {r4, r5, r6}    
  4.  4.     sub r3, r3, r4          @ get offset between virt&phys     
  5.  5.     add r5, r5, r3          @ convert virt addresses to        
  6.  6.     add r6, r6, r3          @ physical address space           
  7.  7. 1:  ldr r3, [r5, #MACHINFO_TYPE]    @ get machine type    
  8.  8.     teq r3, r1              @ matches loader number?           
  9.  9.     beq 2f              @ found        
  10. 10.     add r5, r5, #SIZEOF_MACHINE_DESC    @ next machine_desc    
  11. 11.     cmp r5, r6    
  12. 12.     blo 1b    
  13. 13.     mov r5, #0              @ unknown machine    
  14. 14. 2:  mov pc, lr    
  15. 15. ENDPROC(__lookup_machine_type)    

这里的过程基本上是同proc的检查是一样的,这里主要检查芯片的类型,比如我们现在的芯片是MSM7X27FFA,这也是一个结构体,它的头文件在arch/arm/include/asm/arch/arch.h里面(machine_desc),它具体的实现根据你对芯片类型的选择而不同,这里我们使用的是高通的7x27,具体实现在arch/arm/mach-msm/board-msm7x27.c里面,这些结构体最后都会注册到_arch_info_begin和_arch_info_end段里面,具体的大家可以看看vmlinux.lds或者system.map,这里的lookup会根据bootloader传过来的nr来在__arch_info里面的相匹配的类型,没有的话就寻找下一个machin_desk结构体,直到找到相应的结构体,并会将结构体的地址赋值给寄存器,如果没有的话就会赋值为0的。一般来说这里的machine_type会有好几个,因为不同的芯片类型可能使用的都是同一个cpu架构。

       对processor和machine的检查完以后就会检查atags parameter的有效性,关于这个atag具体的定义我们可以在./include/asm/setup.h里面看到,它实际是一个结构体和一个联合体构成的结合体,里面的size都是以字来计算的。这里的atags param是bootloader创建的,里面包含了ramdisk以及其他memory分配的一些信息,存储在boot.img头部结构体定义的地址中,具体的大家可以看咱以后对bootloader的分析~


[cpp]  view plain  copy
 print ?
  1.  1. __vet_atags:    
  2.  2.     tst r2, #0x3            @ aligned?     
  3.  3.     bne 1f    
  4.  4.     ldr r5, [r2, #0]            @ is first tag ATAG_CORE?          
  5.  5.     cmp r5, #ATAG_CORE_SIZE    
  6.  6.     cmpne   r5, #ATAG_CORE_SIZE_EMPTY    
  7.  7.     bne 1f    
  8.  8.     ldr r5, [r2, #4]    
  9.  9.     ldr r6, =ATAG_CORE    
  10. 10.     cmp r5, r6    
  11. 11.     bne 1f    
  12. 12.     mov pc, lr              @ atag pointer is ok               
  13. 13. 1:  mov r2, #0    
  14. 14.     mov pc, lr    
  15. 15. ENDPROC(__vet_atags)    
这里对atag的检查主要检查其是不是以ATAG_CORE开头,size对不对,基本没什么好分析的,代码也比较好看~ 下面我们来看后面一个重头戏,就是创建初始化页表,说实话这段内容我没弄清楚,它需要对ARM VIRT MMU具有相当的理解,这里我没有太多的时间去分析spec,只是粗略了翻了ARM V7的manu,知道这里建立的页表是arm的secition页表,完成内存开始1m内存的映射,这个页表建立在kernel和atag paramert之间,一般是4000-8000之间~具体的代码和过程我这里就不贴了,大家可以看看参考的链接,看看其他大虾的分析,我还没怎么看明白,等以后仔细研究ARM MMU的时候再回头来仔细研究了,不过代码虽然不分析,这里有几个重要的地址需要特别分析下~

      这几个地址都定义在arch/arm/include/asm/memory.h,我们来稍微分析下这个头文件,首先它包含了arch/memory.h,我们来看看arch/arm/mach-msm/include/mach/memory.h,在这个里面定义了#define PHYS_OFFSET     UL(0x00200000) 这个实际上是memory的物理内存初始地址,这个地址和我们以前在boardconfig.h里面定义的是一致的。然后我们再看asm/memory.h,他里面定义了我们的memory虚拟地址的首地址#define PAGE_OFFSET     UL(CONFIG_PAGE_OFFSET)。      

      另外我们在head.S里面看到kernel的物理或者虚拟地址的定义都有一个偏移,这个偏移又是从哪来的呢,实际我们可以从arch/arm/Makefile里面找到:textofs-y   := 0x00008000     TEXT_OFFSET := $(textofs-y) 这样我们再看kernel启动时候的物理地址和链接地址,实际上它和我们前面在boardconfig.h和Makefile.boot里面定义的都是一致的~

      建立初始化页表以后,会首先将__switch_data这个symbol的链接地址放在sp里面,然后获得__enable_mmu的物理地址,然后会跳到__proc_info_list里面的INITFUNC执行,这个偏移是定义在arch/arm/kernel/asm-offset.c里面,实际上就是取得__proc_info_list里面的__cpu_flush这个函数执行。
[cpp]  view plain  copy
 print ?
  1. 1. ldr r13, __switch_data      @ address to jump to after         
  2. 2.                     @ mmu has been enabled             
  3. 3. adr lr, __enable_mmu        @ return (PIC) address    
  4. 4. add pc, r10, #PROCINFO_INITFUNC    

这个__cpu_flush在这里就是我们proc-v6.S里面的__v6_setup函数了,具体它的实现我就不分析了,都是对arm控制寄存器的操作,这里转一下它对这部分操作的注释,看完之后就基本知道它完成的功能了。

 /*

 *  __v6_setup

 *

 *  Initialise TLB, Caches, and MMU state ready to switch the MMU

 *  on.  Return in r0 the new CP15 C1 control register setting.

 *

 *  We automatically detect if we have a Harvard cache, and use the

 *  Harvard cache control instructions insead of the unified cache

 *  control instructions.

 *

 *  This should be able to cover all ARMv6 cores.

 *

 *  It is assumed that:       

 *  - cache type register is implemented

 */    

        完成这部分关于CPU的操作以后,下面就是打开MMU了,这部分内容也没什么好说的,也是对arm控制寄存器的操作,打开MMU以后我们就可以使用虚拟地址了,而不需要我们自己来进行地址的重定位,ARM硬件会完成这部分的工作。打开MMU以后,会将SP的值赋给PC,这样代码就会跳到__switch_data来运行,这个__switch_data是一个定义在head-common.S里面的结构体,我们实际上是跳到它地一个函数指针__mmap_switched处执行的。

        这个switch的执行过程我们只是简单看一下,前面的copy data_loc段以及清空.bss段就不用说了,它后面会将proc的信息和machine的信息保存在__switch_data这个结构体里面,而这个结构体将来会在start_kernel的setup_arch里面被使用到。这个在后面的对start_kernel的详细分析中会讲到。另外这个switch还涉及到控制寄存器的一些操作,这里我不没仔细研究spec,不懂也就不说了~


4. 为kernel建立临时页表create_table_pages

前面提及到,kernel里面的所有符号在链接时,都使用了虚拟地址值。在完成基本的初始化后,kernel代码将跳到第一个c语言函数 start_kernl来执行,在哪个时候,这些虚拟地址必须能够对它所存放在真正内存位置,否则运行将为出错。为此,CPU必须开启MMU,但在开启 MMU前,必须为虚拟地址到物理地址的映射建立相应的面表。在开启MMU后,kernel指并不马上将PC值指向start_kernl,而是要做一些C 语言运行期的设置,如堆栈,重定义等工作后才跳到start_kernel去执行。在此过程中,PC值还是物理地址,因此还需要为这段内存空间建立va = pa的内存映射关系。当然,本函数建立的所有页表都会在将来paging_init销毁再重建,这是临时过度性的映射关系和页表。

 

在介绍__create_table_pages前,先认识一个macro pgtbl,它将KERNL_RAM_PADDR – 0x4000的值赋给rd寄存器,从下面的使用中可以看它,该值是页表在物理内存的基础,也即页表放在kernel开始地址下的16K的地方。

[cpp]  view plain  copy
 print ?
  1. # __create_page_tables:    
  2. #   /* r4 = KERNEL_RAM_PADDR – 0x4000 = 0x30004000   
  3. #    * 后面的C代码中的swapper_pg_dir变量,它的值也指向0x30004000   
  4. #    * 内存地址,不过它的值是虚拟内存地址,即0xc0004000   
  5. #    */    
  6. #        pgtbl       r4                         @ page table address    
  7. #     
  8. #    /* 将从r4到KERNEL_RAP_PADDR的16K页表空间清空。 */    
  9. #     
  10. #        mov r0, r4    
  11. #        mov r3, #0    
  12. #        add r6, r0, #0x4000    
  13. #     
  14. # 1:     str   r3, [r0], #4    
  15. #        str   r3, [r0], #4    
  16. #        str   r3, [r0], #4    
  17. #        str   r3, [r0], #4    
  18. #        teq  r0, r6    
  19. #        bne  1b    
  20. #     
  21. #      
  22. #      /* 还记得r10指向开发板相应的proc_info元素吗?这里它将的mm_mmuflags值读到r7中。   
  23. #       * PROCINFO_MM_MMUFLAGS值为8,可对应上面列出来的__arm920_proc_info结构或你相应开发板结构的值来查看该mmu_flags值。   
  24. #       * 这里的flags就是用于设置目录项的flags。查看该mmu_flags的定义,发现它是要求一级页表是section。   
  25. #       */    
  26. #     
  27. #        ldr   r7, [r10, #PROCINFO_MM_MMUFLAGS] @ mm_mmuflags    
  28. #     
  29. #        /*   
  30. #         * Create identity mapping for first MB of kernel to   
  31. #         * cater for the MMU enable.  This identity mapping   
  32. #         * will be removed by paging_init().  We use our current program   
  33. #         * counter to determine corresponding section base address.   
  34. #         */    
  35. #     
  36. #        /* r3 = ((pc >> 20) << 20) | r7, 即取PC以1M向下对齐的地址。R6 = pc >> 20也即r6 = 0x300(pgd_idx),   
  37. #         * 即PC对所有1M内存空间,在页表中的下标。                                       
  38. #         * R7值表明该目录项是section,即它映射的大小是1M。故刚好一个目录项就可以映射kernel上的1M空间。   
  39. #         * 这个暂时的va = pa映射只建立1M大小内存的,而不需要建立整个kernel镜像范围的映射。   
  40. #         * 因为这个va = pa的映射只有当前汇编语言才使用,一量跳进start_kernl后,这将不会用到了。而汇编代码在链接时,   
  41. #         * 已将它安排到代码段的最前面了。   
  42. #     
  43. #         mov r6, pc, lsr #20                     @ start of kernel section   
  44. #         orr   r3, r7, r6, lsl #20         @ flags + kernel base   
  45. #    
  46. #        /* 将目录内空写到页表相应位置,即((uint32_t *)r4)[pgd_idx] = r3 */    
  47. #      
  48. #        str   r3, [r4, r6, lsl #2]         @ identity mapping    
  49. #     
  50. #      
  51. #        /* 上面代码段为[pc &(~0xfffff), (pc + 0xfffff) &(~0xfffff)]的物理内存空间建立了va = pa的映射关系。*/    
  52. #     
  53. #        /* 下面为kernel镜像所占有空间,即KERNL_START到KERNEL_END建立内存映射,   
  54. #         * 映射关系为:va = pa – PHYS + PAGR_OFFSET。注意,这里的KENEL_START是kernel空间开始的虚拟地址。   
  55. #         * 这里的目录表项同样是section,即一个项映射1M的内存。   
  56. #         */    
  57. #     
  58. #     
  59. #        /* KERNEL_START = PAGE_OFFSET + TEXT_OFFSET,   
  60. #         * r0 = ((uint32_t *)(r4))[ (KERNEL_START & 0xff000000) >> 20],   
  61. #         * 即r0指向KERNEL_START& 0xff000000(即kernel以16M向下对齐的)虚拟地址,所在项表目录中的位置。   
  62. #    
  63. #        add r0, r4,  #(KERNEL_START & 0xff000000) >> 18   
  64. #    
  65. #       /* r0 = ((uint32_t *)r0)[(KERNEL_START & 0x00f00000) >> 20]   
  66. #        * 执行前r0指向kernel以16M向下对齐的虚执地址,而这里再加上KERNEL_START未以16M向对齐部分的偏移量。   
  67. #        * 将原来r3的值写到页表目录中。R3的值就是之前已建立好va=pa映射的那个PA值。   
  68. #        */    
  69. #     
  70. #        str   r3, [r0, #(KERNEL_START & 0x00f00000) >> 18]!    
  71. #     
  72. #       /* r6为kernel镜像的尾部虚拟地址。*/    
  73. #     
  74. #        ldr   r6, =(KERNEL_END - 1)    
  75. #     
  76. #       /* 指向下一个即将要填写的目录项 */    
  77. #     
  78. #        add r0, r0, #4    
  79. #     
  80. #       /* r6指向KERNEL_END- 1虚拟地址所在的目录表项的位置 */    
  81. #     
  82. #       add r6, r4, r6, lsr #18    
  83. # 1:    cmp       r0, r6    
  84. #     
  85. #       /* 每填一个目录项,后一个比前一个所指向的物理地址大1M。*/    
  86. #       add r3, r3, #1 << 20    
  87. #       strls r3, [r0], #4    
  88. #       bls   1b    
  89. #     
  90. #  #ifdef CONFIG_XIP_KERNEL    
  91. #        /* 忽略,不分析这种情况 */    
  92. # #endif    
  93. #     
  94. #     /* 通常kernel的启动参数由bootloader放到了物理内存的第1个M上,所以需要为RAM上的第1个M建立映射。   
  95. #      * 上面已为PHYS_OFFSET + TEXT_OFFSET建立了映射,如果TEXT_OFFSET小于0x00100000的话,   
  96. #      * 上面代码应该也为SDRAM的第一个M建立了映射,但如果大于0x0010000则不会。   
  97. #      * 所以这里无论如何均为SDRAM的第一个M建立映射(不知分析对否,还请指正)。   
  98. #      */    
  99. #        add r0, r4, #PAGE_OFFSET >> 18    
  100. #        orr   r6, r7, #(PHYS_OFFSET & 0xff000000)    
  101. #        .if    (PHYS_OFFSET & 0x00f00000)    
  102. #        orr   r6, r6, #(PHYS_OFFSET & 0x00f00000)    
  103. #        .endif    
  104. #        str   r6, [r0]    
  105. #     
  106. # #ifdef CONFIG_DEBUG_LL    
  107. #        /*略去 */    
  108. # #if defined(CONFIG_ARCH_NETWINDER) || defined(CONFIG_ARCH_CATS)    
  109. #        /* 略去 */    
  110. # #endif    
  111. #     
  112. # #ifdef CONFIG_ARCH_RPC    
  113. #  /* 略去 */    
  114. # #endif    
  115. #     
  116. # #endif    
  117. #     
  118. #        mov pc, lr    
  119. # ENDPROC(__create_page_tables)   

5. 开启MMU

看完页表的建立,想必开启MMU的代码也是小菜一碟吧。此函数的主要功能是将页表的基址加到cp15中的面表指针寄存器,同时设置域访问(domain access)寄存器。
[cpp]  view plain  copy
 print ?
  1.  1. /*  
  2.  2.  * Setup common bits before finally enabling the MMU.  Essentially  
  3.  3.  * this is just loading the page table pointer and domain access  
  4.  4.  * registers.  
  5.  5.  */    
  6.  6. __enable_mmu:    
  7.  7.  /* 这里设置是否为非对齐内存访问产生异常 */    
  8.  8. #ifdef CONFIG_ALIGNMENT_TRAP    
  9.  9.        orr   r0, r0, #CR_A    
  10. 10. #else    
  11. 11.        bic   r0, r0, #CR_A    
  12. 12. #endif    
  13. 13.  /* 是否禁用数据缓存功能*/    
  14. 14. #ifdef CONFIG_CPU_DCACHE_DISABLE    
  15. 15.        bic   r0, r0, #CR_C    
  16. 16. #endif    
  17. 17.  /* 是否禁用CPU_BPREDICT ?,不是很清楚此选项 */    
  18. 18. #ifdef CONFIG_CPU_BPREDICT_DISABLE    
  19. 19.        bic   r0, r0, #CR_Z    
  20. 20. #endif    
  21. 21.  /* 是否禁用指令缓存功能 */    
  22. 22. #ifdef CONFIG_CPU_ICACHE_DISABLE    
  23. 23.        bic   r0, r0, #CR_I    
  24. 24. #endif    
  25. 25.     
  26. 26.       /* 设置域访问寄存器的值。这里设置每个domain的属性是否上面建立的页表中,  
  27. 27.        * 每个目录项的damon值一起进行访问控制检查。具体情况请参考ARM处理器手册。  
  28. 28.        */    
  29. 29.     
  30. 30.        mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | \    
  31. 31.                     domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | \    
  32. 32.                     domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | \    
  33. 33.                     domain_val(DOMAIN_IO, DOMAIN_CLIENT))    
  34. 34.        mcr p15, 0, r5, c3, c0, 0           @ load domain access register    
  35. 35.        mcr p15, 0, r4, c2, c0, 0           @ load page table pointer    
  36. 36.        b     __turn_mmu_on    
  37. 37. ENDPROC(__enable_mmu)    
  38. 38.     
  39. 39. /*  
  40. 40.  * Enable the MMU.  This completely changes the structure of the visible  
  41. 41.  * memory space.  You will not be able to trace execution through this.  
  42. 42.  * If you have an enquiry about this, *please* check the linux-arm-kernel  
  43. 43.  * mailing list archives BEFORE sending another post to the list.  
  44. 44.  *  
  45. 45.  *  r0  = cp#15 control register  
  46. 46.  *  r13 = *virtual* address to jump to upon completion  
  47. 47.  *  
  48. 48.  * other registers depend on the function called upon completion  
  49. 49.  */    
  50. 50.     
  51. 51.        .align      5    
  52. 52. __turn_mmu_on:    
  53. 53.        mov r0, r0    
  54. 54.     
  55. 55.       /* 将r0的值写到控制寄存器中。这里,终于开启MMU功能了。  
  56. 56.        * 查阅手册说控制寄存器的0位置1表示开启MMU,但这里r0的第0是多少呢(还请大家指正)  
  57. 57.        */    
  58. 58.     
  59. 59.        mcr p15, 0, r0, c1, c0, 0           @ write control reg    
  60. 60.        mrc p15, 0, r3, c0, c0, 0           @ read id reg    
  61. 61.     
  62. 62.      /* 这里的两个mov似乎是否流水线有关的,开启MMU语句后面几条是不能进行内存寻址的。但仍未搞明白具体东西的。*/    
  63. 63.        mov r3, r3    
  64. 64.        mov r3, r3    
  65. 65.     
  66. 66.      /* 转跳到r13的函数中去,r13为__mmap_switched函数的虚拟地址,  
  67. 67.       * 从stext函数的未尾可以找到它的赋值。故从此开始pc的值就真正在内存的虚拟地址空间了。  
  68. 68.       */    
  69. 69.     
  70. 70.        mov pc, r13    
  71. 71. ENDPROC(__turn_mmu_on)    

6.__mmap_switched函数

__mmap_switched函数专用来设置C语言的执行环境,比如重定位工作,堆栈,以及BSS段的清零。 

__switch_data变量先定义了一系里面处量的数据,如重定位和数据段的地址,BSS段的地址,pocessor_id和__mach_arch_type变量的地址等。

[cpp]  view plain  copy
 print ?
  1. #       .type       __switch_data, %object    
  2. # __switch_data:    
  3. #        .long       __mmap_switched    
  4. #        .long       __data_loc                  @ r4    
  5. #        .long       _data                           @ r5    
  6. #        .long       __bss_start                  @ r6    
  7. #        .long       _end                            @ r7    
  8. #        .long       processor_id               @ r4    
  9. #        .long       __machine_arch_type         @ r5    
  10. #        .long       __atags_pointer                  @ r6    
  11. #        .long       cr_alignment                @ r7    
  12. #        .long       init_thread_union + THREAD_START_SP @ sp    
  13. #      
  14. # /*   
  15. #  * The following fragment of code is executed with the MMU on in MMU mode,   
  16. #  * and uses absolute addresses; this is not position independent.   
  17. #  *   
  18. #  *  r0  = cp#15 control register   
  19. #  *  r1  = machine ID   
  20. #  *  r2  = atags pointer   
  21. #  *  r9  = processor ID   
  22. #  */    
  23. # __mmap_switched:    
  24. #        adr  r3, __switch_data + 4    
  25. #        /* r4 = __data_loc, r5 = _data, r6 = _bss_start, r7 = _end */    
  26. #        ldmia      r3!, {r4, r5, r6, r7}    
  27. #     
  28. #       /* 下面这段代码类似于这段C代码, 即将整个数据段从__data_loc拷贝到_data段。   
  29. #        * if (__data_loc  == _data || _data != _bass_start)   
  30. #        *    memcpy(_data, __data_loc, _bss_start - _data);   
  31. #        */    
  32. #     
  33. #        cmp      r4, r5                           @ Copy data segment if needed    
  34. # 1:     cmpne    r5, r6    
  35. #        ldrne       fp, [r4], #4    
  36. #        strne       fp, [r5], #4    
  37. #        bne  1b    
  38. #     
  39. #        /* 将BSS段,也即从_bss_start到_end的内存清零。 */    
  40. #     
  41. #        mov fp, #0                          @ Clear BSS (and zero fp)    
  42. # 1:     cmp     r6, r7    
  43. #        strcc       fp, [r6],#4    
  44. #        bcc  1b    
  45. #     
  46. #    /* r4 = processor_id,   
  47. #     * r5 = __machine_arch_type   
  48. #     * r6 = __atags_pointer   
  49. #     * r7 = cr_alignment   
  50. #     * sp = init_thread_union + THREAD_START_SP   
  51. #     * 为什么将栈顶指针设置为init_thread_union + THREAD_START_SP   
  52. #     * init_head_union 变量是一个大小为THREAD_SIZE的union,它在编译时,放到数据段的前面。   
  53. #     * 初步估计这块空间是内核堆栈。故在跳入C语言代码时,它SP的值设置为init_thread_union + THREAD_START_SP。   
  54. #     * 注意THREAD_START_SP定义为THREAD_SIZE – 8,中间为什么留出8个字节呢?是与ARM的堆栈操作有关吗? 还有用专向start_kernel函数传递参数?   
  55. #     */    
  56. #      
  57. #        ldmia      r3, {r4, r5, r6, r7, sp}    
  58. #        str   r9, [r4]                 @ Save processor ID    
  59. #        str   r1, [r5]                 @ Save machine type    
  60. #        str   r2, [r6]                 @ Save atags pointer    
  61. #        bic   r4, r0, #CR_A                    @ Clear 'A' bit    
  62. #     
  63. #     /* cr_alignment变量的后面接着放置cr_no_alignment,    
  64. #      * r0为打开alignment检测时,控制寄存器的值,而r4为关闭时的值,   
  65. #      * 这里分将将打开和关闭alignment检查的控制寄存器的值写到   
  66. #      * cr_alignment和cr_no_alignement变量中。   
  67. #      */    
  68. #     
  69. #        stmia      r7, {r0, r4}                  @ Save control register values    
  70. #     
  71. #      /* 跳到start_kernel函数,此函数代码用纯C来实现,它会调用各个平台的相关初始化函数,   
  72. #       * 来实现不同平台的初始化工作。至此,arm linux的启动工作完成。   
  73. #       */    
  74. #     
  75. #        b     start_kernel    
  76. # ENDPROC(__mmap_switched)    

        好啦,switch操作完成以后就会b start_kernel了~ 这样就进入了c代码的运行了,下一篇文章仔细研究这个start_kernel的函数~~

原文链接:http://blog.youkuaiyun.com/Tommy_wxie/article/details/7238748

kernel启动流程-head.S的执行_9.__primary_switched 1. 前言 kernel版本:5.10 平台:arm64 本专题主要基于《arm64_linux head.S的执行流程》系列文章,前者是基于3.18,本专题针对的是内核5.10。主要分析head.S的执行过程。前面分析了___primary_switch的执行过程,在___primary_switch的最后将会跳转到__primary_switched执行,它最终将跳转到start_kernel。 2. __primary_switched 设置init进程信息 adrp x4, i 阅读详情

相关推荐

ARM64启动过程分析

文章目录arm64启动过程分析arm64启动过程分析(一)boot protocolarm64启动过程分析(二)内核启动第一步arm64启动过程分析(三)创建启动阶段页表arm64启动过程分析(四)为开启mmu进行的cpu初始化arm64启动过程分析(五)开启mmuarm64启动过程分析(六)进入start_kernelarm64启动过程分析(七)其他一些功能实现 arm64启动过程分析 参考:DDI0487F_b_armv8_arm.pdf ​ 蜗窝科技 ARMv8A Arch arm64启动过程分析

看我眼色行事的博客 8390

arm64_linux head.S的执行流程(3.18)- 8.stext之__create_page_tables

1.前言 本文基于高通8996平台,kernel版本为3.18.31。 本文主要介绍head.S的__create_page_tables执行流程 2. 页表基础知识 PGD(Page Global Directory)对应Level 0 translation table PUD (Page Upper Directory) 对应Level 1 translation table PMD (Page Middle Directory) 对应Level 2 translation table PTE (

HZero 862

[kernel 启动流程] (第七章)第一阶段之——跳转到start_kernel

零、说明本文是《[kernel 启动流程] (第一章)概述》的延伸, 阅读本文前建议先阅读《[kernel 启动流程] (第一章)概述》1、kernel启动流程第一阶段简单说明arch/arm/kernel/head.S kernel入口地址对应stext ENTRY(stext) 第一阶段要做的事情,也就是stext的实现内容设置为SVC模式,关闭所有中断 获取CPU ID,提取相应的proc

ooonebook的博客 6762

arm linux 内存初始化,ARM linux kernel启动流程 head.S

http://blog.csdn.net/tommy_wxie/article/details/72387481. kernel运行的史前时期和内存布局在arm平台下,zImage.bin压缩镜像是由bootloader加载到物理内存,然后跳到zImage.bin里一段程序,它专门于将被压缩的kernel解压缩到KERNEL_RAM_PADDR开始的一段内存中,接着跳进真正的kernel去执行。该...

weixin_35836405的博客 706

kernel启动流程-head.S的执行_1.概述

1.前言 kernel版本:5.10 平台:arm64 本专题主要基于《arm64_linux head.S的执行流程》系列文章,前者是基于3.18,本专题针对的是内核5.10。主要分析head.S的执行过程。本文主要记录head.S的总体执行过程。 2.如何执行到head.S的第一条指令? vmlinux.lds中定义了入口点为_text ....... ENTRY(_text) ....... SECTIONS { /* * XXX: The linker does

HZero 2361

利用Skipper跨层追踪技术高效定位LVS Short问题

本文详细介绍了如何利用Skipper跨层追踪技术高效定位LVS Short问题。通过解析Skipper的跨层trace net原理和实战操作步骤,帮助芯片设计工程师快速识别版图中的短路问题,显著提升验证效率。文章还提供了处理大规模设计的高级技巧和常见问题排查方法。

weixin_29239661的博客 509

Linux内核4.14版本:ARM64的内核启动过程(一)——start_kernel之前

目录 一、vmlinux.lds.S 二、head.S(进入kernel之前) 2.1 kernel之前bootloder的工作 2.1bootloader和kernel的交互的时候需求 2.3内核执行的第一行代码(_stext) 2.3.1preserve_boot_args() 2.3.2 el2_setup 2.3.3set_cpu_boot_mode_flag 2.3.4__create_page_tables 2.3.5__cpu_setup 2.3.6__pr...

yangguoyu8023的博客 6908

华为HCIP大数据备考实战:从题库精析到834分通关策略

本文详细介绍了华为HCIP大数据认证考试的备考策略,包括题库精析、重点章节突破和考场实战经验。作者以834分的高分通过考试,分享了官方教材的三遍阅读法、自建题库技巧以及各章节核心知识点,帮助考生高效备考并顺利通关。

weixin_29169899的博客 150

22、(1)Linux内核启动分析 “arch/arm/kernel/head.S” 以及流程图

内核启动总流程图 “arch/arm/kernel/head.S”是内核执行的第一个文件,U-Boot调用内核前,必须满足的条件如下:         (1)CPU寄存器的设置               R0 = 0               R1 = 机器类型ID

WuHan MediaTek~~辉煌在于不懈的努力和拼搏! 1082

光伏5G多合一融合终端|光伏多合一融合终端|光伏多合一群调群控网关|分布式光伏群控群调|光伏AGC/AVC系统这几者之间什么技术关系,多少钱一套预算?

RCL-0923光伏电站电压\功率自动控制装置(光伏AVC/AGC),能够接收调度DMS下发的调节目标(电压或无功),按既定策略的调节目标计算光伏电站无功功率出力需求,支持总有、无功,最大可调范围、逆变器个数等数据的上送,通过选择控制设备并进行无功功率出力分配,并将最终控制指令自动下达给被控制设备,最终实现光伏电站无功功率控制,实现无功功率、并网点电压的监测和控制,跟踪调度中心下发的电压或无功调节目标值,实现分布式电源的可观可测,可调可控。光伏5G多合一融合终端的功能:群调群控/AGC/AVC功能。

分布式DTU物联网DTU 2607

在trae CN中使用conda环境(已解决)

Trae CN 是字节跳动于 2025 年 3 月 3 日发布的国内首个 AI 原生集成开发环境(AI IDE)123。

zwzscnds的博客 6937

【linux kernel】linux内核入口:head.o

一、开篇 对于linux内核来说,她有一个统一的入口,那就是head.o模块,在不同架构下,该模块有着不同的文件名称,例如head.S、head_xxx.S;该文件的具体路径是(/arch//kernel/head.S),其中是具体的架构名称。head.o模块用于完成和架构、CPU相关的初始化工作,主要用于为内核主体的执行做准备。除此之外,head.o的功能还有: 检查处理器和架构的有效性。 创建初始的页表表项。 启用处理器的内存管理单元(MMU)。 进行错误检测并报告。 跳转到内核主体的起始位置。(ma

iriczhao的博客 5319

多模态融合相关概念

多模态图像融合

weixin_43921949的博客 7357

【转】Android kernel启动流程

转自:http://blog.csdn.net/yili_xie/article/details/5716837 http://blog.csdn.net/yili_xie/article/details/5735235 http://blog.csdn.net/yili_xie/article/details/5750664  

风花散却隽世间,雪月寥落化雨田 2073

2️⃣ Coze创建智能体教学(2025年全新版本)

扣子平台提供强大支持,可将智能体轻松集成至各类社交平台,使智能体得以服务更为庞大的用户群体。当下支持集成的平台如下:​1️⃣豆包:作为聚焦 AI 内容创作的平台,非常适合发布充满创意、娱乐性强的智能体。​2️⃣飞书:适合发布用于办公协作的智能体,助力团队提升工作效率。​3️⃣微信:借助公众号或小程序的形式,能够服务海量微信用户。​4️⃣抖音:适合发布与短视频相关的智能体,为用户提供创意与娱乐服务。​

李同学Lino的博客 4577

车载以太网-组播

车载以太网-组播

天赐好车的博客 253

Linux系统移植篇(七)Linux 内核kernel 启动流程

Linux系统移植篇(七)Linux 内核kernel 启动流程

你要拥有一个你喜欢并且热爱的人生 1363

卷积层 (Convolutional Layer) 原理与代码实例讲解

卷积层 (Convolutional Layer) 原理与代码实例讲解 关键词:卷积层,卷积操作,滤波器,特征提取,神经网络 1. 背景介绍 卷积神经网络 (Convolutional N

欢迎来到我的优快云空间!这里聚焦AI大模型应用实战,分享前沿技术、实战案例与开发经验。 770

linux内核启动分析(一)

最近工作中经常使用飞腾E2000的开发版,也遇到一些启动问题,所以追踪了一下linux内核启动流程。麒麟的代码我们看不了,但是我们可以直接看飞腾的开源内核代码,,我们使用的是5.10分支。飞腾E2000的开发版可以使用uboot和UEFI,无论是uboot还是UEFI加载linux内核,并且启动linux内核都是从arch/arm64/kernel/head.S文件的_head这里开始运行的。

jianjian的博客 2062

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

庖丁解牛式剖析详解linux内核内存管理子系统-kernel阶段之head.S

#linux内核内存管理子系统之kernel阶段head.S分析

weixin_43996442的博客 261

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

海思hi3520dv400 kernel分析(2)——汇编阶段启动分析

在完成了zImage自解压之后,就跳转到了解压后的内核(也就是vmlinux的bin版本Image),具体的入口可以在arch/arm/kernel/vmlinux.lds.S(最终的链接脚本是通过这个文件产生的)中获得: ...... SECTIONS { #ifdef CONFIG_XIP_KERNEL . = XIP_VIRT_ADDR(CONFIG_XIP_PHYS_ADDR)............

Biao 1894

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

上一篇: Android系统启动流程 -- bootloader、linux kernel、android
下一篇: ARMlinux kernel 启动之start_kernel (二)
makeyourprogress
博客等级 码龄11年 26粉丝 57原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值