Безопасен ли `memcpy ((void *) dest, src, n)` с массивом `volatile`? - PullRequest
0 голосов
/ 03 марта 2019

У меня есть буфер, который я использую для 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 может не быть необходимо необходимо (я задал это в другом вопросе: volatile для переменной, которая читается только в ISR? ) ,на этот вопрос все еще следует ответить в предположении, что volatile необходим, чтобы будущие пользователи, которым действительно нужен volatile (например, я, когда я реализую буфер RX), могли знать, что делать.


РЕДАКТИРОВАТЬ (Связано) (Jul / 19):

энергозависимый против памяти барьер для прерываний

В основном говорит, что volatile не требуется, и поэтому эта проблема исчезает.

Ответы [ 2 ]

0 голосов
/ 05 марта 2019

В стандарте отсутствуют какие-либо средства, с помощью которых программисты могут требовать, чтобы операции, которые обращаются к области памяти с помощью обычного указателя, были завершены до того, как будет выполнен конкретный volatile доступ к указателю, а также отсутствуют какие-либо средства обеспечения того, чтобы операции, которыеДоступ к области памяти с помощью обычного указателя не осуществляется до тех пор, пока не будет выполнен какой-либо конкретный volatile доступ к указателю.Поскольку семантика операций volatile определяется реализацией, авторы стандарта, возможно, ожидали, что разработчики компиляторов узнают, когда их клиентам может понадобиться такая семантика, и определят свое поведение в соответствии с этими потребностями.К сожалению, этого не произошло.

Для достижения требуемой семантики вам потребуется либо использовать "популярное расширение", такое как режим clang -fms-volatile, встроенную функцию компилятора, либо заменить memcpy с чем-то настолько ужасно неэффективным, чтобы затмить любые предполагаемые преимущества, которые компиляторы могли бы получить, не поддерживая такую ​​семантику.

0 голосов
/ 03 марта 2019

Является ли 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;
}

Единственная причина написания вашего собственного memcpy_v() в том, что компилятор может «понимать» / анализировать memcpy() и генерировать код, который сильно отличается от ожидаемого - даже оптимизировать его, еслиКомпилятор считает, что копия не нужна .Напомните себе, что компилятор считает, что memcpy() манипулируемая память является энергонезависимой.


И все же OP использует volatile struct Eusart eusart; неправильно.Доступ к eusart нуждается в защите, которую volatile не обеспечивает.

В случае OP код может сбросить volatile в буферах , а затем просто использовать memcpy().

Остается проблема в скудном коде того, как OP использует eusart.Использование volatile не решает проблему OP там.ОП действительно заявляет: «Я пишу в нее атомарно», но без отправленного atomic кода, что не является точным.


Код, подобный приведенному ниже, имеет преимущество eusart.tx.msg_len, равное volatile, но этонедостаточноvolatile гарантирует, что .tx.msg_len не кэшируется и вместо этого перечитывает каждый раз.

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 должен быть указан как неделимый (атомарный), иначе, время от времени код таинственно завершается сбоем.

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. Не блокируйте ISR долго в шаге № 1.

  3. Не предполагайте, что лежащий в основе ISR() будет успешно соединять ввод / вывод без икоты.Код верхнего уровня должен быть готов перезапустить вывод, если он остановился.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...