将打包的数据与对齐的内存访问相结合

标签 c gcc arm memory-management memory-alignment

我正在尝试执行理论上应该可行的内存优化,但是我开始怀疑arm-elf-gcc的能力。请告诉我我错了。

我有一个嵌入式系统,它的主内存非常少,而电池供电的nvram甚至更少。我将校验和的配置数据存储在nvram中,以便在引导时可以验证校验和并继续先前的运行,或者如果校验和无效,则可以开始新的运行。在运行期间,我会在此配置数据中更新各种大小的各个字段(并且可以使校验和无效,直到以后重新计算为止)。

所有这些都在物理地址空间中运行-普通sram映射到一个位置,而nvram映射到另一个位置。这就是麻烦所在-对nvram的所有访问都必须以32位字为单位;不允许字节或半字访问(尽管在主存储器中显然没问题)。

因此,我可以a)将所有配置数据的工作副本存储在主内存中,并在我重新计算校验和时将其memcpy存入nvram,或者b)直接在nvram中使用它,但是以某种方式使编译器确信所有结构都是压缩,并且所有访问不仅必须对齐32位,而且还必须是32位宽。

选项a)浪费了宝贵的主内存,我宁愿在运行时进行权衡以保存它(尽管如果代码大小最终浪费的时间超过了我在数据大小上节省的时间),则可以通过选项b)。

我希望__attribute__ ((packed, aligned(4)))或其中的一些变体可以在这里有所帮助,但是到目前为止,我所做的所有阅读和实验都使我失望。

这是我正在处理的那种配置数据的玩具示例:

#define __packed __attribute__ ((packed))
struct __packed Foo
{
    uint64_t foo;
    struct FooFoo foofoo;
}

struct __packed Bar
{
    uint32_t something;
    uint16_t somethingSmaller;
    uint8_t evenSmaller;
}

struct __packed PersistentData
{
    struct Foo;
    struct Bar;
    /* ... */
    struct Baz;
    uint_32 checksum;
}

您可以想象不同的线程(每个线程分别执行Foo,Bar和Baz的功能)会适本地更新其自身的结构,并在某个时刻进行同步以声明其时间以重新计算校验和并进入休眠状态。

最佳答案

避免众所周知的位域,它们是C语言的问题,不可靠,不可移植,可能随时更改实现。无论如何也不会帮助您解决这个问题。

union 也浮现在脑海,但是我已经在SO上纠正了足够的时间,以至于您不能使用 union 根据C标准更改类型。尽管正如我在其他海报上所假定的那样,但我还没有看到使用 union 来更改类型的情况还没有奏效的情况。 splinter 的位域,不断的, splinter 的 union 内存共享,到目前为止没有痛苦。 union 不会为您节省任何费用,因此在这里实际上不起作用。

您为什么要让编译器来完成这项工作?您可能需要在编译时使用某种链接程序类型的脚本,该脚本指示编译器对某些地址空间进行掩码,移位,读-修改-写操作进行32位访问,而对于其他地址空间,则使用更自然的单词,halfword和字节访问。我还没有听说过gcc或C语言具有这样的控件,无论是在语法上还是在某种形式的编译器脚本或定义文件中。而且,如果确实存在,那么它的使用范围还不够广泛,不够可靠,我希望编译器能够避免并避免发生错误。我只是没有看到编译器这样做,当然不是以struct的方式。

对于阅读而言,您可能会很幸运,这在很大程度上取决于硬件人员。该nvram存储器接口(interface)在贵公司,其他公司制造的芯片内部,芯片边缘等位置在哪里?像您部分描述的那样的局限性可能意味着区分访问大小或字节 channel 的控制信号可能会被忽略。因此,ldrb可能会将nvram视为32位读取,并且arm会捕获正确的字节 channel ,因为它认为这是8位读取。我将做一些实验来验证这一点,它有多个臂存储总线,每个总线都有许多不同类型的传输。也许与硬件人员交谈,或者进行一些hdl模拟(如果有的话),以了解 ARM 的实际作用。如果您不能使用此快捷方式,则无论您如何使编译器执行操作,读操作都将是具有可能的掩码的ldr并转移。

除字长以外的其他写入必须为读取-修改-写入。 ldr,bic,shift或str。无论是谁做的,您还是编译器。

自己动手,看不到编译器将如何为您完成。包括gcc在内的编译器很难完成您似乎认为正在告诉它的特定访问:

*(volatile unsigned int *)(SOME_ALIGNED_ADDRESS)= some_value;

我的语法可能是错误的,因为我几年前就放弃了,但是它并不总是产生无符号的int大小的存储,并且当编译器不想要时,它就不会。如果它不能可靠地做到这一点,您如何期望它为该变量或结构创建一种加载和存储形式,以及为该变量或结构创建另一种加载形式?

因此,如果您有特定的说明,则需要编译器来生成,那么您将失败,必须使用汇编器,句点。特别是ldm,ldrd,ldr,ldrh,ldrb,strd,str,strh,strb和stm。

我不知道您有多少nvram,但在我看来,解决您的问题的方法是将nvram中的所有内容都设置为32位。您会花费一些额外的周期来执行校验和,但您的代码空间和(可变)内存使用量最少。所需的组装非常少(如果您愿意,则不需要)。

如果您担心这么多的优化,我也建议尝试使用其他编译器。至少要尝试一下gcc 3.x,gcc 4.x,llvm和rvct,我认为Keil附带了一个版本(但不知道它与真正的rvct编译器相比如何)。

我对您的二进制文件必须小有一种感觉。如果必须将内容打包到nvram中,而不能全部放入32位条目,则建议您使用几个汇编器辅助函数,一种类型的get32和put32,两种类型的get16和put16,以及四种类型的get8和put8。在编写代码时,您会知道打包的地方,因此您可以直接编写代码,也可以通过宏/定义get16或put8的样式来编写代码。这些函数应该只有一个参数,因此使用它们的代码空间成本为零,性能以分支上的管道刷新的形式出现,具体取决于您的核心风格。我不知道的是,这50或100条put和get函数指令会破坏您的代码大小预算吗?如果是这样,我想知道您是否应该使用C。特别是gcc。

如果大小至关重要,则可能要使用thumb而不是arm;如果有,则可能要使用thumb2。

我看不到如何让编译器为您完成任务,需要使用一些特定于编译器的编译指示,如果存在的话,它可能很少使用或出现故障。

您使用的是什么核心?我最近一直在使用axi总线在arm 11家族中工作,而arm在将ldrs,ldrbs,ldrhs等序列转换为单独的32或64位读取方面确实做得很好(是的,一些单独的指令可能会变成一个内存周期)。您可能只需要根据内核的功能来定制代码,这取决于内核以及该 ARM 到nvram内存接口(interface)的位置。但是,这将需要做很多模拟操作,我只通过查看总线来了解这一点,而不是从任何arm文档中了解。

关于将打包的数据与对齐的内存访问相结合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4074049/

相关文章:

c - 使用 ESP32 的 S7735R LCD 字体

c - Lua/C绑定(bind),从lua绑定(bind)

c - GCC 自动矢量化对运行时没有影响,即使假定为 "profitable"

Android ARMv6/v7 和 VFP/NEON

linux - 如何使用 gdbserver 进行远程调试?

c - C中的函数调用需要多少条机器指令?

c - 这个指定约束的含义是什么

c++ - Gsoap 文件已留在@

c - 为什么 gcc 垃圾回收对于已初始化全局变量和未初始化全局变量的行为不同?

openssl - 在 RTOS 环境中基于 ARM Cortex M4 的 STM32F4 Controller 构建 OpenSSL 库