4

我在大学的实时系统课程中使用 ARM 微控制器。在我目前正在进行的项目中,我正在实现矢量场直方图(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)。此外,当我在“非常高的价值”时刻使用符号调试器时,索引 SensorTail 和 SensorHead 可以达到 300+(队列是一个 100 元素的数组)。

这听起来像是某种溢出,但我无法想象它是如何发生的。有人可以帮我找到吗?

我知道问题的答案是“使用线程安全队列”,但我想了解这里的竞争条件是如何发生的,索引是如何搞砸的,等等。谢谢!

4

4 回答 4

3

通过清除中断,您允许它再次发生。想象一下,如果(例如)在ControlSignalHandler第一个循环的中间重新进入会发生什么,因为计时器设法超过了您的代码......

将整个函数(两者)包装在IntMasterDisable+IntMasterEnable中,并在禁用之后和启用之前清除中断。(我会在启用之前立即执行此操作。)

于 2012-10-25T23:16:46.883 回答
3

您可以通过使用无锁单读取器单写入器 FIFO 来避免头指针和尾指针的竞争条件 - 其中头指针仅写入一个线程(或在您的情况下为 ISR),而尾部写入另一个。这意味着您在每个 ISR 中执行缓冲区包装测试。

如果您这样做并在每个 ISR 结束时重置您的中断源,那么您根本不需要任何锁定- 全局禁用中断是非常不礼貌的。目前你持有的锁很长时间。

需要重写 FIFO 实现的另一个原因是:

    for (i = 0; i < 3; ++i) {
        if (SensorsHead < 100) {

由于您一次添加 3 个读数,因此您最终将输入SensorAcquisitionHandler()-SensorsHead==99这保证您将丢弃 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 中总是严格禁止的。您可能希望在计时器线程和处理输出的非中断上下文之间建立另一个队列。

于 2012-10-26T00:03:14.053 回答
2

您使用的是什么特定的处理器/微控制器?什么 RTOS(如果有)?

ARM 微控制器上的中断堆栈通常非常非常小。像这样的运行时例程snprintf()很容易需要数百个字节,并且可能会溢出一个小堆栈。即使不考虑堆栈空间,C 运行时函数在中断上下文中使用通常也不安全——您通常非常受限于可以从中断调用哪些函数。具体细节取决于您使用的实际 RTOS 和编译器工具链。

如果您违反这些约束,则很容易导致数据损坏。

于 2012-10-25T23:24:41.763 回答
0

查看程序集转储。非易失性代码可以相对于易失性代码重新排序。

想象一下你有:

assignmentA_with_volatile_operands;
assignmentB_with_non_volatile_operands;
assignmentC_with_volatile_operands;

编译可以免费重新排序到:

assignmentA_with_volatile_operands;
assignmentC_with_volatile_operands;
assignmentB_with_non_volatile_operands;

例如,您for在第一个处理程序中的循环SensorAcquisitionHandler实际上可以在之后执行,因为循环中IntMasterEnable出现的对象都没有被限定。forvolatile

编辑:

有些人认为这种代码重新排序是不允许的。事实是它们是,并且它们是用现实生活中的编译器执行的。

volatile 在程序中不充当内存屏障。您不应该假设易失性访问对非易失性访问起到了内存屏障的作用。

该标准在 C11 的 5.1.2.3 中定义了关于程序的可观察行为,对符合实现的最低要求是什么。

gcc例如说:对非易失性对象的访问相对于易失性访问没有排序。您不能使用易失性对象作为内存屏障来排序对非易失性内存的写入序列。

http://gcc.gnu.org/onlinedocs/gcc/Volatiles.html

许多编译器谨慎地不要在存在 volatile 的情况下执行此类代码重新排序优化,但我已经看到编译器(gcc例如)执行它们。

于 2012-10-25T23:02:41.273 回答