泰晓科技 -- 聚焦 Linux - 追本溯源,见微知著!
网站地址:https://tinylab.org

泰晓实验箱:上手简单,配公开课
请稍侯

LicheePi 4A 实时性测试实践

王杰迅 创作于 2023/09/20

Corrector: TinyCorrect v0.2-rc2 - [tounix spaces header tables] Author: 王杰迅 wangjiexun@foxmail.com Date: 2023/09/17 Revisor: falcon falcon@tinylab.org Project: RISC-V Linux 内核剖析 Sponsor: PLCT Lab, ISCAS

前言

之前的文章 中介绍了 LicheePi 4A 开发板构建并运行 Linux v6.5-rc1 的过程,当时实现的效果为:内核可以成功启动并进入 initramfs 命令行界面。

在本文中,将介绍为 LicheePi 4A 开发板添加 eMMC 支持的过程,使得内核可以从 eMMC 上的 rootfs 启动,同时将内核版本更新到 v6.5,打入对应的 PREEMPT_RT 补丁。

除此之外,本文还将介绍使用 cyclictest 和 ftrace 进行延迟追踪的小技巧。

软件版本

SoftwareVersion
Linux6.5
U-Boot2020.01
OpenSBIthead-opensbi
Buildroot2023.02.2

添加 eMMC 支持

LicheePi 4A 开发板提供了 eMMC 作为外部存储,eMMC 是 Flash 的一种,在嵌入式系统中使用广泛。

官方 Linux 内核从 v6.5 版本开始添加了对 LicheePi 4A 开发板的设备树支持,具体见 arch/riscv/boot/dts/thead 文件夹下的各文件。但其中的设备树目前不够完善,主要的外设只实现了串口,没有办法使用外部存储。因此想要使用 LicheePi 4A 的 eMMC,必须借助社区中其他开发者提供的补丁。

Drew Fustini 的补丁 为开发板 Beaglev-Ahead 添加了 eMMC 支持。由于 Beaglev-Ahead 和 LicheePi 4A 都是使用 TH1520 作为主控芯片,因此该补丁也可适用于 LicheePi 4A。

该补丁添加了对 eMMC 的设备树支持,并修改了驱动文件 drivers/mmc/host/sdhci-of-dwcmshc.c,在开启内核配置项 CONFIG_MMC_SDHCI_OF_DWCMSHC=y 之后,可以实现对 eMMC 外设的基本读写。但目前该驱动还没有支持 DMA,效率较低,仍在迭代开发中。

将 eMMC 驱动和设备树替换完毕后,仍出现了多个小问题,最终通过选择合适的 OpenSBI,才实现了稳定地使用 eMMC 进行读写。

load access fault

最初时,由于使用的 OpenSBI 版本过低,没有支持设备树中的 “thead,c900-clint”,在进行初始化时会出现如下报错:

