smartmx / TFDB

Tiny Flash Database for MCU.
MIT License
102 stars 31 forks source link

关于块管理的一些问题 #5

Closed WangShuoran closed 5 months ago

WangShuoran commented 9 months ago

您好!

我在找单片机KV资源块管理的过程中,看到您开源的库。在看README及部分源码的时候,有一些问题:

  1. 在tfdb_index_t中的flash_addr应该最好填入物理块的起始地址,flash_size应该最好要填入物理块的大小吧(块Block指被擦除的最小单位),否则的话如果第二次修改数据将可能导致把其他数据清除掉,是吗?
  2. 如果这样的话假如一个单片机有6个模块,用原生的tfdb_set来操作时是不是必须要手动管理数据,保证数据在下次修改过程中不要断电丢失?
  3. 因此您提供了第二类API(dual API),您讲到tfdb dual会调用tfdb_set和tfdb_get,并且在数据前部添加两个字节的seq,所以在tfdb dual中,最长支持的存储变量长度为253字节。这是不是指dual API维护两个数据块(两个数据块完成数据相互更新),但是一般Flash数据块都要大于256字节的,您这里最长变量支持253代表一个块只能用256字节,那就至少有一半空间没有用到(我相信应该不是这么理解的)。那么意义到底是什么,维护两个数据块的原因到底是什么?
WangShuoran commented 9 months ago

而且在README中提到的代码块中:

tfdb_dual_index_t my_test_tfdb_dual = {
        .indexes[0] = {
                .end_byte     = 0x00,
                .flash_addr   = 0x08077000,
                .flash_size   = 256,
                .value_length = TFDB_DUAL_VALUE_LENGTH(sizeof(my_test_params_t)),
        },
        .indexes[1] = {
                .end_byte     = 0x00,
                .flash_addr   = 0x08077100,
                .flash_size   = 256,
                .value_length = TFDB_DUAL_VALUE_LENGTH(sizeof(my_test_params_t)),
        },
};

这两个flash_addr都指向了一个块,此时如果tfdb_port中实现的tfdb_port_erase将全把上面数据擦除了,我感觉不应该是这样。所以想问下这个dual API作用是什么?

smartmx commented 9 months ago

您好!

我在找单片机KV资源块管理的过程中,看到您开源的库。在看README及部分源码的时候,有一些问题:

  1. 在tfdb_index_t中的flash_addr应该最好填入物理块的起始地址,flash_size应该最好要填入物理块的大小吧(块Block指被擦除的最小单位),否则的话如果第二次修改数据将可能导致把其他数据清除掉,是吗?
  2. 如果这样的话假如一个单片机有6个模块,用原生的tfdb_set来操作时是不是必须要手动管理数据,保证数据在下次修改过程中不要断电丢失?
  3. 因此您提供了第二类API(dual API),您讲到tfdb dual会调用tfdb_set和tfdb_get,并且在数据前部添加两个字节的seq,所以在tfdb dual中,最长支持的存储变量长度为253字节。这是不是指dual API维护两个数据块(两个数据块完成数据相互更新),但是一般Flash数据块都要大于256字节的,您这里最长变量支持253代表一个块只能用256字节,那就至少有一半空间没有用到(我相信应该不是这么理解的)。那么意义到底是什么,维护两个数据块的原因到底是什么?
  1. 是的,flash_size就是一个物理块的大小,就是这个块被擦除的最小单位。
  2. 是的,tfdb_set在用完一整个扇区后悔执行擦除操作,这时断电会导致数据丢失。可以使用tfdb_dual来完成这个操作。
  3. 存储的变量长度为253字节,不是扇区长度为253字节。例如,扇区为4096个字节,存储的数据长度为253字节,则你每次写入flash的长度为253 + 2(seq) + 1(和校验) + 1(end_byte)+ 对齐字节(如果需要)。tfdb会从扇区起始地址开始写入,一直往后写,写到扇区剩余长度不满足单次写入的长度时,擦除从头写。这个时候断电,擦除扇区数据丢失,但是另一个扇区数据还在,所以可以实现断电不丢失之前的数据。
