IEC61508系统中的动态内存使用

描述

IEC 61508标准强烈推荐使用静态内存管理方式。在安全应用设计中,我们都在遵循这个建议。但我们可能会需要这样的功能:

• 运行时配置

• 删除组件的编译时配置

• 更灵活的控制外部行为或硬件

• 平台作为完整的一代产品

这些功能需要动态内存管理解决方案。

IEC61508中推荐使用静态内存管理方式的原因与动态内存管理相关的编程错误导致的潜在风险相关,我们必须避免和发现这些编程错误,潜在风险包括:

1 内存碎片

2 没有可用内存

3 空闲内存丢失

4 内存释放到错误内存池

5 内存多次释放

6 释放后继续使用内存

7 使用时释放内存

一类解决方案只允许分配内存,这类解决方案通过避免内存重用来解决上述问题中的3-7,这是一种有效的解决方案,通常用于不同产品变体启动期间中的内存灵活配置,在运行期间保持内存布局不变。

当这类方法不能满足需求时,我们需要一种更通用的方法来系统地避免或检测所有列出的风险情况。

1内存碎片

内存碎片是一种效应,当我们使用不同的内存大小执行多个分配和释放周期时可能会产生这种效应。

在实时系统中,我们需要确定性和恒定的执行时间。使用改进的分配算法(如首次适配算法),执行时间严重将依赖于以前的内存活动,这对我们的系统设计是不利的。

避免内存碎片的方法是使用一个(或一组)固定大小的内存块池。好消息是专业的实时内核为用户提供了固定大小内存块的内存管理机制,用户可以使用实时内核提供的这些服务。在本文中,我们考虑实时内核的主函数接口。

作为参考,一个简单的使用序列为:

 

ptr = MemAlloc(pool);
/* use memory via ptr */
MemFree(pool, ptr);

 

在启动过程中调用:

 

pool = Create(mem, size, num);

 

2没有可用内存

如果没有足够的内存满足当前分配请求,内存分配函数会反馈相应信息(如NULL指针)。用户应该检查函数返回值并采取适当的措施。

 

ptr = MemAlloc(pool);
if (ptr == NULL) {
    MemErrOutOfMemory(pool);
}
/* use memory via ptr */
MemFree(pool, ptr);

 

使用实时内核时,我们可以通过扩展内存分配函数,进一步避免丢失检查和失败处理的风险,使用阻塞等待空闲内存:

 

ptr = MemAllocWait(pool);
/* use memory via ptr */
MemFree(pool, ptr);
ptr = MemAllocWait(pool);

 

通过阻塞等待方式确保返回的指针是一个有效的内存块。强制性安全任务监视器将检测任务是否在定义的截止时间内获得足够的内存。

3内存释放到错误内存池

如果用户负责将内存释放到正确的内存池中(例如,内存池是释放内存的参数),可能会出错。我们可以通过向info区域添加额外的参数来避免这个错误。为了简单(快速)地访问这些数据,info区作为已分配内存的一部分。应用程序不能使用这部分数据,并且位于应用程序内存块引用的前面:

动态内存

改进后的内存使用顺序如下:

 

ptr = MemAllocWait(pool);
/* use memory via ptr */
MemFree(ptr);

 

4空闲内存丢失

所谓的“内存泄漏”发生在内存分配且被使用之后,但没有释放内存块的时候。此错误不容易检测,特别是当分配和释放操作将在不同的函数(或任务)中实现时。

在不知道应用程序细节的情况下,检测内存泄漏的一种方法是使用内存看门狗,看门狗的工作方式类似于通常使用的执行看门狗。

假设在内存分配过程中定义了一个看门狗时间,这迫使我们在这段时间内触发看门狗,以保持内存块有效。

我们将内存计时数据存储在info区域中。

为了检查所有分配的内存块的看门狗时间周期,我们引入了一个检查函数。下列伪代码可以帮助了解我们如何检查内存块。

 

