ARMv8-A 那些事 - 使能MMU之后的一些怪事

By: Ailson Jack
Date: 2024.12.05
个人博客:http://www.only2fire.com/

微信公众号:嵌入式那些事

前述

前几年自己写的一个OS一直是运行在STM32等Cortex-M系列处理器上的,由于我对Cortex-A系列处理器不是很了解也没有将OS移植到Cortex-A系列处理器上。后面自己学习了ARMv7A指令集的一些知识,于是就将自己写的OS移植到了ZYNQ7020芯片上,算是熟悉了OS在ARMv7A架构处理器的移植过程,后面将lwip和fatfs移植到了OS中,丰富了OS的功能。

最近自己看了些ARMv8A指令集的一些知识,就考虑将OS移植到ARMv8A架构的处理器上,目前系统能够正常的运行到第一个任务,但是一使能MMU系统就挂了。因为启动MMU之后系统马上就挂了,也没法打印具体的错误信息,因此这个问题还是比较难排查的。

下面简单的记录下解决这些问题的过程,给遇到类似问题的伙伴一个排查方向吧。

怪事1:使能MMU导致系统死机

由于OS在使能MMU之后就死机了,无法通过打印关键寄存器知道死机的真实原因。这里我就换个方向排查,因为我的MMU代码在裸机下是能够正常工作的,我就只需要找出裸机和OS之间的差异,这里我主要排查3个方向:

  • 更换裸机的交叉编译器;
  • 对比OS的启动配置和裸机的启动配置;
  • 对比Makefile的配置;

更换裸机的交叉编译器

在将裸机的交叉编译器更换为OS使用的交叉编译器编译裸机代码之后,运行裸机镜像,在使能MMU之后,裸机的后续功能正常,暂时排除交叉编译器不一致的影响。

对比OS的启动配置和裸机的启动配置

对比汇编入口代码到调用MMU使能之间的一些关键寄存器配置,我这里主要对比了OS和裸机在启动过程中下述寄存器的值:

  • SCR_EL3
  • SPSR_EL3
  • HCR_EL2
  • SPSR_EL2
  • SCTLR_EL1

将裸机中上述寄存器的值配置得和OS一样,然后编译裸机代码,运行裸机镜像,在使能MMU之后,裸机的后续功能正常,暂时排除启动配置不一致的影响。

对比Makefile的配置

在更换裸机的交叉编译器和对比启动时的一些寄存器配置之后,裸机镜像都没有出现使能MMU之后就死机的问题,这个时候其实我也没有太多的思路了,想着这个问题与代码和编译器都没有关系了,那还有什么会造成这个问题呢。当时思考了下,还剩下Makefile和链接脚本没有去对比了,于是我对比了下OS和裸机的链接脚本,并没有太大的区别,那么只剩下对比Makefile的配置了。

通过对比Makefile,发现只要裸机的Makefile添加上-ffunction-sections编译选项之后,重新编译裸机代码,运行裸机镜像,使能MMU之后就会出现死机问题。

裸机链接脚本对代码段的描述如下:

/* text.boot代码段 */
_text_boot = .;
.text.boot : { *(.text.boot) }
_etext_boot = .;

/*
 * text代码段
 */
_text = .;
.text :
{
    *(.text)
}
_etext = .;

根据链接脚本的规则,所有的代码段应该都要在_text_boot_etext之间,没有添加-ffunction-sections编译选项时,所有的代码段也确实都在_text_boot_etext之间,_etext之后就是数据段了,如下图所示:

在这里插入图片描述
根据map文件可知,所有的函数都在.text段和.text.boot段。

添加-ffunction-sections编译选项之后,重新编译裸机代码,分析map文件发现_etext并不是代码段的结尾,如下图所示:

在这里插入图片描述
_etext之后还有以各个函数命名的代码段,包括使能MMU的函数也在_etext之后。然而在MMU配置时,设置代码段的范围为_text_boot_etext之间,MMU配置_etext之后的一段空间为数据段,此时由于使能MMU的函数在数据段了,因此一使能MMU程序就导致系统死机。