smartmx commented 9 months ago

而且在README中提到的代码块中:

tfdb_dual_index_t my_test_tfdb_dual = {
        .indexes[0] = {
                .end_byte     = 0x00,
                .flash_addr   = 0x08077000,
                .flash_size   = 256,
                .value_length = TFDB_DUAL_VALUE_LENGTH(sizeof(my_test_params_t)),
        },
        .indexes[1] = {
                .end_byte     = 0x00,
                .flash_addr   = 0x08077100,
                .flash_size   = 256,
                .value_length = TFDB_DUAL_VALUE_LENGTH(sizeof(my_test_params_t)),
        },
};

这两个flash_addr都指向了一个块,此时如果tfdb_port中实现的tfdb_port_erase将全把上面数据擦除了,我感觉不应该是这样。所以想问下这个dual API作用是什么?

这个只是一个例子,tfdb_dual必须使用两个可以分开擦除的不同扇区。我使用的是ch32v203,最小擦除单位256字节。所以实例没问题,你需要根据自己的单片机进行调整。

smartmx commented 9 months ago

建议你可以写一个测试例程,使用tfdb_set或者tfdb_dual_set后,把整个扇区内容打印出来,循环往复,就可以理解tfdb的工作原理。

WangShuoran commented 9 months ago

谢谢您的快速回复,我的理解如下,并马上根据您的建议写例程实测理解,主要现在是在综合考虑。

其中您对我问题的两个回答我都看了,其中我复述一下,再马上执行您的建议进行理解:

您好! 我在找单片机KV资源块管理的过程中,看到您开源的库。在看README及部分源码的时候,有一些问题:

  1. 在tfdb_index_t中的flash_addr应该最好填入物理块的起始地址,flash_size应该最好要填入物理块的大小吧(块Block指被擦除的最小单位),否则的话如果第二次修改数据将可能导致把其他数据清除掉,是吗?
  2. 如果这样的话假如一个单片机有6个模块,用原生的tfdb_set来操作时是不是必须要手动管理数据,保证数据在下次修改过程中不要断电丢失?
  3. 因此您提供了第二类API(dual API),您讲到tfdb dual会调用tfdb_set和tfdb_get,并且在数据前部添加两个字节的seq,所以在tfdb dual中,最长支持的存储变量长度为253字节。这是不是指dual API维护两个数据块(两个数据块完成数据相互更新),但是一般Flash数据块都要大于256字节的,您这里最长变量支持253代表一个块只能用256字节,那就至少有一半空间没有用到(我相信应该不是这么理解的)。那么意义到底是什么,维护两个数据块的原因到底是什么?
  1. 是的,flash_size就是一个物理块的大小,就是这个块被擦除的最小单位。
  2. 是的,tfdb_set在用完一整个扇区后悔执行擦除操作,这时断电会导致数据丢失。可以使用tfdb_dual来完成这个操作。
  3. 存储的变量长度为253字节,不是扇区长度为253字节。例如,扇区为4096个字节,存储的数据长度为253字节,则你每次写入flash的长度为253 + 2(seq) + 1(和校验) + 1(end_byte)+ 对齐字节(如果需要)。tfdb会从扇区起始地址开始写入,一直往后写,写到扇区剩余长度不满足单次写入的长度时,擦除从头写。这个时候断电,擦除扇区数据丢失,但是另一个扇区数据还在,所以可以实现断电不丢失之前的数据。

其中第三点您回答的是value_length的TFDB_DUAL_VALUE_LENGTH(sizeof(my_test_params_t))中my_test_params_t的大小为253字节,而不是flash_size的限制是这么大。在程序工作中,会将value_length的数据在flash_size依次存放,直到不能整除。才会进行擦除处理。

WangShuoran commented 9 months ago

tfdb_dual必须使用两个可以分开擦除的不同扇区,和我对KV库理解相同。您看看上述理解是否正确?

