2012-03-14 127 views
1

我正在为内存非常有限的微处理器编程设计,我必须在不同的功能中使用“大量”内存。我不能有一个大的堆栈段,堆段,数据段,我必须选择哪个做大,哪个做小。我有大约32KB的总数,我应该让堆栈段很大还是堆段很大?

我使用大约20KB的文本段,这给我剩下12KB。我需要一个4KB的缓冲区来传递不同的功能(SPI闪存扇区大小)。应该在哪里初始化那个大缓冲区?

所以我的选择是:

1)如果我宣布在函数开头的缓冲区,堆栈将需要进行大量

spiflash_read(...) 
{ 
    u8 buffer[4096]; // allocated on stack 
    syscall_read_spi(buffer,...) 
} 

2)分配动态,堆会需要进行大

spiflash_read(...) 
{ 
    u8 *buffer = (u8*) malloc(4096); // allocated in heap 
    syscall_read_spi(buffer,...) 
} 

3)分配静态,巨大的一面,不能在“SPI库”之外使用。

static u8 buffer[4096]; // allocated in data section. 

spiflash_read(...) 
{ 
    syscall_read_spi(buffer,...) 
} 

我的问题是这是实现该设计的最佳方法是什么?有人能解释推理吗?

+0

为什么你需要一个4KB缓冲区来读取闪存?我曾在许多具有相似内存的系统上工作,但我从来不需要处理大型的单个缓冲区。你大概可以设计你的代码,不需要传递整个闪存扇区。 – TJD 2012-03-14 20:38:49

+0

“文本”段包含代码和常量数据,在通常由只读存储器执行的受限制系统上。为什么你有20K的RAM用于这个目的? – Clifford 2012-03-14 20:46:22

+0

@TJD我正在使用一个4kB子扇区的闪光灯,您无法擦除任何东西,也请注意,您无法写入之前未擦除的位置。所以如果我做了写操作,我必须读取4kB,修改我打算写入的部分,擦除整个扇区然后写回4kB。 – user968102 2012-03-14 21:44:19

回答

9

静态分配总是运行时安全的,因为如果你的内存不足,链接器会在buid时间告诉你,而不是在运行时崩溃的代码。但是,除非内存在执行过程中被永久需要,否则它可能是浪费的,因为分配的内存不能用于多种目的,除非您以这种方式明确地编写内存。

动态内存分配运行时可检查 - 如果用完堆,malloc()返回空指针。然而,要感谢您测试返回值,并根据需要释放内存。动态内存块通常是4或8字节对齐的,并且存在堆管理数据开销,使得它们对于非常小的分配而言效率低下。频繁分配和重新分配各种块大小可能导致堆碎片和浪费的内存 - 对于“永远在线”的应用程序而言,这可能是灾难性的。如果你永远不打算释放内存,并且它会一直分配,并且你知道你需要多少,那么你可能会更好地使用静态分配。如果你有库源,你可以修改malloc立即停止内存分配失败,以避免检查每个分配。如果分配大小通常为几个常见大小,则固定块分配器而不是标准malloc()可能更可取。这将更具确定性,并且您可以实施使用情况监视来帮助优化每个大小的块大小和数量。

堆栈分配是最有效的,因为它会根据需要自动获取并返回内存。但是它也很少或没有运行时检查支持。通常,当发生堆栈溢出时,代码将非确定性地失败 - 并且不一定在根本原因附近的任何地方。一些链接器可以生成堆栈分析输出,该输出将通过调用树来计算最坏情况下的堆栈使用情况;如果你有这个设施,你应该使用它,但是要记住,如果你有一个多线程系统,将会有多个栈,并且你不需要检查每个入口点的最坏情况。此外,lonker不会分析中断堆栈的使用情况,并且您的系统可能有单独的中断堆栈或共享系统堆栈。

我会解决这个肯定不是放置在堆栈上大数组或对象,但遵循以下过程的方法:

  1. 使用链接器堆栈分析计算最坏情况下的堆栈使用,允许额外的堆栈如果需要的话可以用于ISR。分配那么多堆栈。

  2. 分配执行期间静态所需的所有对象。

  3. 使用链接图来确定多少内存仍然存在,分配几乎所有的,要堆(您的链接或链接脚本可以自动做到这一点,但如果你必须明确地设置堆大小,留下一点未使用的,否则每次添加一个新的静态对象,或者扩展堆栈时,都必须调整堆大小)。分配堆中的所有大型临时对象,并尽力释放分配的内存。

