【发布时间】:2019-07-24 15:19:53
【问题描述】:
我有一个用于 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 for variable that is only read in ISR?),这个仍然应该在需要volatile 的假设下回答问题,以便真正需要volatile 的未来用户(例如我在实现RX 缓冲区时)可以知道该怎么做。
编辑(相关)(7 月 19 日):
volatile vs memory barrier for interrupts
基本上说volatile 是不需要的,因此这个问题就消失了。
【问题讨论】:
-
您的平台是否指定
volatile使对象成为线程安全的?因为在大多数平台上,这不是真的。 -
它是线程安全的不是因为
volatile,而是因为只有一个线程,而且在我开始写之前检查中断是否被禁用,然后再启用。所以有人在同一时间胡闹的可能性为 0。 -
你还需要
volatile做什么? -
因为该变量用于普通代码和中断。只是在写它的那一刻,我证明没有其他人在使用它,但在任何其他时刻,该变量在主循环和中断之间共享。
-
我的理解是,严格来说,如果您通过非易失性指针访问具有
volatile限定符的变量,则会调用未定义的行为。因此,您对“普通”memcpy()的使用是可疑的,即使实际上不太可能造成麻烦。
标签: c casting interrupt volatile memcpy