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

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

问题描述

我有一个用于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循环可确保已发送最后一条消息,因此禁止了中断. /p>

这样安全地丢弃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 可能不需要 (我在另一个问题中问过:用于中断的易失性与内存屏障

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

解决方案

具有volatile阵列的memcpy((void *)dest, src, n)是否安全?

不.在一般情况下,未指定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;
}

编写自己的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)
    ;

.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()将成功打断输入/输出而不会打h.如果遇到停顿,应该准备顶层代码以重新启动输出.

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

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.

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

Edit:

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

Edit 2 (add context):

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

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.


EDIT (Related) (Jul/19):

volatile vs memory barrier for interrupts

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

解决方案

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

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.

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

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

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.


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

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

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.


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

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); also suffers from being an end-less loop. Should the transmission stop for some reason other than empty, the while loop never exits.

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. While non-ISR code reads or writes eusart, make sure the ISR cannot ever change eusart.

  2. Do not block ISR for long in step #1.

  3. 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天全站免登陆