如果您的库包含堆诊断函数,那么您可以在您的代码中使用它们来监视堆使用情况,以检查您的耗尽程度。

的链接分析“最坏情况”是likley要大一些是啥子,你在实践中看到 - 最坏的情况下的路径我永远不会被执行。您可以预先用特定字节(比如0xEE)或模式填充堆栈,然后在进行大量测试和操作后,检查“高潮”标记并以这种方式优化堆栈。谨慎使用此技术;您的测试可能无法涵盖所有​​可预见的情况。

+2

也要小心任何能够告诉你“最坏情况”堆栈使用情况的静态分析。实际上,通过静态分析无法确定最糟糕的堆栈使用情况。如果您使用动态变化的函数指针,那么您无法准确分析函数调用链。 – TJD 2012-03-14 22:09:15

+0

@TJD:好点。有很多地方静态分析可以给出错误的答案。正如我所说的ISR需要考虑,函数指针为TJD说,如果使用RTOS,你需要考虑每个任务的切入点最坏的情况下,然后添加RTOS线程上下文的开销,如果存储在堆栈上 - 的静态分析不会解释这一点。不要精确到静态分析所说的 - 如果我能负担得起,我通常会增加20%或更多的保证金。这允许更改代码的空间,所以您不必每次都进行分析。 – Clifford 2012-03-15 11:52:15

+0

@TJD:如果这是讲述“隐藏”的呼叫路径(如中断)一个正确设计的静态分析是能够报告最差值,实际堆栈需求应该没有比这更。静态分析工具的缺点是,他们可能会报告可能永远不会发生的,或者是因为调用图中包含明显的周期可能无法确定最坏情况下的堆栈使用,无论是否有可能基于执行路径最坏情况下的堆栈使用为了执行而继续前进。 – supercat 2013-07-09 22:37:59

0

传统的答案是,你应该操纵你的运行时间,以便你的堆栈和堆堆向对方增长。这可以让你忽略哪一个需要“更大”,只是担心如果你没有分配足够的空间TOTAL会发生什么。

+0

我同意。一些进一步的观察......将它分配为静态的问题在他的问题中提到了他的缺点。通过malloc分配它有一些开销;这是一个非常小的内存,但是在内存受限的环境中,每个字节都是有效的。因此,将它分配到堆栈上作为最佳选择。 – 2012-03-14 20:08:22

+0

当然,除非堆堆碎片化,你可能仍然有足够的空闲内存,但是不够大。 – 2012-03-15 01:53:12

+0

@Alex,堆碎片似乎不太可能,因为那家伙说他在内存极其有限的微控制器上。因此,他可能会非常小心地打乱他的堆。 – 2012-03-15 15:45:30

3

这取决于你是否需要缓冲所有的时间。如果你的工作的90%花费在这个缓冲区上,那么我会把它放在数据段

如果只是暂时需要一个给定的函数,然后把它放在堆栈上。这样做很便宜,意味着您可以重新使用该空间。这意味着你必须有一个很大的堆栈

否则把它放在堆上。

真的,如果你是这个内存受限的你应该做你的内存消耗是什么详细分析。一旦你变得如此之小,你不能把它看作'正常',把它放在操作系统/运行时,开发。我见过不允许进行任何动态mem分配的嵌入式开发商店;每件事情都是预先计算并静态分配的。虽然它们可能具有多用途内存区域(例如,一个常见的IO缓冲区)。回到我的COBOL时代,这是你工作的唯一方式(今天的年轻人......抱怨,抱怨......)

0

问题是,你是否真的需要一次读取4096个字节?

如果数据对象较小,则只能读取所需的大小。

即使你只能删除4KB页,也没有必要去缓存完整的块RAM,因为它是一个坏主意,缓存它,擦除,然后重写一遍。

通常情况下,如果第一页是满的,你可以在需要的数据复制到一小块一小块新的一页,如果第二页全是你再删除第一个。

当其中一个动作正在运行时,这也是安全的。