8

我正在开发一个嵌入式项目(PowerPC 目标,Freescale Metrowerks Codewarrior 编译器),其中寄存器是内存映射的,并在漂亮的位域中定义,以便轻松处理各个位标志。

目前,我们正在使用此功能来清除中断标志和控制数据传输。虽然我还没有发现任何错误,但我很好奇这是否安全。有什么方法可以安全地使用位域,还是我需要将每个位域包装在 DISABLE_INTERRUPTS ... ENABLE_INTERRUPTS 中?

澄清一下:微提供的标头具有以下字段

union {
        vuint16_t R;
        struct {
            vuint16_t MTM:1;        /* message buffer transmission mode */
            vuint16_t CHNLA:1;      /* channel assignement */
            vuint16_t CHNLB:1;      /* channel assignement */
            vuint16_t CCFE:1;       /* cycle counter filter enable */
            vuint16_t CCFMSK:6;     /* cycle counter filter mask */
            vuint16_t CCFVAL:6;     /* cycle counter filter value */
        } B;
    } MBCCFR;

我假设在位域中设置一个位不是原子的。这是一个正确的假设吗?编译器实际上为位域生成什么样的代码?使用 R(原始)字段自己执行掩码可能更容易记住操作不是原子的(很容易忘记像这样的赋值CAN_A.IMASK1.B.BUF00M = 1不是原子的)。

感谢您的建议。

4

6 回答 6

3

原子性取决于目标和编译器。例如,AVR-GCC 会尝试检测位访问并在可能的情况下发出位设置或清除指令。检查汇编器输出以确保...

编辑:这是直接从马口中获得 PowerPC 原子指令的资源:

http://www.ibm.com/developerworks/library/pa-atom/

于 2010-07-21T13:49:49.727 回答
3

假设设置位域不是原子的是正确的。C 标准对于如何实现位域并不是特别清楚,并且各种编译器对它们采取了不同的方式。

如果你真的只关心你的目标架构和编译器,反汇编一些目标代码。

通常,您的代码将实现所需的结果,但效率远低于使用宏和移位的代码。也就是说,如果您不关心这里的性能,使用您的位字段可能更具可读性。

如果您担心未来的编码人员(包括您自己)会感到困惑,您总是可以为原子位编写一个 setter 包装函数。

于 2010-07-21T13:50:35.637 回答
3

是的,您的假设是正确的,因为您可能不会假设原子性。在特定平台上,您可能会额外获得它,但无论如何您都不能依赖它。

基本上,编译器会为您执行屏蔽和操作。他也许可以利用极端案例或特殊说明。如果您对效率感兴趣,请查看编译器生成的汇编器,通常它很有指导意义。根据经验,我会说现代编译器生成的代码与中等编程工作一样高效。对您的特定编译器进行真正深入的操作可能会为您带来一些周期。

于 2010-07-21T13:56:58.307 回答
3

我认为使用位域来模拟硬件寄存器不是一个好主意。

编译器如何处理位域的很多内容是实现定义的(包括如何处理跨越字节或字边界的字段、字节顺序问题,以及究竟如何实现获取、设置和清除位)。请参阅C/C++:强制位字段顺序和对齐

要验证寄存器访问是否按照您可能期望或需要的方式处理,您必须仔细研究编译器文档和/或查看发出的代码。我想如果微处理器工具集提供的头文件使用它们,你可以假设我的大部分问题都得到了解决。但是,我猜原子访问不一定......

我认为最好使用函数(或宏,如果必须的话)来处理这些类型的硬件寄存器的位级访问,这些函数使用您需要的位掩码执行显式读/修改/写操作,如果这是您的处理器需要的话。

这些功能可以针对支持原子位级访问的架构进行修改(例如 ARM Cortex M3 的“位带”寻址)。我不知道 PowerPC 是否支持这样的东西——M3 是我处理过的唯一一个以一般方式支持它的处理器。甚至 M3 的位带也支持 1 位访问;如果你正在处理一个 6 位宽的字段,你必须回到读/修改/写场景。

于 2010-07-21T17:21:56.633 回答
0

位域操作是否是原子的完全取决于架构和编译器。我的个人经验告诉我们:如果不需要,不要使用位域。

于 2010-07-21T13:51:16.977 回答
0

我很确定在 powerpc 上这不是原子的,但是如果您的目标是单核系统,那么您可以:

void update_reg_from_isr(unsigned * reg_addr, unsigned set, unsigned clear, unsigned toggle) {
   unsigned reg = *reg_addr;
   reg |= set;
   reg &= ~clear;
   reg ^= toggle;
   *reg_addr = reg;
}

void update_reg(unsigned * reg_addr, unsigned set, unsigned clear, unsigned toggle) {
   interrupts_block();
   update_reg_from_isr(reg_addr, set, clear, toggle);
   interrupts_enable();
}

我不记得 powerpc 的中断处理程序是否可中断,但如果是,那么您应该始终使用第二个版本。

如果您的目标是多处理器系统,那么您应该制作锁(自旋锁,它禁用本地处理器上的中断,然后等待任何其他处理器完成锁)以保护对硬件寄存器等内容的访问,并在之前获取所需的锁您访问寄存器,然后在完成更新寄存器(或寄存器)后立即释放锁定。

我读过一次如何在 powerpc 中实现锁——它涉及告诉处理器在你执行一些操作时监视内存总线的某个地址,然后在这些操作结束时检查是否已写入监视地址由另一个核心。如果没有,那么您的操作是成功的;如果有,那么您必须重做该操作。这是为编译器、库和操作系统开发人员编写的文档。我不记得我在哪里找到它(可能在 IBM.com 上的某个地方),但稍微打猎一下就可以了。它可能还包含有关如何进行原子位旋转的信息。

于 2010-07-21T14:32:06.977 回答