【问题标题】:Deadlock of powerfail sequence during write to flash page写入闪存页面期间电源故障序列死锁
【发布时间】:2014-06-09 12:23:01
【问题描述】:

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

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

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

但我的项目经理告诉我,设备总是会在某些时候丢失一些“特殊”数据,此时车辆的电源电压会下降到一些伏特,设备会尝试将数据保存到闪存中。 通常电源电压约为 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;
}

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

谢谢

【问题讨论】:

    标签: c embedded rtos freertos


    【解决方案1】:

    在许多系统上,写入闪存需要在写入期间禁用中断,因此我不确定如何在写入进行时使 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,同时仍确保没有互斥锁死锁。

    【讨论】:

    • +1 这正是我的建议。写入应在单个中断中完成,所有其他级别应仅对请求进行排队。如果您还允许它们被优先考虑,那么可以在其他请求之前执行“重要”请求。
    • 对不起,我忘了说:当我收到 powerwarn 中断时,powerfail 任务开始执行回调,但中断/中断被禁用。
    • @arge - 是的,我猜到了:) 我的第一个想法是“不要暂停其他任务,以便它们释放互斥锁”,但这可能不适用于禁用中断:( 然后我想到了更好的主意..
    猜你喜欢
    • 2011-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-09
    • 2018-10-16
    • 2016-09-20
    • 2021-12-18
    • 1970-01-01
    相关资源
    最近更新 更多