强制 GCC 访问带有单词的结构

Posted

技术标签:

【中文标题】强制 GCC 访问带有单词的结构【英文标题】:Force GCC to access structs with words 【发布时间】:2017-02-11 02:11:31 【问题描述】:

在 ARM 处理器 (HT32F1655) 上,寄存器的特定部分需要字访问。来自用户手册:

请注意,AHB 总线中的所有外设寄存器仅支持字访问。

但 gcc 正在打包结构上生成一些 ldrb(加载字节)和 strb(存储字节)指令。结构看起来像这样:

typedef union 
    struct 
        uint32_t CKOUTSRC    : 3;    //!< CKOUT Clock Source Selection
        uint32_t             : 5;
        uint32_t PLLSRC      : 1;    //!< PLL Clock Source Selection
        uint32_t             : 2;
        uint32_t CKREFPRE    : 5;    //!< CK_REF Clock Prescaler Selection
        uint32_t             : 4;
        uint32_t URPRE       : 2;    //!< USART Clock Prescaler Selection
        uint32_t USBPRE      : 2;    //!< USB Clock Prescaler Selection
        uint32_t             : 5;
        uint32_t LPMOD       : 3;    //!< Lower Power Mode Status
     __attribute__((packed)) __attribute__ ((aligned(4)));
    uint32_t word;
 reg;

示例用法:

(*(volatile uint32_t*)0x40088000)->CKOUTSRC = 1;

产生类似于:

 ldrb r2, [r1]
 orr r2, r2, #1
 strb r2, [r1]

当我需要时:

 ldr r2, [r1]
 orr r2, r2, #1
 str r2, [r1]

有没有办法强制 gcc 只生成访问整个单词的指令?一些选项 (-mno-unaligned-access) 使 gcc 生成字访问,但仅当字节不是 4 字对齐时。

有一个 -mslow-bytes 应该做正确的事情,但是 arm-none-eabi-gcc 似乎不存在该选项。

理想情况下,有一种方法可以仅在受影响的结构上强制执行此操作。

请不要回答“不要使用位域”。我知道缺点,但我有能力控制使用的编译器,所以我不担心可移植性。

【问题讨论】:

发布“使打包的位图覆盖到设备寄存器上”的代码会使这变得更好、更清晰。 只需决定一组好的函数来提供您需要的访问权限,并在需要时使用内联汇编编写代码。 @DavidSchwartz 有大量这些寄存器需要映射。映射结构工作得很好,除了这个问题,所以我认为在转向不同的解决方案之前尝试让它完全工作是值得的。 @Shade 很好奇,如果你去掉“packed”属性会发生什么? godbolt.org/g/Yz85co 是一个没有表现出您报告的行为的示例。最好有命令行来查看-fstrict-volatile-bitfields 是否为您的案例代码生成问题提供了解决方案。 【参考方案1】:

您正在寻找的是 GCC 的 -fstrict-volatile-bitfields 选项:

如果访问 volatile 位字段(或其他结构字段,尽管编译器通常支持这些类型)应该使用字段类型宽度的单一访问,如果可能的话,应该使用自然对齐方式.例如,具有内存映射外设寄存器的目标可能要求所有此类访问为 16 位宽;使用此标志,您可以将所有外围位域声明为 unsigned short(假设这些目标上的 short 为 16 位)以强制 GCC 使用 16 位访问,而不是更有效的 32 位访问。

如果禁用此选项,编译器将使用最有效的指令。在前面的示例中,这可能是一个 32 位加载指令,即使它访问的字节不包含位字段的任何部分,或者与正在更新的寄存器无关的内存映射寄存器。

在某些情况下,例如将打包属性应用于结构字段时,可能无法通过针对目标计算机正确对齐的单次读取或写入来访问该字段。在这种情况下,GCC 会退回到生成多个访问,而不是生成会在运行时出错或截断结果的代码。

注意:由于 C/C++11 内存模型的限制,不允许写访问触及非位域成员。因此建议将字段类型的所有位定义为位字段成员。

此选项的默认值由目标处理器的应用程序二进制接口确定。

同时使用volatile 关键字。见:https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html

【讨论】:

这适用于位域结构。我对硬件寄存器的非位域结构有同样的问题。我必须定义一个内联 asm 宏来解决问题:#define WRITE32(_reg, _val) asm("str %0, [%1]" : : "r" (_val), "r" (&amp;_reg)); @robsn:那么您没有使用 volatile,这对于内存映射的硬件寄存器是绝对必需的。 我做到了。这是一个typedef volatile struct __attribute__((packed)) uint32_t registers here stuff; 和一个static stuff * regs;,我使用宏#define WRITE32(_reg, _val) (*(volatile uint32_t *)&amp;_reg = _val) 访问了它们。这不起作用,因为它展开到一堆strbs。但仅限于某些地方。内联汇编始终有效。我不使用位域。 @robsn:可能是打包的人把它弄坏了。打包几乎总是错误的。还有为什么在宏中强制转换(nop,但如果不是 nop,则很危险)指针类型? @robsn:Packed+aligned 可能会解决问题,但我怀疑您在开始使用 packed 时是错误的,并且可以/应该摆脱所有属性。 Packed 几乎总是一个错误,而您需要将成员作为对齐的全字单元访问这一事实更强烈地表明这是一个错误。【参考方案2】:

这正是 volatile 关键字的设计目的。

【讨论】:

问题是 gcc 在 WORD-addressable-only 内存上生成 BYTE 访问指令。 volatile 关键字是为了告诉编译器一些内存可能被外部修改,这使得一些优化变得不可能。 @Chaos 不仅如此。

以上是关于强制 GCC 访问带有单词的结构的主要内容,如果未能解决你的问题,请参考以下文章

是否可以强制 GCC 在 .rodata 中填充字符串常量

GCC/GProf - 以编程方式访问线程的当前函数/堆栈跟踪

强制访问者默认使用 SSL - Apache2

nginx如何配置访问时域名中不带某字符就强制重定向

尝试访问时带有结构的 C mq_receive() 会导致段错误

HTTPS与强制门户