【发布时间】:2017-11-21 15:52:09
【问题描述】:
在嵌入式 C 中,我正在尝试创建一种通用方法来安全地将值从 ISR(前台)传递到我的主循环(后台)。通过“通用”,我的意思是我不想暂停中断(因为这是编译器/CPU 特定的并且可能有副作用),只想用繁忙的标志等来做。对于这个特殊的机制,我不需要一个队列,我只想检索 ISR 报告的最新值。
所以我使用的模式是一个结构和一些对该结构进行操作的函数。问题当然是“写入”功能是基于 ISR 的,可以随时中断“读取”功能,我想消除数据损坏的可能性。该方法是一个双槽系统和几个繁忙标志。
它会起作用吗?和/或有更简单的方法吗? (请记住,这是嵌入式 C,我正在尝试通用/便携。)谢谢!
typedef struct
{
uint8_t busy;
int32_t valueA;
int32_t valueB;
uint8_t reading_from_A;
uint8_t last_wrote_to_B;
} sSafeI32_Fore2Back;
void SafeI32_InitFore2Back(sSafeI32_Fore2Back * si)
{
si->busy = 0;
si->valueA = 0;
si->valueB = 0;
si->reading_from_A = 0;
si->last_wrote_to_B = 1;
}
int32_t SafeI32_ReadFromBack(sSafeI32_Fore2Back * si)
{
int32_t rtn;
si->busy = 1;
if (si->last_wrote_to_B)
{
rtn = si->valueB;
}
else
{
si->reading_from_A = 1;
rtn = si->valueA;
si->reading_from_A = 0;
}
si->busy = 0;
return rtn;
}
void SafeI32_WriteFromFore(sSafeI32_Fore2Back * si, int32_t v)
{
if (si->busy == 0)
{
si->valueA = v;
si->last_wrote_to_B = 0;
}
else
{
if (si->reading_from_A)
{
si->valueB = v;
si->last_wrote_to_B = 1;
}
else
{
si->valueA = v;
si->last_wrote_to_B = 0;
}
}
}
【问题讨论】:
-
使用循环队列是很常见的。 ISR 在“保存”索引处加载数据,当“保存”索引不同时,主循环从“加载”索引中提取。ISR 读取“保存”索引,将其移动一轮,然后加载新数据索引,并且在安全完成所有操作后,将索引的新值存储回“保存”索引中。然后,在新数据完全可用之前,主循环永远不会看到“保存”索引的任何新值。指数应该是易变的。如果您有 RTOS,您可能会发出信号量,处理程序线程在该信号量上等待而不是轮询。
-
两件事:我认为包装队列本质上不是线程安全的,例如,如果它比 256 个插槽宽,那么您的索引大于一个字节,并且需要两次组装操作来读取或写入对他们来说(即它的非原子性的)。因此,您可能会在 中途 读取索引后对其进行修改,从而产生随机结果和不良行为。
-
其次,队列会产生与我在上面所做的稍有不同的行为。我想要来自 ISR 的 最新 值,不需要历史记录——如果还没有读取前一个值,我希望它被覆盖。
-
也感谢您的回复:)
-
只要遵循我上面描述的协议,在一个 ISR 和一个线程/主循环之间使用包装队列是安全的。索引会递增,并且可能会被 ISR 包装在一个临时的、自动的 var 中,并且在设置 volatile 'save' var 之前在这个新索引处加载数据,因此线程/main 在递增/测试/设置发生在自动变量中。没关系——我已经多次使用过这样的方案,只要最后完成“保存”的设置,就可以了。可能会因 a) 嵌套 ISR b) 多个线程等待而失败。
标签: c thread-safety embedded interrupt isr