带有 `volatile` 数组的 `memcpy((void *)dest, src, n)` 是否安全? [英] Is `memcpy((void *)dest, src, n)` with a `volatile` array safe?

查看:41
本文介绍了带有 `volatile` 数组的 `memcpy((void *)dest, src, n)` 是否安全?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个用于 UART 的缓冲区,声明如下:

I have a buffer that I use for UART, which is declared this way:

union   Eusart_Buff {
    uint8_t     b8[16];
    uint16_t    b9[16];
};

struct  Eusart_Msg {
    uint8_t             msg_posn;
    uint8_t             msg_len;
    union Eusart_Buff   buff;
};

struct  Eusart {
    struct Eusart_Msg   tx;
    struct Eusart_Msg   rx;
};

extern  volatile    struct Eusart   eusart;

这是填充缓冲区的函数(将使用中断发送):

And here is the function that fills the buffer (which will be sent using interrupts):

void    eusart_msg_transmit (uint8_t n, void *msg)
{

    if (!n)
        return;

    /*
     * The end of the previous transmission will reset
     * eusart.tx.msg_len (i.e. ISR is off)
     */
    while (eusart.tx.msg_len)
        ;

    if (data_9b) {
        memcpy((void *)eusart.tx.buff.b9, msg,
                sizeof(eusart.tx.buff.b9[0]) * n);
    } else {
        memcpy((void *)eusart.tx.buff.b8, msg,
                sizeof(eusart.tx.buff.b8[0]) * n);
    }
    eusart.tx.msg_len   = n;
    eusart.tx.msg_posn  = 0;

    reg_PIE1_TXIE_write(true);
}

在使用 memcpy() 的那一刻,我知道没有其他人会使用缓冲区(原子),因为 while 循环确保最后一条消息已发送,因此中断被禁用.

At the moment of using memcpy(), I know no one else is going to use the buffer (atomic), because the while loop ensures that the last message has been sent, and therefore the interrupt is disabled.

以这种方式丢弃 volatile 是否安全,以便我能够使用 memcpy() 或者我应该创建一个名为 memcpy_v() 的函数 像这样安全吗?:

Is it safe to cast away volatile this way so that I am able to use memcpy() or should I make a function maybe called memcpy_v() like these to be safe?:

void *memcpy_vin(void *dest, const volatile void *src, size_t n)
{
    const volatile char *src_c  = (const volatile char *)src;
    char *dest_c                = (char *)dest;

    for (size_t i = 0; i < n; i++)
        dest_c[i]   = src_c[i];

    return  dest;
}

volatile void *memcpy_vout(volatile void *dest, const void *src, size_t n)
{
    const char *src_c       = (const char *)src;
    volatile char *dest_c   = (volatile char *)dest;

    for (size_t i = 0; i < n; i++)
        dest_c[i]   = src_c[i];

    return  dest;
}

volatile void *memcpy_v(volatile void *dest, const volatile void *src, size_t n)
{
    const volatile char *src_c  = (const volatile char *)src;
    volatile char *dest_c       = (volatile char *)dest;

    for (size_t i = 0; i < n; i++)
        dest_c[i]   = src_c[i];

    return  dest;
}

如果我需要这些新功能,鉴于我知道没有人会同时修改数组,使用 restrict 来(也许)帮助编译器优化(如果可以的话)有意义吗?可能是这样(如果我错了,请纠正我):

