27. Easyflash4 boot times¶
27.1. 总览¶
本示例主要介绍Easyflash4 启动读写测试相关
27.2. 算法实现¶
27.2.1. 1.首次使用¶
假定 ENV 分区里有 4 个扇区,以下将按照操作 ENV 的方式,逐一举例讲解不同操作下,对应的 Flash 状态及数据变化。
首次使用时,EasyFlash 会检查各个扇区的 header,如果不符合规定的格式将执行全部格式化操作,格式化后,每个扇区的顶部将被存入 header ,负责记录当前扇区的状态、魔数等信息。格式化的初始化状态为空状态。
27.2.2. 2.添加 KV1、KV2、KV3¶
在执行添加操作前,会先检索合适地址来存放即将添加的新 KV,这里检索策略主要是:
确定当前选择的扇区剩余容量充足
优选选择正在使用状态的扇区,最后使用空状态扇区
检查新 KV 是否有同名的 KV 存在,存在还需要额外执行删除旧值的动作
通过上图可以看出, KV1、KV2 及 KV3 已经被放入 sector1 ,添加后,扇区状态也被修改为正在使用。
27.2.3. 3.修改 KV2 KV3,删除 KV1,添加 KV4¶
修改 ENV 时,旧的 ENV 将被删除,扇区的状态也将被修改为脏状态,然后再执行新增 ENV 的操作。
执行修改 KV2 时,已经存在的 KV2 旧值被修改为已删除,sector1 状态被修改为脏状态,此后将 KV2 新值放入 sector1,发现 sector1 已经没有空间了,sector1 的状态还会被修改为已满状态;
执行修改 KV3 时,已经存在的 KV3 旧值被修改为已删除,sector1 状态已经为脏状态,无需再做修改。经过查找发现 KV3 的新值只能放到 sector2,放到 sector2 后将其修改为正在使用状态;
执行删除 KV1 时,找到 KV1 的位置,将其修改为已删除状态,sector1 状态已经为脏状态,无需再做修改;
执行添加 KV4 时,经过查找在 sector2 找到合适的存储位置,将其添加后,sector2 状态已经为正在使用状态,无需再做修改。
27.2.4. 4.添加 KV5 KV6,触发 GC (Garbage Clear)¶
执行添加 KV5 操作,由于 KV5 体积较大,sector2 放不下,所以只能放在一个新扇区 sector3 上,添加后,修改 sector3 状态为正在使用 ;
执行添加 KV6 操作,KV6 也只能放在 sector3 下,将其放入 sector 3 后,发现 sector3 空间已满,所以将其修改已满状态。执行完成后,发现整个 ENV 的 4 个扇区只有 1 个状态为空的扇区了,这个扇区如果再继续使用就没法再执行 GC 操作了,所以此时触发了 GC 请求;
执行 GC 请求,EasyFlash 会找到所有被标记为已满并且为脏状态的扇区,并将其内部的 ENV 搬运至其他位置。就这样 sector1 上的 KV2 被搬运至了 sector2,腾空 sector1 后,又对其执行了格式化操作,这样整个 ENV 分区里又多了一个空状态的扇区。
27.3. boot times测试¶
27.3.1. 1. 测试流程以及效果¶
测试流程为:easyflash初始化 → 读boottimes → boottimes++ → 写boottimes,反复复位重启800次。
easyflash初始化
uint32_t timer_us;
timer_us = bl_timer_now_us();
easyflash_init();
timer_us = bl_timer_now_us() - timer_us;
printf("easyflash init time us %ld\r\n", timer_us);
读写boottimes
static void __easyflash_boottimes_dump()
{
char *times = NULL;
uint32_t times_num = 0;
char env_set[12] = {0};
uint32_t timer_us;
timer_us = bl_timer_now_us();
times = ef_get_env(EASYFLASH_BOOT_TIMES);
timer_us = bl_timer_now_us() - timer_us;
printf("easyflash read boot_times us %ld\r\n", timer_us);
if (times == NULL) {
__easyflash_first_boottimes();
return;
}
times_num = atoi(times);
sprintf(env_set, "%ld", ++times_num);
timer_us = bl_timer_now_us();
ef_set_env(EASYFLASH_BOOT_TIMES, env_set);
ef_save_env();
timer_us = bl_timer_now_us() - timer_us;
printf("easyflash write boot_times us %ld\r\n", timer_us);
printf("The system now boot times %ld\r\n", times_num);
}
测试结果如下图:
横坐标:boot times (单位:次数)
纵坐标:时间(单位:us)
红色线:easyflash 初始化耗时
绿色线:easyflash 写耗时
黄色线:读easyflash耗时
27.3.2. 2. 测试分析¶
easyflash_init过程包含读和其他操作,故初始化时间与读时间相关。图中第一次出现尖峰现象说明此时easyflash在检查并格式化扇区,详见: 首次使用。
读过程分析:由于easyflash4每write一次kv(写KV详细过程见: 添加KV),都会在old_kv地址后新增一个kv,再将old_kv标记为“delete”,所以每读一次kv,都需要遍历一遍kv,write次数越多,读耗时越长。
写过程分析:写之前都需要read找到kv(修改KV详细过程见: 修改KV),本次测试write在read之后,每read一次后easyflash会更新到cache,故write的时间并没有与read呈线性关系。
图中可见,在boottimes在688次左右时,读写操作时间“初始化”了,同时write的时间出现尖峰,此时触发了GC(触发GC过程详见: 触发GC),说明flash的大小已经快操作尽,只剩一个空闲sector。