【问题标题】:Circular Buffer in FlashFlash 中的循环缓冲区
【发布时间】:2009-11-03 18:25:46
【问题描述】:

我需要将不同长度的项目存储在闪存芯片的循环队列中。每个项目都有它的封装,所以我可以弄清楚它有多大以及下一个项目从哪里开始。当缓冲区中有足够的项目时,它会自动换行。

在闪存芯片中存储循环队列的好方法是什么?

我想存储数以万计的物品的可能性。所以从头开始读取到缓冲区的末尾并不理想,因为搜索到末尾需要时间。

另外,因为它是循环的,我需要能够区分第一个项目和最后一个项目。

最后一个问题是它存储在闪存中,因此擦除每个块既耗时又只能为每个块执行一定次数。

【问题讨论】:

  • 我对你的语言感到困惑。在我看来,缓冲区是一块快速、易失且经常变化的内存。正如您正确指出的那样,闪存可能存在一些问题。您能否提供更多背景信息或详细信息,说明您正在尝试做什么以及为什么?
  • 我想将数据保存在非易失性内存中。我拥有的非易失性存储器是闪存。非易失性很重要,因为产品可能会关闭一段时间,我不想丢失数据。重新上电后,需要将下一条数据放在上一条之后。

标签: embedded queue circular-buffer flash-memory


【解决方案1】:

首先,区块管理:

在每个块的开头放置一个较小的标题。您需要跟踪“最旧”和“最新”的主要内容是一个块号,它只是以 k 为模递增。 k 必须大于您的总块数。理想情况下,使 k 小于您的 MAX 值(例如 0xFFFF),这样您就可以轻松分辨出什么是已擦除块。

在启动时,您的代码会依次读取每个块的标头,并在 ni+1 = (ni + 1) 模数 k。注意不要被擦除的块(块号例如 0xFFFF)或以某种方式损坏的数据(例如不完全擦除)弄糊涂。

在每个区块内

每个块最初都是空的(每个字节都是 0xFF)。每条记录只是一个接一个地写入。如果您有固定大小的记录,那么您可以使用简单的索引来访问它。如果您有可变大小的记录,那么要读取它,您必须从块的开头进行扫描,链表样式。

如果您想拥有可变大小的记录,但要避免线性扫描,那么您可以在每条记录上都有一个明确定义的标题。例如。使用 0 作为记录分隔符,并使用COBS-encode(或COBS/R-encode)每个记录。或者使用您选择的字节作为分隔符,如果该字节出现在每条记录中,则“转义”该字节(类似于PPP protocol)。

在启动时,一旦你知道你的最新区块,你就可以对最新记录进行线性扫描。或者,如果您有固定大小的记录或记录分隔符,您可以进行二分搜索。

清除计划

对于某些闪存芯片,擦除一个块可能需要很长时间 - 例如。 5秒。考虑“提前”将擦除安排为后台任务。例如。当当前块已满 x% 时,开始擦除下一个块。

记录编号

您可能想要对记录进行编号。我过去的做法是在每个块的标题中放置第一条记录的记录号。然后软件必须对块内每条记录的数量进行计数。

校验和或 CRC

如果您想检测损坏的数据(例如,由于意外电源故障导致的不完整写入或擦除),那么您可以将校验和或 CRC 添加到每条记录,也许还可以添加到块头。请注意,块头 CRC 只会覆盖头本身,而不是记录,因为在写入每个新记录时它不能被重写。

【讨论】:

【解决方案2】:

保留一个单独的块,其中包含指向第一条记录开头和最后一条记录结尾的指针。您还可以保留更多信息,例如记录总数等。

在您最初用完空间之前,添加记录就像将它们写入缓冲区的末尾并更新尾指针一样简单。

由于您需要回收空间,请删除足够的记录以适应当前记录。删除记录时更新头指针。

您需要跟踪释放了多少额外空间。如果保留指向最后一条记录末尾的指针,下次需要添加记录时,可以将其与指向第一条记录的指针进行比较,以确定是否需要删除更多记录。

另外,如果这是 NAND,您或闪存控制器将需要进行去块和磨损均衡,但这都应该在比为循环缓冲区分配空间更低的层。

【讨论】:

  • 包含指向第一条和最后一条记录的指针的块将比包含记录的块磨损得更快,因为每次写入新记录时都需要更新它。对于某些写入周期较低的闪存,这可能早在第 100,000 条记录时就会发生。
【解决方案3】:

