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

标签 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;

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

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 循环确保了最后一条消息已发送,因此中断被禁用。

以这种方式丢弃 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;
}

编辑:

如果我需要那些新功能, 鉴于我知道没有人会同时修改数组,使用 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;
}

编辑 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 may not be is needed(我在另一个问题中问过:volatile for variable that is only read in ISR?) ,这个问题仍然应该在假设需要 volatile 的情况下回答,以便 future 真正需要 volatile 的用户(例如我实现 RX 缓冲区时) ,可以知道该怎么做。


编辑(相关)(7 月/19 日):

volatile vs memory barrier for interrupts

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

最佳答案

Is memcpy((void *)dest, src, n) with a volatile array safe?

没有。在一般情况下,未指定 memcpy() 与 volatile 内存一起正常工作。
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;
}

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


然而 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)
    ;

然而 .tx.msg_len 的读取未指定为 atomic。当 .tx.msg_len == 256 和 ISR 触发时,递减 .tx.msg_len,读取 LSbyte(256 中的 0)和 MSbyte(255 中的 0) ),非 ISR 代码可能会将 .tx.msg_len 视为 0,而不是 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;
}

通用通信代码思路:

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

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

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

关于c - `memcpy((void *)dest, src, n)` 和 `volatile` 数组安全吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54964154/

相关文章:

c++ - 验证C中数组的大小

c - udp 套接字 - 绑定(bind)和连接成功,但发送不起作用

C++ 错误 : no viable conversion from 'mapped_type' to 'int'

java - 需要设计建议以跳过对象类型转换

java - Java中如何判断一个Type是否实现了某个接口(interface)?

c - 如何在不按 RET 的情况下请求输入 - C、Linux 操作系统中的键盘中断

c - 识别干扰的可能性

c - C语言树数据结构教程

c - 为什么 libcurl 在清理调用后仍然留下可访问的 block ?

x86 - 是否可以设置在 cpu 写入特定地址时中断的中断?