smartmx commented 9 months ago

tfdb_dual必须使用两个可以分开擦除的不同扇区,和我对KV库理解相同。您看看上述理解是否正确?

是的,还有一个减去扇区头部信息(4字节或者8字节)后不能整除,其他没有问题。

WangShuoran commented 9 months ago

image image 也就是您在上面讲到TinyFlashDB设计原理,其中一个块分为几个value_length空间,每个value_length都有图片上提到扇区头部信息(4字节或者8字节),是这样的吧?

smartmx commented 9 months ago

没有,只有扇区头部有,后面写入的数据不包括头部

WangShuoran commented 9 months ago

谢谢,确实应该名词叫扇区,叫块是不对的,您这么说我就理解了。其中README中提到各种4字节的限制也是来源于此的吧? image

smartmx commented 9 months ago

是的。

WangShuoran commented 9 months ago

我当前移植完成了,其中TFDB_WRITE_UNIT_BYTES是每个写入的单元长度,但是TFDB_VALUE_AFTER_ERASE_SIZE这个是代表什么呢? 闪存擦除后的值大小,不大于TFDB_WRITE_UNIT_BYTES大小,但是很难找到应用在哪里,想请问设计这个值作用在哪里呢?

smartmx commented 9 months ago

TFDB_WRITE_UNIT_BYTES是flash写入的单位,有的flash支持一次写入一个字节,有的flash只能一次写入4字节,这个时候软件需要将写入的地址对齐。 TFDB_VALUE_AFTER_ERASE_SIZE是表示有的flash擦除后的值的判断单位。 有的flash擦除后是每4个字节的值为0xF5F9BDA9,这个时候TFDB_VALUE_AFTER_ERASE_SIZE为4,TFDB_VALUE_AFTER_ERASE0xF5F9BDA9。 有的flash擦除后是2个字节的0x39e3,这个时候TFDB_VALUE_AFTER_ERASE_SIZE为2,TFDB_VALUE_AFTER_ERASE0x39e3

一般的flash没有加密。TFDB_VALUE_AFTER_ERASE_SIZE为1,TFDB_VALUE_AFTER_ERASE0xff,即可。

WangShuoran commented 9 months ago

谢谢您,我继续移植,移植完成我关闭这个ISSUE,并立志整理后开源我的移植过程,再次感谢

WangShuoran commented 8 months ago

您好,我在使用中发现一个问题,和您一起探讨下: 在主要的四个函数tfdb_get/tfdb_set/tfdb_dual_get/tfdb_dual_set中的rw_buffer是至少4字节,但是例程中:

uint8_t test_buf[TFDB_ALIGNED_RW_BUFFER_SIZE(2,1)]; /*aligned_value_size*/

其中TFDB_ALIGNED_RW_BUFFER_SIZE的实现为:

#define TFDB_ALIGNED_RW_BUFFER_SIZE(VALUE_LENGTH, ALIGNED_SIZE)             ((VALUE_LENGTH + 1 + ALIGNED_SIZE) / (ALIGNED_SIZE))

带入数据得到(2+1+1)/2=2,而此时少于4字节,而且我认为这个应该由我们底层实现,或者让用户注意,而在README的例程并没有遵守这个概念吧?

PS:我认为其中VALUE_LENGTH是值的长度,比如uint16_t就是2字节;其中ALIGNED_SIZE是对齐的长度,由TFDB_WRITE_UNIT_BYTES决定;不知道是否正确?

smartmx commented 8 months ago

tfdb_dual_set 和 tfdb_dual_get 遵守下面的宏定义

#define TFDB_DUAL_ALIGNED_RW_BUFFER_SIZE(VALUE_LENGTH, ALIGNED_SIZE)        ((VALUE_LENGTH + 3 + ALIGNED_SIZE) / (ALIGNED_SIZE))

#define TFDB_DUAL_VALUE_LENGTH(VALUE_LENGTH)                                (VALUE_LENGTH + 2)