我想我现在明白了。似乎您最大的问题是,已经填满了可用的录制空间,接下来会发生什么?新数据应该覆盖最旧的数据,我相信你所说的循环缓冲区是什么意思。但由于数据不是固定长度的,您可能会覆盖多条记录。

我假设长度的可变性足够高,无法将所有内容填充到固定长度。

您的写入段需要跟踪代表下一条要写入的记录开始的地址。如果您提前知道要写入的块的大小,您可以判断您是否将在逻辑缓冲区的末尾结束并从“0”重新开始。我不会把唱片分成一些在结尾和一些在开头。

一个单独的寄存器可以跟踪开始;这是尚未被覆盖的最旧数据。如果你去读出数据,这就是你要开始的地方。

然后,数据写入器将检查,给定写入起始地址和即将提交的数据长度,是否应该碰撞读取寄存器,读取寄存器将检查第一个块并查看长度,然后前进到下一个块记录,直到有足够的空间来写入任何数据。可能在写入数据的结尾和最旧数据的开头之间存在垃圾数据的间隙。但是这样一来,你可以只写一个或两个地址作为开销,而不是重新排列块。

至少,这可能是我会做的。高温

【讨论】:

    【解决方案4】:

    闪存中的“循环”可以根据块大小来完成,这意味着您必须声明为该缓冲区分配多少闪存块。

    缓冲区的实际大小将在 n-1(n 是块数)和 n 之间的每个特定时间。

    每个块应以包含序列号或时间戳的标头开头,可用于确定哪个块比另一个块更旧。

    用页眉和页脚封装的每个项目。默认标题包含您想要的任何内容,但根据此标题,您必须知道项目的大小。默认页脚为 0xFFFFFFFF。此值指示空终止。

    在您的 RAM 中,您必须保存指向最旧块和最新块的指针以及指向最旧项和最新项的指针。上电后,您会遍历所有块,找到相关块并加载此成员。

    当你想存储一个新项目时,你检查最新的块是否包含足够的空间来存储这个项目。如果是,则将项目保存在上一个项目的末尾,并将上一个页脚更改为指向该项目。如果它不包含足够的空间,则需要擦除最旧的块。在擦除此块之前,将最旧的块成员 (RAM) 更改为指向下一个块,并将最旧的项目指向该块中的第一个项目。 然后您可以将新项目保存在此块中,并将最新项目的页脚更改为指向该项目。

    我知道解释可能听起来很复杂,但过程非常简单,如果你写得正确,你甚至可以让它断电安全(始终牢记写入的顺序)。

    注意缓冲区的循环并没有保存在flash中,而是flash只包含一个blocks,你可以根据blocks headers和items headers来决定这些items的顺序是什么

    【讨论】:

      【解决方案5】:

      我看到三个选项:

      选项1:是将所有内容填充到相同的大小,这很简单,存储指向缓冲区头部和尾部的指针,以便您知道从哪里写入以及从哪里开始读取,使用每个对象的大小来获取到下一个的偏移量,这意味着您需要像处理链表一样遍历缓冲区,如果您需要第 5000 项,它也很慢。

      option2: 是在循环缓冲区中只存储指向真实数据的指针,这样当你循环时你不必处理大小不匹配的问题。如果您将真实数据存储在一个循环缓冲区中并且不将其填充出来,您可能会遇到这样一种情况,即您使用 1 个新数据对象过度了解多个项目,我认为这是不行的。

      将实际数据存储在闪存中的其他位置,大多数闪存都会内置某种磨损均衡功能,如果是这样,您无需担心多次覆盖同一位置,IC 会找出实际存储它的位置在芯片上,只需写入下一个可用空间。

      这意味着您需要为循环缓冲区选择一个最大大小,具体操作方式取决于数据的可变性。如果数据的大小变化很大,比如只有几个字节,那么您应该将其填充并使用选项 1。如果大小变化剧烈且不可预测,请选择可能的最大大小并计算出有多少对象该大小将适合您的闪存,将其用作缓冲区中的最大条目数。这意味着您浪费了大量空间。

      选项 3:如果对象真的可以是任何大小,那么您应该只使用文件系统,按顺序命名文件并在完全记住时循环返回,如果您的新条目很大,您可以必须删除多个旧条目以适应它。这实际上只是选项 2 的扩展,因为选项2 在许多方面都是一个简单的文件系统。

      【讨论】:

      • 小心磨损均衡假设...如果您有 USB 或 SD 类型接口,则为真。如果您直接处理 flash 部分,则不是这样。
      猜你喜欢
      • 2016-06-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-26
      • 2022-11-14
      • 2019-06-08
      • 1970-01-01
      相关资源
      最近更新 更多