`memcpy((void*)dest,src,n)``volatile`数组是否安全?

Cac*_*ito 9 c casting interrupt volatile memcpy

我有一个用于UART的缓冲区,它以这种方式声明:

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;
Run Code Online (Sandbox Code Playgroud)

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

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);
}
Run Code Online (Sandbox Code Playgroud)

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

抛弃volatile这种方式是否安全,以便我可以使用memcpy()或者我应该将这样的函数称为memcpy_v()安全吗?:

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;
}
Run Code Online (Sandbox Code Playgroud)

编辑:

如果我需要那些新函数,因为我知道没有人会同时修改数组,是否有意义restrict(可能)帮助编译器优化(如果可以)?可能就是这样(如果我错了,请纠正我):

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;
}
Run Code Online (Sandbox Code Playgroud)

chu*_*ica 7

memcpy((void *)dest, src, n)一个volatile阵列安全吗?

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

如果代码想要memcpy() volatile记忆,请编写辅助函数。

OP 的代码restrict放错了地方。建议

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;
}
Run Code Online (Sandbox Code Playgroud)

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


然而 OP 使用volatile struct Eusart eusart;不正确。访问eusart需要volatile不提供的保护。

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

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


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

while (eusart.tx.msg_len)
    ;
Run Code Online (Sandbox Code Playgroud)

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

while (eusart.tx.msg_len);也遭受无限循环的困扰。如果传输因除空以外的其他原因停止,while 循环永远不会退出。

建议在检查或更改eusart.tx.msg_len, eusart.tx.msg_posn. 检查您的编译器支持atomic

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;
}
Run Code Online (Sandbox Code Playgroud)

一般通信代码思路:

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

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

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