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

时间:2010-11-01 23:51:57

标签: 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;
}

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

4 个答案:

答案 0 :(得分:2)

答案 1 :(得分:1)

最简单的方法是使用联合。

typedef union something {
    struct useful {
        uint8_t one;
        uint8_t two;
    };
    struct useless {
        uint32_t size_force[1];
    };
} something;
void something_memcpy(something* main_memory, something* memory_in_nvram) {
    for(int i = 0; i < sizeof(main_memory->useless.size_force); i++) {
        memory_in_nvram->useless.size_force[i] = main_memory->useless.size_force[i];
    }
}

这只是一个例子 - 您可以在编译时编写一些算法来自动确定大小。根据无用的成员从NVRam读取和写入,但总是根据“真正的”有用成员在主存储器中访问它。这应该强制编译器一次读取和写入32位(无用结构中的数组中每个32位),但仍然允许您轻松地,类型安全地访问真实数据成员。

答案 2 :(得分:1)

由于很难知道编译器可能对位域(有时甚至是并集​​)做了什么,为了安全起见,我创建了一些函数,这些函数只使用对齐的读/写从任意偏移量中获取/设置特定大小的数据。

类似以下内容(未经测试 - 甚至未编译)代码:

uint8_t nvram_get_u8( uint8_t const* p)
{
    uint32_t const* p32 = ((uintptr_t) p) & (~0x03);    // get a 32-bit aligned pointer
    int bit_offset = (((uintptr_t) p) & 0x03) * 8;      // get the offset of the byte 
                                                        //      we're interested in

    uint8_t val = ((*p32) >> bit_offset) & 0xff;

    return val;
}


void nvram_set_u8( uint8_t* p, uint8_t val)
{
    uint32_t* p32 = ((uintptr_t) p) & (~0x03);  // get a 32-bit aligned pointer
    int offset = (((uintptr_t) p) & 0x03) * 8;  // get the offset of the byte 
                                                //      we're interested in

    uint32_t tmp = *p32;

    tmp &= ~(((uint32_t) 0xff) << bit_offset);  // clear the byte we're writing
    tmp |= val << bit_offset;                   // and 'or' in the new data

    *p32 = tmp;

    return;
}

现在你可以读/写类似myBar.evenSmaller的东西(假设链接器/加载器已经布置了myBar,这样它就在NVRAM地址空间中),如下所示:

uint8_t evenSmaller = nvram_get_u8( &myBar.evenSmaller);

nvram_set_u8( &myBar.evenSmaller, 0x5a);

当然,处理较大数据类型的函数可能更复杂,因为它们可能跨越32位边界(如果要打包结构以避免填充占用的空间)。如果你对速度不感兴趣,他们可以建立在上面一次读/写单个字节的函数上,以帮助保持这些函数的简单。

在任何情况下,如果您有多个线程/任务同时读取NVRAM,则需要同步访问以避免非原子写入被破坏或导致读取损坏。

答案 3 :(得分:0)

如果你把所有东西都变成了一个位域,你可能会这样做:

uint32_t something;
uint32_t somethingSmaller:16;
uint32_t evenSmaller:8;
uint32_t pad:8;  // not strictly necessary but will help with your sanity

但是你的编译器可能会超出你的想象力。您必须检查生成的程序集。