非对称 32 位 SoC

作者:Will Deacon <will@kernel.org>

本文档描述了非对称 32 位 SoC 对执行 32 位(AArch32)应用程序的影响。

日期:2021-05-17

简介

一些 Armv9 SoC 存在一种 big.LITTLE 的缺陷(misfeature),即只有一部分 CPU 能够执行 32 位用户应用程序。在这样的系统上,Linux 默认将这种非对称性视为“不匹配(mismatch)”,并禁用对 PER_LINUX32 进程属性以及 32 位 ELF 二进制文件 execve(2) 的支持,后者会返回 -ENOEXEC。如果在仅支持 64 位的 CPU 的后期上线(late onlining)过程中检测到这种不匹配,那么上线操作将失败,并且新 CPU 将无法用于调度。

令人惊讶的是,这些 SoC 的生产初衷却是为了运行传统的 32 位二进制文件。毫不意外的是,这与 Linux 的默认行为配合得并不好。

未来的 SoC 彻底放弃 32 位支持似乎是不可避免的,因此,如果你陷入了必须在这些过渡平台上运行 32 位代码的尴尬境地,明智的做法是考虑诸如重新编译、仿真或退役等替代方案。如果这些选项都不切实际,请继续阅读下文。

启用内核支持

由于内核支持对用户空间并非完全透明,因此允许 32 位任务在非对称 32 位系统上运行需要显式的“选择加入(opt-in)”,可以通过在内核命令行中传递 allow_mismatched_32bit_el0 参数来启用。

在本文档的剩余部分中,我们将 非对称系统 定义为启用了此内核命令行选项的、运行 Linux 的非对称 32 位 SoC。

对用户空间的影响

在非对称系统上运行的 32 位任务其表现与在同构系统上大多相同,但在 CPU 亲和力(affinity)方面有一些关键区别。

sysfs

能够运行 32 位任务的 CPU 子集在 /sys/devices/system/cpu/aarch32_el0 中进行了描述,并在 ABI 文件 testing/sysfs-devices-system-cpu 中进行了进一步文档说明。

注意: CPU 在被检测到时会通过此文件通告,因此对支持 32 位的 CPU 进行后期上线(late-onlining)可能会导致内核在运行时修改该文件的内容。一旦通告,CPU 绝不会从该文件中移除。

execve(2)

在同构系统上,任务的 CPU 亲和力在 execve(2) 期间得以保留。在非对称系统上,这并不总是可行的,特别是当正在执行的新程序是 32 位但亲和力掩码包含仅支持 64 位的 CPU 时。在这种情况下,内核按如下方式确定新的亲和力掩码

  1. 如果亲和力掩码中支持 32 位的子集不为空,则亲和力将限制在该子集,并且旧的亲和力掩码会被保存。这个保存的掩码在 fork(2) 时会被继承,并在执行 32 位程序的 execve(2) 期间得以保留。

    注意: 此步骤不适用于 SCHED_DEADLINE 任务。请参阅 SCHED_DEADLINE

  2. 否则,将遍历该任务的 cpuset 层级结构,直到找到包含至少一个支持 32 位 CPU 的祖先。然后,任务的亲和力会更改为与遍历确定的 cpuset 的支持 32 位子集相匹配。

  3. 如果失败(即内存不足),亲和力将更改为内核已知的、所有支持 32 位的 CPU 集合。

随后该 32 位任务对 64 位程序执行 execve(2) 将使 (1) 中保存的亲和力掩码失效,并尝试使用之前有效的保存掩码来恢复任务的 CPU 亲和力。由于截止时间策略或 cpuset 层级结构的中间更改,此恢复可能会失败,在这种情况下,execve(2) 继续执行且亲和力保持不变。

对 32 位任务调用 sched_setaffinity(2) 时,将仅考虑请求的亲和力掩码中支持 32 位的 CPU。成功后,任务的亲和力将被更新,并且先前 execve(2) 保存的任何掩码都将失效。

SCHED_DEADLINE

除非通过向 /proc/sys/kernel/sched_rt_runtime_us 写入 -1 来禁用准入控制,否则在非对称 32 位系统上,将 32 位 deadline 任务显式接入默认根域(例如通过调用 sched_setattr(2))将被拒绝。

如果任务的根域包含任何仅 64 位 CPU 且准入控制已启用,则 64 位 deadline 任务对 32 位程序执行 execve(2) 将返回 -ENOEXEC。并发下线支持 32 位的 CPU 可能仍需要执行 execve(2) 中描述的流程,在这种情况下,将跳过步骤 (1) 并在控制台上发出警告。

注意: 如果要在非对称系统上将 SCHED_DEADLINE 用于 32 位任务,建议将一组支持 32 位的 CPU 放入单独的根域中。不这样做很可能会导致错过 deadline(任务超时)。

Cpusets

非对称系统上 32 位任务的亲和力可能包含其所附加的 cpuset 未显式允许的 CPU。这可能是由以下两种情况导致的

  • 附加到仅允许 64 位 CPU 的 cpuset 上的 64 位任务执行了 32 位程序。

  • 包含 32 位任务的 cpuset 所允许的所有支持 32 位的 CPU 都已下线。

在这两种情况下,新亲和力都是根据 execve(2) 中描述的过程的步骤 (2) 计算的,并且无论 cgroup 版本如何,cpuset 层级结构都保持不变。

CPU 热插拔

在非对称系统上,用户空间无法将第一个检测到的支持 32 位的 CPU 下线,任何此类尝试都将返回 -EPERM。请注意,即使主 CPU(即 CPU 0)仅支持 64 位,仍然允许系统挂起。

KVM

尽管 KVM 不会向非对称系统上的任何 vCPU 通告 32 位 EL0 支持,但 EL1 处损坏的客户机仍可能尝试在 EL0 处执行 32 位代码。在这种情况下,32 位模式下的 vCPU 线程退出将返回到宿主机用户空间,其 exit_reasonKVM_EXIT_FAIL_ENTRY,并且在通过随后的 KVM_ARM_VCPU_INIT 操作成功重新初始化之前,它将保持不可运行状态。

调度域隔离

为了避免在强制迁移 32 位任务时干扰引导定义的域隔离 CPU(使用 isolcpus=[domain] 指定),当启用对非对称 32 位系统的支持时,这些 CPU 将被视为仅 64 位。

然而,与引导定义的域隔离相反,在非对称 32 位系统上不建议使用 cpuset 隔离分区进行运行时定义的域隔离,这会导致未定义行为。