我目前正在使用将FreeRTOS作为系统OS的ARM Cortex M3微 Controller 来进行嵌入式项目。该代码是由一位前同事编写的,可悲的是该项目存在一些怪异的错误,我必须尽快找到并修复。

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

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

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

在powerfail任务执行的回调(称为powerfail回调)中,有RTOS调用,
其他任务被暂停的地方。但是不幸的是,在接收到powerwarn中断之前,那些被暂停的任务还可能有一个未完成的EEPROM_WriteBlock()调用。
因此,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()中使用EE_WriteBlock()来确定谁拥有互斥体的所有权,然后在任务不再运行时给出该互斥体的所有权。

谢谢

最佳答案

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

不要使用互斥锁直接控制对保留的Flash页的访问-而是使用阻塞的生产者-消费者队列。

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

主题有多种选择-如果所有写请求线程仅需要同步访问,则它们可以使用自己的信号量保留自己的静态请求结构,并仅将指向它的指针排队。

使用生产者-消费者队列类,该类允许在队列的开头进行高优先级推送,并在运行powerfail时在队列的前部推送“stopWriting”请求。然后,flashWriter将完成正在进行的所有写操作,弹出stopWriting请求,并被指示暂停自身(或者您可以使用flashWriter每次尝试弹出队列之前都检查的“stop”易失 bool 值)。

这样可以通过从其他线程中推送的闪存写入请求中删除硬互斥锁来防止死锁。其他线程是否继续排队写请求也没关系-它们将永远不会执行。

编辑:我刚喝了两杯咖啡,考虑到这一点,“flashWriter”线程可以轻松地成为“FlashWriterAndPowerFail”线程:

如果设置了可变的“停止” bool 值,则无论队列中是否有条目,都可以安排生产者-消费者队列返回pop()结果为null。在“FWAPF”线程中,在每个pop()返回之后执行空检查,如果不是,则对空或flashWrite操作执行powerFail操作。

当发生powerFail中断时,请设置停止 bool 值并在队列中发出“计数”信号灯,以确保FWAPF线程当前正在队列中被阻塞时使其处于运行状态。

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

09-26 12:23