我有一个用于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
的用户(例如我在实现RX缓冲区时)知道该怎么做,仍然应该回答这个问题。
编辑(相关)(Jul/19):
volatile vs memory barrier for interrupts
基本上说,volatile
不是必需的,因此这个问题就不存在了。
volatile
可以使对象线程安全?因为在大多数平台上,这并不是真的。 - David Schwartzvolatile
,而是因为只有一个线程,并且在我开始写之前检查中断是否被禁用,在此之后启用。因此,没有人同时搞砸的可能性。 - alx - recommends codidactvolatile
? - curiousguyvolatile
限定符的变量,则会调用未定义的行为。因此,即使不太可能实际上会引起问题,您对“普通”的memcpy()
的使用也是可疑的。 - Jonathan Leffler