void MemCheck(pool)
{
    for each 'block' in 'pool' do:
        if ( 'block::timeout' is greater than 0 ) then:
            decrement 'block::timeout' by 1
        if ( 'block::timeout' is equal to 0 ) then:
            call MemErrTimeout (block)
}

 

我们系统安全自检过程中定期调用这个函数。使用序列如下所示。

 

ptr = MemAllocWait(timeout, pool);
MemUseTrigger (ptr);
/*use memory via ptr*/
MemFree(ptr);

 

如果错过了看门狗时间周期,我们可以在回调函数中执行适当的动作:

 

void MemErrTimeout(ptr)
{
    /* action on timeout: */
    /* log diagnostic data */
    /* initiate safe state */
} 

 

5内存多次释放

当在没有检测机制的情况下多次(错误地)释放一个内存块时,可能导致正在运行系统的奇怪行为。这种错误很难调试,必须避免。

在info区域中添加一个额外的冗余值,可以非常有效地处理这个问题。推荐使用内存池参数的补值,需在内存分配函数中将这两个值设置为匹配的一对。

内存释放函数可以检查这些冗余信息,将内存块放回相应的内存池中,然后破坏冗余信息。实现伪代码如下:

 

void MemFree(ptr)
{
    if ( 'ptr::pool' is equal to complement of 'ptr::inv_pool' ) then:
        release memory block 'ptr' to 'ptr::pool'
        set 'ptr::inv_pool' to 'ptr::pool'
    else:
        call MemErrDoubleFree (ptr)
}

 

最后,我们可以在回调函数中为这种错误情况定义适当的操作:MemErrDoubleFree(ptr)。

6释放后继续使用内存

这个编程错误听起来不可思议,但在多任务环境中,这个错误可能存在,在最坏的情况下,很长一段时间都没有被发现。

为了检测这种情况,我们可以重用info区域中的数据冗余。在这种情况下,我们在使用内存块之前检查冗余是否损坏。为了使代码顺序尽可能简单,我们可以集成内存看门狗触发:

 

void MemStartAccess(ptr)
{
    if ( 'ptr::pool' is not equal to complement of 'ptr::inv_pool' ) then:
        call MemErrUsedFree (ptr)
    else
        set 'ptr::timeout' to 'ptr::watchdog_time'
}

 

最后,在回调函数MemErrUsedFree(ptr)中为这种错误情况定义适当的操作。

在使用内存块之前,该检测机制将需要一个额外的函数调用。

 

ptr = MemAllocWait(timeout, pool);
MemStartAccess(ptr);
/* use memory block via ptr */
MemFree(ptr);

 

7使用时释放内存

这个编程错误与前面的错误属于同一类。在多任务环境中,可能释放了被中断任务占用的内存块。该任务重新运行时,其使用的内存已经被释放。

针对这个问题,我们可以使用一个对称函数的解决方案。对应MemStartAccess()函数,引入结束函数。这个新函数可以通知用户内存块不能使用了:

void MemEndAccess (ptr);

通过相应的阻塞函数来释放内存,我们可以避免这类编程错误。

void MemFreeWait (ptr);

对应的动态内存使用序列如下:

 

ptr = MemAllocWait(timeout, pool);
MemStartAccess (ptr);
/*use memory block via ptr*/
MemEndAccess (ptr);
MemFreeWait (ptr);

 

总结

在本文中,我们发现了一种在IEC61508系统中使用动态内存的方法,避免相应的编程错误并在检测到错误后做出响应。我们为这些安全功能付出了相应的代价:每个分配的内存块将占用额外内存空间,消耗了CPU运行时间。

尽管如此,还是建议在安全关键型系统中谨慎使用动态内存。

Flexible Safety RTOS是基于μC/OS-II+MPU机制实现的功能安全操作系统,提供了基于块的内存管理机制。麦

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分