启动系统自带解压缩代码怎么写_ARM32 Linux内核如何解压缩

传统上,ARM使用压缩内核。这样做有两个主要原因:

它可以节省闪存或其他保存内核的存储介质上的空间,而内存就是金钱。例如,对于我正在使用的Gemini平台,vmlinux未压缩内核为11.8 MB,而压缩zImage仅为4.8 MB,我们节省了50%以上

加载速度更快,因为解压缩运行所需的时间比从存储介质(例如闪存)传输未压缩图像所需的时间短。对于NAND闪存控制器,这很容易实现。

这旨在作为Linux内核在ARM 32位旧系统上如何自解压的全面摘要。如果arch / arm / *下的所有计算机都是使用压缩内核启动的,则大多数机器都使用压缩内核。

引导程序

引导加载程序,无论是RedBoot,U-Boot还是EFI,都将内核映像放置在物理内存中的某个位置,并通过在低位寄存器中传递一些参数来执行该映像。

Russell King在2002年的《引导ARM Linux》文档中定义了用于从引导加载程序引导Linux内核的ABI。引导加载程序将0放入寄存器r0中,将体系结构ID放入寄存器r1中,并将指针指向寄存器r2中的ATAG。ATAG将包含物理内存的位置和大小。内核将放置在此内存中的某个位置。只要解压缩的内核适合,它就可以从任何地址执行。然后,引导加载程序在超级用户模式下跳转到内核,同时禁用所有中断,MMU和高速缓存。

在当代的设备树内核上,r2被重新用作物理内存中设备树blob(DTB)的指针。(在这种情况下,将忽略r1。)DTB也可以附加到内核映像,并可以选择使用r2中的ATAG进行修改。我们将在下面进一步讨论。

解压zImage

如果内核是压缩的,则执行在符号开始处的arch / arm / boot / compressed / head.S中开始:在文件下移一点。(这不是立即显而易见的。)出于遗留原因,它以8或7条NOP指令开头。它将跳过一些幻数并将指针保存到ATAG。因此,现在内核解压缩代码是从加载它的物理内存的物理地址执行的。

然后,解压缩代码找到物理内存的开始。在大多数现代平台上,这是通过Kconfig选择的代码AUTO_ZRELADDR完成的,这意味着程序计数器与0xf8000000之间的逻辑与。这意味着内核很容易假定它已经在物理内存的第一块的第一部分中被加载并执行。

正在制作一些补丁,这些补丁将尝试从设备树中获取此信息。

然后将TEXT_OFFSET添加到指向物理内存开始的指针。顾名思义,这是内核.text段(作为编译器的输出)应位于的位置。.text段包含可执行代码,因此这是解压缩后内核的实际起始地址。TEXT_OFFSET通常为0x8000,因此内核将位于物理内存中的0x8000字节中。这在arch / arm / Makefile中定义。

0x8000(32KB)偏移是一个约定,因为通常在0x00000000处放置一些特定于体系结构的固定数据,例如中断向量,并且许多旧系统将ATAG放置在0x00000100处。还必须有一些空间,因为当内核最终启动时,它将从该地址中减去0x4000(对于LPAE则为0x5000)并将初始内核页表存储在该地址中。

对于某些特定平台,TEXT_OFFSET将向下推到内存中,特别是某些高通平台会将其推到0x00208000,因为物理内存的前0x00200000(2 MB)用于与调制解调器CPU的共享内存通信。

接下来,如果有可能在整个未压缩+压缩的内核映像中放入一个页面,则解压缩代码将建立一个页表。页表不是用于虚拟内存,而是用于启用缓存,然后将其打开。如果可以使用缓存,出于自然原因,解压缩会更快。

接下来,内核设置一个本地堆栈指针和malloc()区域,以便我们可以处理子例程调用和少量内存分配,并执行用C编写的代码。该位置设置为指向内核映像末尾。

3e7712bf477a360597f6ee961d6acf74.png

带有附加DTB的内存中压缩内核。

如果找到了附加的DTB,并且设置了CONFIG_ARM_ATAG_DTB_COMPAT,我们首先将DTB扩展50%,然后调用atagstofdt,它将使用来自ATAG的信息(如内存块和大小)来扩展DTB。

下一个。DTB指针(开头是作为r2传递的指针)被指向附加DTB的指针覆盖,我们还保存了DTB的大小,并在DTB之后设置了内核映像的末尾,因此附加了DTB(可选 被ATAG修改)包含在压缩内核的总大小中。如果找到了附加的DTB,我们还将增加堆栈和malloc()的位置,以免破坏DTB。

注意:如果在r2中传入了设备树指针,并且还提供了附加的DTB,则附加的DTB将“获胜”,并且系统将使用它。有时可以使用它来覆盖引导加载程序传递的默认DTB。

注意:如果ATAG在r2中传入,则肯定没有DTB通过该寄存器传入。如果您使用不想替换的旧引导加载程序,则几乎总是需要CONFIG_ARM_ATAG_DTB_COMPAT符号,因为ATAG正确定义了旧平台上的内存。可以在设备树中定义内存,但是通常人们会跳过这种情况,而是依靠引导加载程序提供这种方式,一种方式(引导加载程序更改了DTB),另一种方式(ATAG扩展了附加的DTB位置)。开机)。

4f3a7bceda9bee7cd03d14b40e05b023.png