If I need those new functions, given that I know no one is going to modify the array at the same time, would it make sense to use restrict to (maybe) help the compiler optimize (if it can)? Possibly this way (correct me if I'm wrong):

volatile void *memcpy_v(restrict volatile void *dest,
                        const restrict volatile void *src,
                        size_t n)
{
    const restrict volatile char *src_c = src;
    restrict volatile char *dest_c      = dest;

    for (size_t i = 0; i < n; i++)
        dest_c[i]   = src_c[i];

    return  dest;
}

编辑 2(添加上下文):

void    eusart_end_transmission (void)
{

    reg_PIE1_TXIE_write(false); /* TXIE is TX interrupt enable */
    eusart.tx.msg_len   = 0;
    eusart.tx.msg_posn  = 0;
}

void    eusart_tx_send_next_c   (void)
{
    uint16_t    tmp;

    if (data_9b) {
        tmp     = eusart.tx.buff.b9[eusart.tx.msg_posn++];
        reg_TXSTA_TX9D_write(tmp >> 8);
        TXREG   = tmp;
    } else {
        TXREG   = eusart.tx.buff.b8[eusart.tx.msg_posn++];
    }
}

void __interrupt()  isr(void)
{

    if (reg_PIR1_TXIF_read()) {
        if (eusart.tx.msg_posn >= eusart.tx.msg_len)
            eusart_end_transmission();
        else
            eusart_tx_send_next_c();
    }
}

虽然 volatile 可能不需要 需要的(我在另一个问题中问过:对于仅在 ISR 中读取的变量的 volatile?),这个问题仍然应该在假设 volatile 是必需的,以便未来真正需要 volatile 的用户(例如我在实现 RX 缓冲区时)可以知道该怎么做.

Although volatile may not be is needed (I asked it in another question: volatile for variable that is only read in ISR?), this question still should be answered in the assumption that volatile is needed so that future users that really need volatile (for example me when I implement the RX buffer), can know what to do.

编辑(相关)(19 年 7 月):

EDIT (Related) (Jul/19):

中断的易失性 vs 内存屏障

基本上说不需要volatile,因此这个问题就消失了.

Basically says that volatile is not needed, and therefore this issue disappears.

推荐答案

memcpy((void *)dest, src, n)volatile 数组安全吗?

没有.在一般情况下,memcpy() 未指定为与易失性内存一起正常工作.
OP 的案例看起来可以抛弃volatile,但发布的代码不足以确定.

No. In the general case, memcpy() is not specified to work correctly with volatile memory.
OP's case looks OK to cast away volatile, yet posted code is insufficient to be certain.

如果代码想要memcpy() volatile 内存,请编写辅助函数.

If code wants to memcpy() volatile memory, write the helper function.

OP 的代码在错误的地方有 restrict.建议

OP's code has restrict in the wrong place. Suggest

volatile void *memcpy_v(volatile void *restrict dest,
            const volatile void *restrict src, size_t n) {
    const volatile unsigned char *src_c = src;
    volatile unsigned char *dest_c      = dest;

    while (n > 0) {
        n--;
        dest_c[n] = src_c[n];
    }
    return  dest;
}

编写自己的 memcpy_v() 的一个单一原因是编译器可以理解"/分析 memcpy() 并发出与预期非常不同的代码 -甚至优化它,如果编译器认为该副本需要.提醒自己编译器认为 memcpy() 操作的内存是非易失性的.

A singular reason for writing your own memcpy_v() is the a compiler can "understand"/analyze memcpy() and emit code that is very different than expected - even optimize it out, if the compiler thinks the copy is not needed. Remind oneself that the compiler thinks memcpy() manipulated memory is non-volatile.

然而 OP 错误地使用了 volatile struct Eusart eusart;.访问 eusart 需要 volatile 不提供的保护.

Yet OP is using volatile struct Eusart eusart; incorrectly. Access to eusart needs protection that volatile does not provide.

在 OP 的情况下,代码可以将 volatile 放在缓冲区,然后使用 memcpy() 就好了.

In OP's case, code can drop volatile on the buffers and then use memcpy() just fine.

剩下的问题在于 OP 如何使用 eusart 的代码不足.使用 volatile 并不能解决 OP 的问题.OP 确实断言我以原子方式写入",但没有发布 atomic 代码,这是不确定的.

A remaining issue is in the scant code of how OP is using eusart. Using volatile does not solve OP's problem there. OP's does assert "I write to it atomically,", yet without posted atomic code, that is not certain.

像下面这样的代码的好处是 eusart.tx.msg_lenvolatile,但这还不够.volatile 确保 .tx.msg_len 不被缓存,而是每次重新读取.

Code like the below benefits with eusart.tx.msg_len being volatile, yet that is not sufficient. volatile insures .tx.msg_len is not cached and instead re-reads each time.

while (eusart.tx.msg_len)
    ;

然而,.tx.msg_len 的读取并未指定为 atomic.当 .tx.msg_len == 256 和 ISR 触发时,递减 .tx.msg_len,读取 LSbyte(0 来自 256)和 MSbyte(0 来自 255),非 ISR 代码可能会看到 .tx.msg_len 为 0,而不是 255 或 256,从而在错误的时间结束循环..tx.msg_len 的访问需要指定为不可分割(原子),否则,偶尔代码会神秘失败.

Yet the read of .tx.msg_len is not specified as atomic. When .tx.msg_len == 256 and a ISR fires, decrementing .tx.msg_len, the read of the the LSbyte (0 from 256) and MSbyte (0 from 255), the non-ISR code may see .tx.msg_len as 0, not 255 nor 256, thus ending the loop at the wrong time. The access of .tx.msg_len needs to be specified as indivisible (atomic), else, once in a while code fails mysteriously.

while (eusart.tx.msg_len); 也有无限循环的问题.如果传输因某种原因而不是空而停止,while 循环永远不会退出.

while (eusart.tx.msg_len); also suffers from being an end-less loop. Should the transmission stop for some reason other than empty, the while loop never exits.

建议在检查或更改eusart.tx.msg_len、eusart.tx.msg_posn时阻止中断.检查编译器对 atomic

Recommend instead to block interrupts while inspecting or changing eusart.tx.msg_len, eusart.tx.msg_posn. Review your compilers support of atomic or

size_t tx_msg_len(void) {
  // pseudo-code
  interrupt_enable_state = get_state();
  disable_interrupts();
  size_t len = eusart.tx.msg_len;
  restore_state(interrupt_enable_state);
  return len;
}

<小时>

一般通信代码思路:


General communication code ideas:

  1. 当非 ISR 代码读取或写入 eusart 时,请确保 ISR 不会永远改变 eusart.

  1. While non-ISR code reads or writes eusart, make sure the ISR cannot ever change eusart.

不要在步骤 1 中长时间阻塞 ISR.

Do not block ISR for long in step #1.

不要假设底层 ISR() 会成功链接输入/输出而不会出现问题.顶级代码应该准备好在它停止时重新开始输出.

Do not assume underlying ISR() will chain input/output successfully without a hiccup. Top level code should be prepared to re-start output if it gets stalled.

这篇关于带有 `volatile` 数组的 `memcpy((void *)dest, src, n)` 是否安全?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