我在大学的实时系统类(class)中使用 ARM 微 Controller 。在我目前从事的项目中,我正在实现 vector 场直方图 (VFH) 算法。
问题是:我需要在线程之间进行通信;更具体地说,我想要一个从测距仪获取传感器数据的线程,对其进行必要的转换并将它们存放在队列中。他们,另一个线程必须得到这个数据并处理它等等。
目前,我正在使用它的一个更简单的版本 - 一个线程从 ADC (SensorAcquisitionHandler) 获取数据,另一个线程将前 5 项(最多)的平均值输出到显示器 (ControlSignalHandler)。
/* Queue used to store data from the rangefinder sensors. */
static unsigned int Sensors[100];
static int SensorsHead = 0;
static int SensorsTail = 0;
void SensorAcquisitionHandler(void) {
/* Clear the interrupt. */
ADCIntClear(ADC0_BASE, 1);
int i; /* Index for the measurements buffer. */
/* There are only 3 rangefinders used. */
if (ADCSequenceDataGet(ADC0_BASE, 1, rangeBuffer) == 3) {
/* Put rangeBuffer's data into SensorDataQueue. */
/* Also, when using SensorDataQueue, must put what's the direction of
the corresponding range measurement. */
/* Critical section ahead!!! Turn off interrupts!!! */
IntMasterDisable();
/* Temporarily using the simple FIFO... */
for (i = 0; i < 3; ++i) {
if (SensorsHead < 100) {
Sensors[SensorsHead] = rangeBuffer[i];
SensorsHead++;
}
}
/* All is fine, turn on interrupts. */
IntMasterEnable();
}
}
void ControlSignalHandler(void) {
/* Clear the timer interrupt. */
TimerIntClear(TIMER0_BASE, TIMER_TIMA_TIMEOUT);
unsigned char i; /* Index for the measurements buffer. */
unsigned long average = 0;
char buffer[20];
/* Average first n (n <= 5) elements from Sensors queue. */
for (i = 0; i < 5 && SensorsTail < SensorsHead; ++i) {
average += Sensors[SensorsTail];
SensorsTail++;
}
IntMasterDisable();
average /= i;
sprintf(buffer, "%d ", average);
average = 0;
if (SensorsTail >= SensorsHead) {
SensorsTail = 0;
SensorsHead = 0;
}
Display96x16x1StringDraw(buffer, 0, 0);
IntMasterEnable();
}
结果在一段时间内相对稳定,但在随机时间间隔内会变得非常高(结果几乎一直是 ~330)。此外,当我在“非常高值(value)”的时刻使用符号调试器时,索引 SensorTail 和 SensorHead 可以达到 300+(队列是一个 100 元素的数组)。
这听起来像是某种溢出,但我无法想象它是如何发生的。有人可以帮我找到吗?
我知道问题的答案是“使用线程安全队列”,但我想了解竞争条件是如何发生的,索引是如何变得困惑等等。谢谢!
最佳答案
您可以通过使用无锁的单读取器单写入器 FIFO 来避免头指针和尾指针上的竞争条件——其中头指针只在一个线程(或在您的情况下是 ISR)中写入,而尾部写在另一个。这意味着您在每个 ISR 中执行缓冲区回绕测试。
如果您这样做并在每个 ISR 结束时立即重置您的中断源,您应该根本不需要任何锁定 - 在您这样做时全局禁用中断是非常不礼貌的。目前你持有锁的时间很长。
您需要重写 FIFO 实现的另一个原因是:
for (i = 0; i < 3; ++i) {
if (SensorsHead < 100) {
由于您一次添加 3 个读数,因此您最终将使用 SensorsHead==99
输入 SensorAcquisitionHandler()
- 这保证您将抛出 2 个读数离开。
类似地:
/* Average first n (n <= 5) elements from Sensors queue. */
for (i = 0; i < 5 && SensorsTail < SensorsHead; ++i) {
average += Sensors[SensorsTail];
SensorsTail++;
}
在某些情况下将对少于 5 个值执行计算。
根据您使用的 ARM 部件,没有硬件差异。计算二次幂值的平均值要便宜得多,因为它是单周期逻辑移位。
最后,我想 Display96x16x1StringDraw(buffer, 0, 0);
是一个特别昂贵的操作,而且它也可能是线程安全的。 IO 在 ISR 中总是被严格禁止。
您可能希望在计时器线程和处理输出的非中断上下文之间有另一个队列。
关于c - 这两个中断服务程序中的竞争条件是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13078394/