解决这个问题其实也比较简单了,不需要去掉-ffunction-sections编译选项,只需要修改下裸机链接脚本让代码段都处于_text_boot_etext之间,修改后的裸机链接脚本对代码段的描述如下:

/* text.boot代码段 */
_text_boot = .;
.text.boot : { *(.text.boot) }
_etext_boot = .;

/*
 * text代码段
 */
_text = .;
.text :
{
    /* 确保所有函数都在代码段 */
    *(.text*)
}
_etext = .;

此时重新编译裸机代码,所有的代码段都在_text_boot_etext之间了,使能MMU之后程序也能正常运行。

将OS中链接脚本修改之后,系统能够正常运行了,开心。

这个问题的根本原因还是在于链接脚本中对代码段范围的设定存在缺陷,导致本该处于代码段范围的函数,却被划到了数据段中。

怪事2:执行某个函数导致系统死机

在解决了怪事1之后,本以为OS能够正常的进行一些测试,没想到在调用某一个函数时出现系统死机,现象和怪事1一样,不过由于此时MMU已经正常工作了,OS的异常提示信息也给出了一些提示:大致意思就是指令异常,感觉还是MMU配置有些bug导致出现这个问题的。我将MMU配置的区间打印出来,MMU配置了3个区间,代码段,数据段和设备地址空间段:

va:0x00080000 pa:0x00080000 size:0x00007000 // 代码段
va:0x00086000 pa:0x00086000 size:0x1ff7a000 // 数据段
va:0xfe000000 pa:0xfe000000 size:0x02000000 // 设备地址空间

可以看出,代码段的0x860000x87000被重新配置成了数据段,调用处于该区间的函数时就会出现系统死机的情况。这个问题是代码段的结尾没有4KB对齐,而配置数据段时是以代码段的结尾作为数据段的起始地址,导致MMU配置时被划分到了数据段。分析map文件,也可以看出确实是调用了处于0x860000x87000代码段的armv8a_int_enable()函数之后,出现了异常信息:

在这里插入图片描述
这里有两个方法来解决这个问题(下面两个方法任选一个即可)。

方法1

修改链接脚本,将代码段的结尾进行4KB对齐,这样配置MMU时,数据段的起始地址就不会和代码段的结尾重合了。

修改后的链接脚本对代码段的描述如下:

/* text.boot代码段 */
_text_boot = .;
.text.boot : { *(.text.boot) }
_etext_boot = .;

/*
 * text代码段
 */
_text = .;
.text :
{
    /* 确保所有函数都在代码段 */
    *(.text*)
}
. = ALIGN(4096);
_etext = .;

修改链接脚本后,OS终于能够正常运行了,map文件对应如下:

在这里插入图片描述
_etext是0x87000,是4KB对齐的。

方法2

将链接脚本中数据段的起始地址进行4KB对齐,配置MMU的数据段空间时,以链接脚本中的数据段起始地址作为参数进行配置。

后记

-ffunction-sections编译选项介绍

查询了下-ffunction-sections编译选项的说明:GCC链接器的链接操作是以section作为最小的处理单元,只要一个section中的某个符号被引用,那么该section就会被链接进可执行程序。使用GCC编译代码时,可以使用-ffunction-sections-fdata-sections将每个函数或者符号生成为一个section,每个section名与function或data名保持一致。在链接阶段,-Wl,--gc-sections链接选项指示链接器将没有使用到的section去掉,从而减小最终可执行程序的大小。

我们可以使用下面的编译链接选项来启用section优化功能:

C_FLAGS += -ffunction-sections -fdata-sections
LD_FLAGS += -Wl,--gc-sections

欢迎关注博主的公众号(微信搜索公众号:嵌入式那些事),可以扫描下面的公众号二维码:

在这里插入图片描述
如果文中有什么问题欢迎指正,毕竟博主的水平有限。

如果这篇文章对你有帮助,记得点赞和关注博主就行了^_^。

欢迎关注博主的博客和微信公众号。

注:转载请注明出处,谢谢!^_^

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jackailson

你的鼓励是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值