PS:我认为其中VALUE_LENGTH是值的长度,比如uint16_t就是2字节;其中ALIGNED_SIZE是对齐的长度,由TFDB_WRITE_UNIT_BYTES决定;不知道是否正确?

不是的,VALUE_LENGTH是你存储变量的长度,ALIGNED_SIZE是你定义变量的对齐宽度,uint32_t 就是 4, uint16_t 就是 2,uint8_t 就是 1。

这个不在底层实现的原因是:

  1. 51等平台不支持局部可变长度变量的定义。
  2. 有些平台写入flash时对数据的ram缓存地址有要求。
  3. 可以更快更大长度的写入数据,像easyflash很多时候会只写4个字节,重复的调用写入。比起作为一个整体写入,很浪费时间。
WangShuoran commented 8 months ago

tfdb_dual_set 和 tfdb_dual_get 遵守下面的宏定义

#define TFDB_DUAL_ALIGNED_RW_BUFFER_SIZE(VALUE_LENGTH, ALIGNED_SIZE)        ((VALUE_LENGTH + 3 + ALIGNED_SIZE) / (ALIGNED_SIZE))

#define TFDB_DUAL_VALUE_LENGTH(VALUE_LENGTH)                                (VALUE_LENGTH + 2)

PS:我认为其中VALUE_LENGTH是值的长度,比如uint16_t就是2字节;其中ALIGNED_SIZE是对齐的长度,由TFDB_WRITE_UNIT_BYTES决定;不知道是否正确?

不是的,VALUE_LENGTH是你存储变量的长度,ALIGNED_SIZE是你定义变量的对齐宽度,uint32_t 就是 4, uint16_t 就是 2,uint8_t 就是 1。

这个不在底层实现的原因是:

  1. 51等平台不支持局部可变长度变量的定义。
  2. 有些平台写入flash时对数据的ram缓存地址有要求。
  3. 可以更快更大长度的写入数据,像easyflash很多时候会只写4个字节,重复的调用写入。比起作为一个整体写入,很浪费时间。

我重复下,就是如果我存放一个uint16_t数组,在VALUE_LENGTH的值为数组个数*类型长度,而ALIGNED_SIZE代表类型长度。

然后您给出了不少于4字节不应该由库实现的原因,因为存在兼容问题以及其他代价。

所以,我的问题是,在README中要求四个函数tfdb_get/tfdb_set/tfdb_dual_get/tfdb_dual_set中的rw_buffer是至少4字节,但例程经过查看是两个字节,到底以谁为准? 详细来说,如果以个函数tfdb_get/tfdb_set/tfdb_dual_get/tfdb_dual_set中的rw_buffer是至少4字节为准,那么就需要例程中test_buf至少4字节,这个我们调用实现并不难做到;或者以test_buf可以两字节,那么就在rw_buffer不应该以4字节为准。

WangShuoran commented 8 months ago

这个不在底层实现的原因是:

  1. 51等平台不支持局部可变长度变量的定义。
  2. 有些平台写入flash时对数据的ram缓存地址有要求。
  3. 可以更快更大长度的写入数据,像easyflash很多时候会只写4个字节,重复的调用写入。比起作为一个整体写入,很浪费时间。

其中第三点您提到的意思是如果rw_buffer可以一次性完成更多数据的写入,而第二点提到一次性写入Flash的数据大小存在上限,这都感觉和rw_buffer是否至少4字节或者不限大小无关。我要重新描述我的提问,以防你误解,我问的是关于rw_buffer以什么数据为准,这涉及到空间申请,申请不够大小的数组空间会引起越界。 其中第一点,您提到在51等平台不支持局部可变长度变量的定义,也就是可变长数组,这个我倒是有点理解;我希望我能详细描述您表达的意思,看看是否正确:就是基于我们必须申请固定长数组,所以必须大于4字节并满足上面提到的TFDB_DUAL_VALUE_LENGTH空间要求(两者选多的那个),这是不是可以理解README的例程在某些情况可用,但实际运用时,如果是固定数组长度,长度必须大于4字节并满足TFDB_DUAL_VALUE_LENGTH空间要求(两者选多的那个)?

