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

Eri*_*ell 6 c gcc arm memory-management memory-alignment

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

我有一个嵌入式系统,主内存非常少,电池支持的nvram数量更少.我将校验和配置数据存储在nvram中,以便在启动时我可以验证校验和并继续前一次运行或在校验和无效时开始新的运行.在运行期间,我在此配置数据中更新各种大小的各种字段(并且可以使校验和无效,直到稍后重新计算).

所有这些都在物理地址空间中运行 - 正常的sram映射在一个位置,nvram映射到另一个位置.这是擦除 - 所有对nvram的访问必须以32位字进行; 不允许字节或半字访问(虽然它在主存中显然很好).

因此,我可以a)将所有配置数据的工作副本存储在主存储器中,并在重新计算校验和时将其存储到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;
}
Run Code Online (Sandbox Code Playgroud)

您可以想象不同的线程(每个线程执行Foo,Bar和Baz函数)根据需要更新自己的结构,并在某些时候进行同步以声明重新计算校验和并进入休眠状态的时间.

old*_*mer 2

避免位域 众所周知,位域是 C 语言的一个问题,不可靠、不可移植、在实现中随时可能发生变化。无论如何也不会帮助你解决这个问题。

联合体也会出现在我的脑海中,但是我已经被纠正了足够多次,所以你不能根据 C 标准使用联合体来改变类型。尽管正如我对另一张海报的假设,我还没有看到使用联合来更改类型不起作用的情况。位域不断损坏,联合内存共享损坏,到目前为止没有痛苦。工会不会为你节省任何公羊,所以在这里不起作用。

你为什么要让编译器来做这项工作?您需要在编译时拥有某种链接器类型脚本,指示编译器对某些地址空间使用掩码、移位、读取-修改-写入进行 32 位访问,而对于其他地址空间则使用更自然的字、半字和字节访问。我还没有听说过 gcc 或 C 语言在语法、编译器脚本或某种定义文件中具有这样的控制。如果它确实存在,那么它的使用还不够广泛,不够可靠,我预计会出现编译器错误并避免它。我只是没有看到编译器这样做,当然不是以结构体的方式。

对于读取,您可能会很幸运,这在很大程度上取决于硬件人员。这个 nvram 内存接口在哪里,是在你们公司制造的芯片内部,还是其他公司制造的,在芯片的边缘等等?像您部分描述的那样的限制可能意味着区分访问大小或字节通道的控制信号可能会被忽略。因此,ldrb 可能会将 nvram 视为 32 位读取,而 Arm 将抓取正确的字节通道,因为它认为这是 8 位读取。我会做一些实验来验证这一点,有不止一个手臂内存总线,每个都有许多不同类型的传输。如果您有能力了解手臂真正在做什么,也许可以与硬件人员交谈或进行一些 HDL 模拟。如果您不能采用此快捷方式,则无论您如何让编译器执行此操作,读取都将是具有可能掩码和移位的 ldr。

字大小以外的写入必须是读-修改-写。ldr、bic、shift 或 str。不管是谁做的,你还是编译器。

你自己做吧,我看不出编译器会如何为你做这件事。包括 gcc 在内的编译器很难执行您似乎认为正在告诉它的特定访问:

*(易失性无符号整数*)(SOME_ALIGNED_ADDRESS)=some_value;

我的语法可能是错误的,因为我几年前就放弃了它,但它并不总是产生一个 unsigned int 大小的存储,并且当编译器不想时,它不会。如果它不能可靠地做到这一点,你怎么能期望它为这个变量或结构创建一种风格的加载和存储,并为该变量或结构创建另一种风格呢?

因此,如果您有需要编译器生成的特定指令,您将失败,您必须使用汇编程序,就这样。特别是 ldm、ldrd、ldr、ldrh、ldrb、strd、str、strh、strb 和 stm。

我不知道你有多少 nvram,但在我看来,解决你的问题的方法是将 nvram 中的所有内容都设置为 32 位大小。您会消耗一些额外的周期来执行校验和,但您的代码空间和(易失性)RAM 使用量是最低的。需要的组装非常非常少(如果您对此感到满意,则无需组装)。

如果您担心太多优化,我还建议尝试其他编译器。至少尝试一下 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。特别是海湾合作委员会。

如果尺寸很重要,您可能想使用拇指而不是手臂,如果有的话,您可能想使用拇指2。

我不明白你如何让编译器为你做这件事,需要一些编译器特定的编译指示,如果存在的话,它可能很少使用并且有错误。

你用的是什么核心?我最近一直在使用 axi 总线处理 arm 11 系列中的一些东西,arm 在将 ldrs、ldrbs、ldrhs 等序列转换为单独的 32 或 64 位读取方面做得非常好(是的,一些单独的指令可能会变成一个内存周期)。您可能只需根据内核的功能定制代码即可,具体取决于内核以及该臂到 nvram 内存接口的位置。不过,我必须为此做很多模拟,我只是通过查看总线而不是从任何 Arm 文档中知道这一点。