6

我目前正在使用带有 FreeRTOS 作为系统操作系统的 ARM Cortex M3 微控制器进行嵌入式项目。该代码是由一位前同事编写的,遗憾的是该项目有一些奇怪的错误,我必须尽快找到并修复它们。

简短描述:该设备集成到车辆中,并使用集成调制解调器将一些“特殊”数据发送到远程服务器。

主要问题:由于设备集成在车辆中,设备的电源随时可能丢失。因此,设备将“特殊”数据的某些部分存储到两个保留的闪存页中。该代码模块被布置为两个闪存页面上的 eeprom 仿真(用于磨损均衡和从一个闪存页面到另一个闪存页面的数据传输)。eeprom 仿真使用所谓的“虚拟地址”,您可以在其中将任何大小的数据块写入当前活动/有效的闪存页面,并使用这些虚拟地址将其读回。前同事将 eeprom 仿真实现为多任务模块,您可以在其中从应用程序中的每个任务读取/写入闪存页面。乍一看,一切似乎都很好。

但我的项目经理告诉我,设备总是会丢失一些“特殊”数据,此时车辆中的电源电压会下降到一些伏特,设备会尝试将数据保存到闪存中。通常电源电压约为 10-18 伏,但如果降至 7 伏以下,设备会收到一个称为中断的中断powerwarn并触发一个称为 的任务powerfail task。具有所有任务的powerfail task最高优先级并执行一些回调,例如关闭调制解调器以及“特殊”数据存储在闪存页面中的位置。我试图理解代码并调试了几天/几周,现在我很确定我发现了问题:

在 powerfail 任务执行的那些回调(称为 powerfail 回调)中,有 RTOS 调用,其他任务在这些调用中被挂起。但不幸的是EEPROM_WriteBlock(),在收到 powerwarn 中断之前,那些暂停的任务也可能有一个未完成的调用。因此,powerfail 任务执行回调,并且在其中一个回调中有一个EE_WriteBlock()调用,其中任务无法获取互斥锁,EE_WriteBlock()因为另一个任务(已暂停)已经获取了它--> 死锁!

这是将数据写入闪存的例程:

uint16_t
EE_WriteBlock (EE_TypeDef *EE, uint16_t VirtAddress, const void *Data, uint16_t Size)
{
     .
     .
     xSemaphoreTakeRecursive(EE->rw_mutex, portMAX_DELAY);
     /* Write the variable virtual address and value in the EEPROM */
     .
     .
     .
     xSemaphoreGiveRecursive(EE->rw_mutex);
     return Status;
}

这是调用“xSemaphoreTakeRecursive()”时的 RTOS 特定代码:

portBASE_TYPE xQueueTakeMutexRecursive( xQueueHandle pxMutex, portTickType xBlockTime )
{
    portBASE_TYPE xReturn;

    /* Comments regarding mutual exclusion as per those within
       xQueueGiveMutexRecursive(). */
    traceTAKE_MUTEX_RECURSIVE( pxMutex );

    if( pxMutex->pxMutexHolder == xTaskGetCurrentTaskHandle() )
    {
        ( pxMutex->uxRecursiveCallCount )++;
        xReturn = pdPASS;
    }
    else
    {
        xReturn = xQueueGenericReceive( pxMutex, NULL, xBlockTime, pdFALSE );

        /* pdPASS will only be returned if we successfully obtained the mutex,
           we may have blocked to reach here. */
        if( xReturn == pdPASS )
        {
            ( pxMutex->uxRecursiveCallCount )++;
        }
        else
        {
            traceTAKE_MUTEX_RECURSIVE_FAILED( pxMutex );
        }
    }

    return xReturn;
}

我的项目经理很高兴我发现了这个错误,但他也强迫我尽快创建一个修复程序,但我真正想要的是重写代码。也许你们中的一个人可能会想,只要避免暂停其他任务就完成了,但这不是一个可能的解决方案,因为这可能会触发另一个错误。有没有人有一个快速的解决方案/想法我可以如何解决这个死锁问题?也许我可以使用xTaskGetCurrentTaskHandle()inEE_WriteBlock()来确定谁拥有互斥锁的所有权,然后在任务不再运行时给出它。

谢谢

4

1 回答 1

6

在许多系统上,写入闪存需要在写入期间禁用中断,所以我不确定在写入过程中如何使 powerFail 运行,但无论如何:

不要使用互斥锁直接控制对保留闪存页面的访问 - 改为使用阻塞的生产者-消费者队列。

通过排队请求将所有这些写入委托给一个“flashWriter”线程。如果请求写入的线程需要同步访问,请在请求结构中包含一个事件或信号量,请求线程在推送其请求后等待。flashWriter 可以在完成时发出信号(或在加载带有错误指示的结构后:)。

一个主题有一些变化——如果所有的写请求线程只需要同步访问,也许他们可以用自己的信号量保留自己的静态请求结构,并只排队一个指向它的指针。

使用允许在队列头部进行高优先级推送的生产者-消费者队列类,并且当 powerfail 运行时,在队列前面推送“stopWriting”请求。然后 flashWriter 将完成任何正在进行的写入操作,弹出 stopWriting 请求,因此被指示暂停自身,(或者您可以使用 flashWriter 每次尝试弹出队列之前检查的“停止”易失性布尔值)。

这应该通过从其他线程中推送的闪存写入请求中删除硬互斥锁来防止死锁。其他线程是否继续排队写入请求并不重要——它们永远不会被执行。

编辑:我刚喝了两杯咖啡,考虑到这一点,“flashWriter”线程很容易变成“FlashWriterAndPowerFail”线程:

如果设置了 volatile 'stop' 布尔值,无论队列上是否有条目,您都可以安排您的生产者-消费者队列返回 null 的 pop() 结果。在“FWAPF”线程中,在每次 pop() 返回后进行一次 null 检查,并在 null 时执行 powerFail 操作,如果不是,则执行 flashWrite 操作。

当 powerFail 中断发生时,设置 stop bool 并在队列中发出“count”信号量,以确保如果 FWAPF 线程当前在队列中被阻塞,则它可以运行。

这样,您就不需要单独的“powerFail”线程和堆栈——一个线程可以执行 flashWrite 和 powerFail,同时仍然确保没有互斥锁死锁。

于 2014-04-24T10:11:18.313 回答