smartmx commented 8 months ago

是的,最少满足4个字节或者8个字节。之前的确写漏了,现在宏定义已经更新。

WangShuoran commented 8 months ago

好的,我看到您仅仅更新到tinyflashdb.h中: image 那README中的应该会用新的计算方式: image 那么我重新计算uint8_t test_buf[TFDB_ALIGNED_RW_BUFFER_SIZE(2,1)];发现:(2+3+1)/(1)=6,结果正确

WangShuoran commented 8 months ago

是的,最少满足4个字节或者8个字节。之前的确写漏了,现在宏定义已经更新。

请问您这个是的代表我前面两个回答理解都正确吗?

我认为我的ALIGNED_SIZE代表类型长度的理解并不正确,因为此时你RAEDME依然保持

uint8_t test_buf[TFDB_ALIGNED_RW_BUFFER_SIZE(2,1)];

而且,例程中的test_value的类型是uint16_t类型,所以是我理解错了还是README依然需要修改?

WangShuoran commented 8 months ago

您可以告诉我正确的对参数的理解,如果您这段时间没空,告诉我理解后,我综合修改后提交PR吧,这些适合在PR中讨论

WangShuoran commented 8 months ago

如果ALIGNED_SIZE确实代表类型长度,此时依照最新的宏定义,VALUE_LENGTH最小和ALIGNED_SIZE相同,此时满足下面曲线

image

会无限逼近于2他,也是不满足条件的 (这个回答是我对另外一种回答的思考)

smartmx commented 8 months ago

类似于定义一个buf时:

uint16_t test_buf [ TFDB_ALIGNED_RW_BUFFER_SIZE(2, sizeof(uint16_t) ];

uint32_t test_buf [ TFDB_ALIGNED_RW_BUFFER_SIZE(2, sizeof(uint32_t) ];

uint8_t test_buf [ TFDB_ALIGNED_RW_BUFFER_SIZE(2, sizeof(uint8_t) ];

Readme中没有uint16_t定义 rw_buf 吧?

这个是你用来定义缓存区大小的类型的大小。

WangShuoran commented 8 months ago

具体在这里,这里提到存储数据是2字节,那么依照下面我的问题和您的回答: image image 可以得到后面的这个是指变量的类型长度吧,那么下面README中提到的这个1就不对吧? image

smartmx commented 8 months ago

test_buf 是使用的 uint8_t 定义的,1 是没问题的。test_value是uint16_t定义的,这个参数和 test_value无关。 这个参数不是你存储的变量的长度,这个参数是你定义 rw_buffer 所使用的类型的长度。

WangShuoran commented 8 months ago

好的,那么我们的test_buf所使用的变量类型是基于什么设置的呢?应该不是可以顺便设置的吧?

是不是基于TFDB_WRITE_UNIT_BYTES是flash写入的单位来设置的,上面你提到有的flash支持一次写入一个字节,有的flash只能一次写入4字节,这个时候软件需要将写入的地址对齐。

smartmx commented 8 months ago

这个是看客户自己想怎么定义就怎么定义的。结合编译器特性等,能定义出满足硬件使用条件的即可。

WangShuoran commented 8 months ago

谢谢,再确认下在VALUE_LENGTH的值为tfdb_get的最后一个参数长度,如果存放的是数组数据,那么空间为数组个数*类型长度?

smartmx commented 8 months ago

就是

sizeof()

那个变量的值。

uint16_t test[100]; 那么value_length就是200.

WangShuoran commented 8 months ago

我已经测试可用,是否可以生成个版本,把上面这个问题解决的代码包进去

smartmx commented 8 months ago

目前版本还没更新,但是代码已经在主线里了,不需要在意这个吧,后面还有个tfdb_dual_get_pre没写好,写完后才会更新版本到0.0.8。

WangShuoran commented 8 months ago

嗯嗯,不在意的,您先修改