【问题标题】:ARM, VFP, floating-point, lazy context switchingARM、VFP、浮点、惰性上下文切换
【发布时间】:2020-05-26 19:08:36
【问题描述】:

我正在为 ARM 处理器 (Cortex-A9) 编写操作系统。

我正在尝试实现浮点寄存器的惰性上下文切换。这背后的想法是,浮点扩展最初是为线程禁用的,因此不需要在任务切换上保存浮点上下文。

当线程尝试使用浮点指令时,它会触发异常。操作系统然后启用浮点扩展,并且知道必须在下一次上下文切换中为此线程保存浮点上下文。然后重新执行浮点指令。

我的问题是,即使在 c 代码中没有使用浮点运算,编译器也会生成浮点指令。 这是一个在 c 中不使用浮点的函数的反汇编示例:

10002f5c <rmtcpy_from>:
10002f5c:   e1a0c00d    mov ip, sp
10002f60:   e92ddff0    push    {r4, r5, r6, r7, r8, r9, sl, fp, ip, lr, pc}
10002f64:   e24cb004    sub fp, ip, #4
10002f68:   ed2d8b02    vpush   {d8}
...
10002f80:   ee082a10    vmov    s16, r2
...
10002fe0:   ee180a10    vmov    r0, s16
...
1000308c:   ecbc8b02    vldmia  ip!, {d8}
...

当我有很多这样的功能时,懒惰的上下文切换就没有意义了。

有人知道如何告诉编译器只有在c代码中有浮点运算时才应该生成浮点指令吗?

我使用 gcc 9.2.0。浮点选项为:-mhard-float -mfloat-abi=hard -mfpu=vfp

这里是一个例子c函数(不能用,只是demo):

void func(char *a1, char *a2, char *a3);
int bar_1[1], foo_1, foo_2;

void fpu_test() {
    int oldest_idx = -1;
    while (1) {
        int *oldest = (int *)0;
        int idx = oldest_idx;
        for (int i = 0; i < 3; i++) {
            if (++idx >= 3)
                idx = 0;
            int *lec = &bar_1[idx];
            if (*lec) {
                if (*lec - *oldest < 0) {
                    oldest = lec;
                    oldest_idx = idx;
                }
            }
        }
        if (oldest) {
            foo_1++;
            if (foo_2)
                func("1", "2", "3");
        }
    }
}

gcc 命令行:

$HOME/devel/opt/cross-musl/bin/arm-linux-musleabihf-gcc  -O2 -march=armv7-a -mtune=cortex-a9 -mhard-float -mfloat-abi=hard -mfpu=vfp -Wa,-ahlms=fpu_test.lst -mapcs-frame -c fpu_test.c -o fpu_test.o

汇编程序列表:

...
  35 0000 0DC0A0E1      mov ip, sp
  36 0004 003000E3      movw    r3, #:lower16:foo_2
  37 0008 F0DF2DE9      push    {r4, r5, r6, r7, r8, r9, r10, fp, ip, lr, pc}
  38 000c 006000E3      movw    r6, #:lower16:foo_1
  39 0010 003040E3      movt    r3, #:upper16:foo_2
  40 0014 04B04CE2      sub fp, ip, #4
  41 0018 006040E3      movt    r6, #:upper16:foo_1
  42 001c 004000E3      movw    r4, #:lower16:bar_1
  43 0020 028B2DED      vpush.64    {d8}                <=== this is the problem
...

【问题讨论】:

  • 所有这些选项都在告诉编译器为浮点构建,您是否有演示问题的示例/最小 C 函数和完整的 gcc 命令行(或足以演示问题)?
  • 我用 gcc 命令行的示例 c 函数和汇编程序列表的一部分更新了我的帖子
  • -mapcs-frame 为所有函数生成一个符合 ARM 过程调用标准的堆栈帧,即使这对于正确执行代码并不是绝对必要的。使用此选项指定 -fomit-frame-pointer 会导致不为叶函数生成堆栈帧。默认值为 -mno-apcs-frame。此选项已弃用。
  • 如果我删除它,那么这个推送就会消失(就像浪费寄存器的堆栈帧一样)
  • 删除 -mapcs-frame 适用于简单功能。在我的项目中,vpush 指令的数量减少了,但它们仍然存在。

标签: gcc floating-point arm osdev


【解决方案1】:

GCC has a command-line switch for this, -mgeneral-regs-only.。使用命令行开关时,您可能需要将故意使用浮点寄存器或运算的代码分离到单独的源文件中,以便在没有该开关的情况下进行编译。

As of GCC 9.3 (perhaps 9?), for ARM targets, this is available as a function attribute:

void MyFunction(char *MyParameter) __attribute__ ((general-regs-only));

将属性放在声明之后是较旧的语法,并且需要非定义声明。测试表明 GCC 现在接受声明符之前的属性,并且可以与定义一起使用:

void __attribute__ ((general-regs-only)) MyFunction(char *MyParameter)
{...}

您也可以使用__attribute__ ((nogeneral-regs-only)) 来否定该属性,尽管我没有看到此文档。

这也可以通过pragma 来控制。

-march-mcpu 开关中还有 +nofp 选项,但我认为 -mgeneral-regs-only 是您想要的。

【讨论】:

  • 是的,-mgeneral-regs-only 将没有浮点的 c 函数编译为没有浮点寄存器的代码。但随后我必须将 c 代码分成带和不带浮点的文件。那不是我想要的。
  • attribute ((general-regs-only))”出现错误。也许这在 gcc 9.2.0 中不可用。然而,这个属性迫使我接触源代码,这是我想要避免的。
  • @ErwinP:如果你不能使用命令行开关并且不想修改源代码,我不希望有一个解决方案。
【解决方案2】:

只需注意与函数属性相关的内容: __attribute__ ((general-regs-only)) 似乎不适用于 gcc 9.3.1。

试试“目标”属性。例如:

void __attribute__((target("general-regs-only"))) MyFunction(char *MyParameter)
{...}

“general-regs-only”的否定似乎不适用于 gcc 9.3.1。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多