C 编译器对位域做什么?

发布于 2024-09-10 15:27:42 字数 937 浏览 3 评论 0 原文

我正在开发一个嵌入式项目(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 这样的赋值不是原子的)不是原子的)。

感谢您的建议。

I'm working on an embedded project (PowerPC target, Freescale Metrowerks Codewarrior compiler) where the registers are memory-mapped and defined in nice bitfields to make twiddling the individual bit flags easy.

At the moment, we are using this feature to clear interrupt flags and control data transfer. Although I haven't noticed any bugs yet, I was curious if this is safe. Is there some way to safely use bit fields, or do I need to wrap each in DISABLE_INTERRUPTS ... ENABLE_INTERRUPTS?

To clarify: the header supplied with the micro has fields like

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;

I assume setting a bit in a bitfield is not atomic. Is this a correct assumption? What kind of code does the compiler actually generate for bitfields? Performing the mask myself using the R (raw) field might make it easier to remember that the operation is not atomic (it is easy to forget that an assignment like CAN_A.IMASK1.B.BUF00M = 1 isn't atomic).

Your advice is appreciated.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

全部不再 2024-09-17 15:27:42

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

编辑:这是直接来自马口的 PowerPC 原子指令的资源:

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

Atomicity depends on the target and the compiler. AVR-GCC for example trys to detect bit access and emit bit set or clear instructions if possible. Check the assembler output to be sure ...

EDIT: Here is a resource for atomic instructions on PowerPC directly from the horse's mouth:

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

So尛奶瓶 2024-09-17 15:27:42

假设设置位域不是原子的,这是正确的。 C 标准对于如何实现位域并不是特别明确,并且不同的编译器对位域采取了不同的方法。

如果您真的只关心目标体系结构和编译器,请反汇编一些目标代码。

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

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

It is correct to assume that setting bitfields is not atomic. The C standard isn't particularly clear on how bitfields should be implemented and various compilers go various ways on them.

If you really only care about your target architecture and compiler, disassemble some object code.

Generally, your code will achieve the desired result but be much less efficient than code using macros and shifts. That said, it's probably more readable to use your bit fields if you don't care about performance here.

You could always write a setter wrapper function for the bits that is atomic, if you're concerned about future coders (including yourself) being confused.

能怎样 2024-09-17 15:27:42

是的,您的假设是正确的,从某种意义上说,您可能不假设原子性。在特定平台上,您可能会额外获得它,但在任何情况下都不能依赖它。

基本上,编译器会为您执行屏蔽等操作。他也许能够利用极端情况或特殊说明。如果您对效率感兴趣,请查看编译器用它生成的汇编程序,通常它非常有启发性。根据经验,我认为现代编译器生成的代码与中等编程工作一样高效。对您的特定编译器进行真正的深度调整也许可以为您赢得一些周期。

Yes, your assumption is correct, in the sense that you may not assume atomicity. On a specific platform you might get it as an extra, but you can't rely on it in any case.

Basically the compiler performs masking and things for you. He might be able to take advantage of corner cases or special instructions. If you are interested in efficiency look into the assembler that your compiler produces with that, usually it is quite instructive. As a rule of thumb I'd say that modern compilers produces code that is as efficient as medium programming effort would be. Real deep bit twiddeling for your specific compiler could perhaps gain you some cycles.

紙鸢 2024-09-17 15:27:42

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

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

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

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

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

I think that using bitfields to model hardware registers is not a good idea.

So much about how bitfields are handled by a compiler is implementation-defined (including how fields that span byte or word boundaries are handled, endianess issues, and exactly how getting, setting and clearing bits is implemented). See C/C++: Force Bit Field Order and Alignment

To verify that register accesses are being handled how you might expect or need them to be handled, you would have to carefully study the compiler docs and/or look at the emitted code. I suppose that if the headers supplied with the microprocessor toolset uses them you can be assume that most of my concerns are taken care of. However, I'd guess that atomic access isn't necessarily...

I think it's best to handle these type of bit-level accesses of hardware registers using functions (or macros, if you must) that perform explicit read/modify/write operations with the bit mask that you need, if that's what your processor requires.

Those functions could be modified for architectures that support atomic bit-level accesses (such as the ARM Cortex M3's "bit-banding" addressing). I don't know if the PowerPC supports anything like this - the M3 is the only processor I've dealt with that supports it in a general fashion. And even the M3's bit-banding supports 1-bit accesses; if you're dealing with a field that's 6-bits wide, you have to go back to the read/modify/write scenario.

枉心 2024-09-17 15:27:42

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

It totally depends on the architecture and compiler whether the bitfield operations are atomic or not. My personal experience tells: don't use bitfields if you don't have to.

南薇 2024-09-17 15:27:42

我很确定在 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 上的某个地方),但是稍微搜索一下就可以找到它。它可能还包含有关如何进行原子位旋转的信息。

I'm pretty sure that on powerpc this is not atomic, but if your target is a single core system then you can just:

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();
}

I don't remember if powerpc's interrupt handlers are interruptible, but if they are then you should just use the second version always.

If your target is a multiprocessor system then you should make locks (spinlocks, which disable interrupts on the local processor and then wait for any other processors to finish with the lock) that protect access to things like hardware registers, and acquire the needed locks before you access the register, and then release the locks immediately after you have finished updating the register (or registers).

I read once how to implement locks in powerpc -- it involved telling the processor to watch the memory bus for a certain address while you did some operations and then checking back at the end of those operations to see if the watch address had been written to by another core. If it hadn't then your operation was sucessful; if it had then you had to redo the operation. This was in a document written for compiler, library, and OS developers. I don't remember where I found it (probably somewhere on IBM.com) but a little hunting should turn it up. It probably also has info on how to do atomic bit twiddling.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文