解压缩的内核可以与压缩的内核重叠。

接下来,我们检查是否要用未压缩的内核覆盖压缩的内核。那将是不幸的。如果发生这种情况,我们将检查未压缩内核在内存中的结束位置,然后将自身(压缩内核)复制到该位置。

然后,该代码简单地执行了一个技巧,即跳回到名为restart的标签的重定位地址:这是设置堆栈指针和malloc()区域的代码的开始,但是现在在新的物理地址处执行。

这意味着它将再次设置stack和malloc()区域并查找附加的DTB,一切看起来就像是从此位置开始加载内核。(但是有一个区别:我们已经使用ATAG扩展了DTB,因此不再需要这样做。)这一次,未压缩的内核不会覆盖已压缩的内核。

6c469cde7da011575fa8a496842f053c.png

我们将压缩后的内核向下移动,以使解压缩后的内核适合。

不会检查内存是否用完,即是否会碰巧将内核复制到物理内存的末尾。如果发生这种情况,结果将不可预测。在以下情况下,如果内存为8MB或更小,则会发生这种情况:不要使用压缩内核。

f910f8052d649729b9877ee05e4bca9f.png

压缩的内核移到解压缩的内核下方。

现在我们知道内核可以被解压缩到压缩映像之下的内存中,并且它们在解压缩期间不会发生冲突,我们将在标签wont_overwrite:上执行。

我们检查是否在解压缩器链接到的地址上执行,并可能更改一些指针表。这是用于执行解压缩器的C运行时环境。

我们确保已打开缓存。(页表肯定没有空间。)

我们也为C运行时环境清除了BSS区域(因此所有未初始化的变量均为0)。

接下来,我们在boot / compressed / misc.c中调用decompress_kernel()符号,该符号又调用do_decompress(),后者调用__decompress()来执行实际的解压缩。

这是用C实现的,解压缩的类型根据Kconfig选项的不同而不同:与构建内核时选择的压缩相同的解压缩器将链接到映像中并从物理内存执行。所有架构共享相同的解压缩库。调用的__decompress()函数将取决于链接到映像中的lib / decompress _ *。c中的哪个解压缩器。通过简单地将整个解压缩器包含到文件中,就可以在arch / arm / boot / compressed / decompress.c中选择解压缩器。

在调用解压缩器之前,在寄存器中设置解压缩器所需的有关压缩内核位置的所有变量。

501a42c0bcbcd78ad70446300fb91619.png

解压缩后,解压缩的内核位于TEXT_OFFSET处,并且附加的DTB(如果有)保留在压缩内核所在的位置。

解压缩后,我们调用get_inflated_image_size()来获取最终的解压缩内核的大小。然后,我们刷新并再次关闭缓存。

然后,我们跳转到符号__enter_kernel,它将r0,r1和r2设置为引导加载程序将离开它们的位置,除非我们有附加的设备树blob,在这种情况下r2现在指向该DTB。然后,我们将程序计数器设置为内核的开始,即物理内存加上TEXT_OFFSET的开始,在非常传统的系统上通常为0x00008000,在某些Qualcomm系统上可能为0x20008000。

现在,我们处于与将未压缩的内核vmlinux文件加载到TEXT_OFFSET处的内存中相同的位置,传递(通常是)r2中的设备树。

内核启动:执行vmlinux

未压缩的内核从符号stext()开始执行,即文本段的开始。可以在arch / arm / kernel / head.S中找到此代码。

这是另一个讨论的主题。但是请注意,此处的代码不会寻找附加的设备树!如果应使用附加的设备树,则必须使用压缩内核。用ATAG扩展任何设备树也是如此。这还必须使用压缩的内核映像,为此代码是引导压缩内核的程序集的一部分。

仔细查看内核解压缩

让我们仔细看看Qualcomm APQ8060减压器。

首先,您需要启用CONFIG_DEBUG_LL,这使您能够在UART控制台上锤击字符,而无需任何高级打印机制的干预。它所做的只是为UART提供一个物理地址,并提供轮询字符的例程。它设置DEBUG_UART_PHYS,以便内核知道物理UART I / O区域的位置。确保这些定义正确。

首先启用一个名为CONFIG_DEBUG_UNCOMPRESS的Kconfig选项。所有这些操作是在解压缩内核之前打印短消息“ Uncompressing Linux…”,在解压缩之后打印“完成,引导内核”。这是一个很好的冒烟测试,它表明CONFIG_DEBUG_LL已设置并且DEBUG_UART_PHYS是正确的,并且减压有效,但仅此而已。这不提供任何低级别的调试。

可以通过启用arch / arm / boot / compressed / head.S中的DEBUG定义来调试和检查实际的解压缩内核解压缩,这最简单的方法是在-DDEBUG上将head.S中的head.S标记到AFLAGS(汇编器标志)上。arch / arm / boot / compressed / Makefile像这样:

AFLAGS_head.o + = -DTEXT_OFFSET = $(TEXT_OFFSET)-DDEBUG

引导时,我们会收到以下消息:

这意味着在启动时,我将内核加载到0x40300000,这将与未压缩的内核发生冲突。因此,内核已复制到0x41801D00,这是未压缩内核将结束的位置。添加更多的调试打印信息,我们可以看到,首先在0x40DEBA68处找到了一个附加的DTB,然后将内核向下移动后,在0x422E56A8处找到了它,这是引导内核时保留的位置。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值