zImage的位置对于ARM32内核解压的影响
ARM32内核解压流程简单总结了解压流程,这里给出zImage加载位置和Image解压位置的不同组合下,zImage,Image和可能存在的重定位zImage在内存上的位置分布。
因为解压过程中的判断是根据zImage和解压后Image的相对位置来进行不同处理,在以下的分析中,可以认为Image位置是固定的,但是zImage的位置在不断地向低地址方向移动。 示意图中,地址是从上方向下方递增的。下面是示意图中各个箭头所表示的意义。
- Page_table_start:解压过程中页表的起始地址。
- Kernel_start:未压缩内核Image的起始地址,实际上是相对于内存起始地址偏移为
TEXT_OFFSET
的地址。 - Kernel_end:未压缩内核Image的结束地址。
- Kernel_bss_end:未压缩内核Image使用的bss段结束地址。
- start:zImage中的start段,这部分代码只在重定位前会运行。
- check_page_table:检查是否可以创建页表的代码位置。
- restart:代码自有的restart标签所在位置。
- check_overlap:检查Image是否和zImage有重叠的代码位置。如果没有重叠的话,就跳转到wont_overwrite。
- wont_overwrite:代码自有的wont_overwrite标签位置。运行到这里时,表示zImage和Image一定不会重叠了。一种情况是不需要重定位,直接跳转到这里。一种是需要重定位,运行到这里时,已经进行过重定位,并且是重定位后所在的位置。
- not_relocated:代码自有not_relocated标签所在位置。如果zImage运行地址和链接时指定的VMA相同,可以直接跳转到这里。否则,就要修正GOT等数据才能执行到这里。
- cache_flush:缓存操作相关代码的起始位置,这部分代码在重定位跳转前,和重定位跳转后都需要运行。
- _edata:zImage链接脚本指定的符号,表示data段的结束地址。如果有appended DTB的话,会根据appended DTB的大小进行调整。
- _end:zImage的bss段结束地址。如果有appended DTB的话,会根据appended DTB的大小进行调整。
- .L_user_stack_end:zImage栈空间的结束地址。如果有appended DTB的话,会根据appended DTB的大小进行调整。
- Heap_end:zImage堆的结束地址。如果有appended DTB的话,会根据appended DTB的大小进行调整。
- Headroom_end:zImage预估结束地址,和实际结束地址可能不一致。基于appended DTB大小不会很大的假设,预估的结束地址一定会大于实际结束地址。否则,页表就有可能覆盖掉appended DTB。
Kernel_end小于等于wont_overwrite
这种情况下,zImage用于解压Image的代码在较高地址,Image在较低地址,两者没有重叠,不需要重定位zImage,同时可以在重定位前就创建页表,并使能缓存。注意的是,在上面的示意图中,虽然内核和zImage部分代码有重叠,但是这部分代码在解压内核过程中不会使用,因此没有影响。如果zImage和Image完全没有重叠,也属于这种情况。
虚线右半部分是zImage不带有appended DTB的情况。
虚线左半部分是zImage支持appended DTB,并且带有appended DTB的情况。此时,Image的bss不会覆盖到appended DTB,也不需要重定位。如果进一步细分的话,这里还有两种情况。代码里是比较Image的bss段大小和_edata与wont_overwrite的差,来决定是否要调整kernel_end的值。一种情况是bss段大小较小,不需要调整,kernel_end自然小于wont_overwrite。另外一种情况是bss段大小较大,需要增加。但是这种情况下,即使有所增加,kernel_end也一定会仍然小于wont_overwrite。
如果内核的bss会覆盖掉appended DTB,就会变成如下所示。这里虚线右半部分是不带有appended DTB,还是和上一幅图一样,不受影响。
代码会对内核结束地址进行调整,也就是加上图中所示的extra space for bss的大小。之后再根据extra space for bss的结束地址是否大于wont_overwrite来确定是否要重定位zImage。在这种情况下,一定会得到要重定位zImage的结论。调整时计算extra space for bss的大小,使用的是wont_overwrite。之后重定位zImage并拷贝时,是从restart处开始拷贝,因此重定位后的appended DTB起始地址实际上会大于kernel_bss_end。
另外,这里还有一块空间是extra space for reloc code。可以看出,如果没有这一块空间,重定位后的代码就有可能覆盖掉cache_flush之后的部分代码。而cache_flush之后的部分代码在跳转到浅色部分zImage前,还会被执行。因此,增加这块空间将重定位后zImage的位置向高地址移动了。
Kernel_end大于wont_overwrite,Kernel_start小于check_page_table
此时,页表和zImage还没有重叠,可以在最开始就创建页表。但是,Image代码会覆盖掉zImage用于解压的代码,因此,需要将zImage拷贝到Image结束地址之后的高地址范围内,也就是上图中浅色部分zImage。重定位完成之后,就在浅色部分所在地址上运行代码了。
对于带有appended DTB的情况,当kernel_end大于wont_overwrite时,一定会对zImage进行重定位。虽然图示中显示的内核的bss不会覆盖appended DTB。但是,还是存在这种可能。如果会覆盖的话,就会在kernel_end后面增加若干空间extra space for bss,用于保证重定位后的appended DTB不会被覆盖。如果不会覆盖appended DTB,就不会有extra space for bss这一块空间。
之后要讨论的情形,kernel_end会一直大于wont_overwrite,需要考虑的是kernel_start和zImage的相对关系了。
Kernel_start大于check_page_table,page_table_start小于Heap_end
随着zImage和Image进一步靠近,zImage进而和页表范围也有重叠了。因此,在重定位过程中,不会创建页表,也就是灰色页表的意义。在重定位完成之后,执行浅色部分的zImage代码时,就会创建页表,也就是浅色页表的意义。页表的位置没有变化,只是由于重定位之前,需要用到这一块的数据,因此不能创建页表。
这里extra space for bss是否存在也是取决于内核bss大小。想象上图中kernel_bss_end往下移动,还是有可能覆盖掉重定位后,也就是浅色的appended DTB。但是代码在拷贝zImage之前,会根据kernel BSS的大小调整extra space for bss的大小,保证浅色的appended DTB不会被覆盖。
一般来说,解压后的Image应该会比zImage要大得多。因此,这种情况下,重定位后的代码一般不会覆盖cache_flush之后的代码了,但是代码实现上还是总会加上extra space for reloc code这一段。
Page_table_start大于等于heap_end,kernel_start小于headroom_end
此时,Image使用的空间和zImage已经没有重叠了,因此不需要重定位zImage。虽然页表和zImage实际上没有重叠了,但是因为在最开始判断是否能够创建页表时,使用的是估计范围headroom_end和kernel_start进行比较,所以还是没有创建页表。在后面获得zImage的准确范围后,使用heap_end和kernel_start进行比较,所以就不用重定位了,也可以放心地创建页表。
Kernel_start大于headroom_end
此时,页表和zImag的预估范围也已经没有重叠了。最开始就可以创建页表,也不用重定位zImage。