[    0.000000] Oops - load access fault [#1]
...
[    0.000000] epc : __plic_toggle+0x6a/0x72
[    0.000000]  ra : __plic_init.constprop.0+0x31e/0x4ac

更换为较新版本的 OpenSBI(v1.2 及以上)后,报错消失,可以正常启动。

illegal instruction

当尝试给 eMMC 施加压力测试时,如使用如下命令:

$ while true; do /bin/dd if=/dev/zero of=bigfile bs=1024000 count=1024; done &

会出现如下报错导致系统崩溃:

sbi_trap_error: hart1: illegal instruction handler failed (error -2)

经 Jisheng Zhang 老师提醒,使用 最新的 thead-opensbi 后,eMMC 在压力测试下也可以稳定使用,不再报错。猜测该报错产生的原因为:TH1520 芯片的部分指令没有被官方 OpenSBI 实现。

总之,目前应使用 最新的 thead-opensbi,以保证系统正常运行。

rootfs

当 eMMC 可以正常使用后,就可以不再使用 initramfs。将 rootfs 烧写到 eMMC 后,系统就可以从 eMMC 的 rootfs 启动。使用 Buildroot 的 menuconfig,选择构建的 rootfs 文件系统类型为 ext4:

Filesystem images  --->
  [*] ext2/3/4 root filesystem
    ext2/3/4 variant (ext4)  --->
  (rootfs) filesystem label
  (60M) exact size

LicheePi 4A 提供的烧录工具最大可以将 4GB 的 rootfs 烧录到 eMMC 中,但实际的 rootfs 可能并没有那么大,在制作 rootfs 时可以选择较小的大小,如 60MB。在系统启动后,可以使用 resize2fs 命令对 rootfs 进行扩容:

$ resize2fs /dev/mmcblk0p3

使用该命令会自动将 rootfs 扩容,将 eMMC 的未使用存储空间全部并入其中。

编译 PREEMPT_RT v6.5 内核

为了提高系统实时性,首先打入和 Linux 内核版本对应的 官方 PREEMPT_RT 补丁。同时,由于 LicheePi 4A 开发板基于 RISC-V 架构,因此还需要打入 针对 RISC-V 架构的 PREEMPT_RT 补丁

解决 redefinition 错误

在将 Linux 内核更新到 v6.5,打入 PREEMPT_RT 补丁并配置抢占模式为 PREEMPT_RT 后,尝试编译时出现报错:

arch/riscv/kernel/irq.c:64:6: error: redefinition of 'do_softirq_own_stack'
   64 | void do_softirq_own_stack(void)
      |      ^~~~~~~~~~~~~~~~~~~~
In file included from ./arch/riscv/include/generated/asm/softirq_stack.h:1,
                 from arch/riscv/kernel/irq.c:15:
./include/asm-generic/softirq_stack.h:8:20: note: previous definition of 'do_softirq_own_stack' was here
    8 | static inline void do_softirq_own_stack(void)
      |                    ^~~~~~~~~~~~~~~~~~~~

经过查看后发现,在 arch/riscv/kernel/irq.cinclude/asm-generic/softirq_stack.h 文件中出现了对 do_softirq_own_stack 函数的重复定义。

该错误与以下两个配置项有关:

CONFIG_HAVE_SOFTIRQ_ON_OWN_STACK
CONFIG_SOFTIRQ_ON_OWN_STACK

正常的逻辑是,如果内核配置 CONFIG_SOFTIRQ_ON_OWN_STACK=y,则该函数由 include/asm-generic/softirq_stack.h 文件负责实现。

如果内核配置 CONFIG_SOFTIRQ_ON_OWN_STACK=n,则该函数在 include/asm-generic/softirq_stack.h 文件中只负责声明,而由特定架构负责具体实现,如 RISC-V 架构就在 arch/riscv/kernel/irq.c 文件中实现。

但在 arch/riscv/kernel/irq.c 文件中,错把实现 do_softirq_own_stack 函数的条件 SOFTIRQ_ON_OWN_STACK 写成了 HAVE_SOFTIRQ_ON_OWN_STACK 选项,当内核配置项如下时:

CONFIG_HAVE_SOFTIRQ_ON_OWN_STACK=y
CONFIG_SOFTIRQ_ON_OWN_STACK=n

两个文件都会实现 do_softirq_own_stack 函数,出现 redefinition 错误。

值得一提的是,当没有开启 PREEMPT_RT 时,开启 HAVE_SOFTIRQ_ON_OWN_STACK 时默认会开启 SOFTIRQ_ON_OWN_STACK,因此不会暴露出错误,只有当开启 PREEMPT_RT 时,才会导致编译失败。

目前,该补丁已发送至 邮件列表

延迟追踪技巧

在优化系统实时性能时,首先需要分析产生最大延迟的原因。常用的延迟测试及追踪工具为 cyclictest 和 ftrace。这两个软件的基本用法,可以参考泰晓科技的往期直播分享 RISC-V 实时抢占优化实践对应 PPT

实际测试时,容易遇到的一个问题是:测试得到的最大延迟并不是 cyclictest 产生的,这对优化实时性能没有任何帮助。例如:

# wakeup_rt latency trace v1.1.5 on 6.5.0-rt6-r1208-00003-g999d221864bf-dirty
# --------------------------------------------------------------------
# latency: 12086 us, #6/6, CPU#0 | (M:preempt_rt VP:0, KP:0, SP:0 HP:0 #P:4)
#    -----------------
#    | task: irq/12-ttyS0-74 (uid:0 nice:0 policy:1 rt_prio:50)
#    -----------------
……

而实际希望得到的最大延迟和调用栈应该是由 cyclictest 产生的,例如:

# wakeup_rt latency trace v1.1.5 on 6.5.0-rt6-r1208-00003-g999d221864bf-dirty
# --------------------------------------------------------------------
# latency: 879 us, #6/6, CPU#2 | (M:preempt_rt VP:0, KP:0, SP:0 HP:0 #P:4)
#    -----------------
#    | task: cyclictest-212 (uid:0 nice:0 policy:1 rt_prio:99)
#    -----------------
……

如果进行了较长时间的测试之后,查看 ftrace 的结果发现没有任何帮助,难免会令人大失所望。因此需要使用一些技巧保证最大延迟和调用栈是由 cyclictest 产生的。

cyclictest 提供了参数 --breaktrace=--tracemark 以更好地锁定最大延迟的产生原因。--breaktrace 表示,如果测试时 cyclictest 的最大延迟超过给定的数值,就中断测试。--tracemark 表示保存产生最大延迟的函数调用栈。在 cyclictest 超过指定延迟后立即中断测试并记录,基本可以保证最大延迟是由 cyclictest 产生的。

在使用 --tracemark 参数前首先要保证 ftrace 挂载好,并在 current_tracer 文件中选择合适的 tracer。测试前无需手动将 tracing_on 文件置 1,cyclictest 在测试时会自动将 tracing_on 文件置 1,测试结束会自动置 0。测试因超过设定数值而中断后,不会自动打印调用栈,需要自己手动打印 ftrace 中的 trace 文件查看。

下面是一个简单的示例脚本,在 cyclictest 产生的最大延迟大于 100μs 时,会自动中断测试,并打印产生的最大延迟和函数调用栈:

#!/bin/sh
mount -t tracefs none /sys/kernel/tracing
cd /sys/kernel/tracing
echo wakeup_rt > current_tracer
cyclictest --breaktrace=100 --tracemark
cat trace

总结

本文介绍了为 LicheePi 4A 开发板添加 eMMC 支持的过程,使得内核可以从 eMMC 上的 rootfs 启动,还为 v6.5 版本的内核打入了 PREEMPT_RT 补丁,并解决了 redefinition 编译错误。除此之外,本文还介绍了使用 cyclictest 和 ftrace 追踪延迟的小技巧。

参考资料



Read Album:

Read Related:

Read Latest: