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,这里检索策略主要是:

  1. 确定当前选择的扇区剩余容量充足

  2. 优选选择正在使用状态的扇区,最后使用空状态扇区

  3. 检查新 KV 是否有同名的 KV 存在,存在还需要额外执行删除旧值的动作

  • 通过上图可以看出, KV1、KV2 及 KV3 已经被放入 sector1 ,添加后,扇区状态也被修改为正在使用。

27.2.3. 3.修改 KV2 KV3,删除 KV1,添加 KV4

  • 修改 ENV 时,旧的 ENV 将被删除,扇区的状态也将被修改为脏状态,然后再执行新增 ENV 的操作。

  1. 执行修改 KV2 时,已经存在的 KV2 旧值被修改为已删除,sector1 状态被修改为脏状态,此后将 KV2 新值放入 sector1,发现 sector1 已经没有空间了,sector1 的状态还会被修改为已满状态;

  2. 执行修改 KV3 时,已经存在的 KV3 旧值被修改为已删除,sector1 状态已经为脏状态,无需再做修改。经过查找发现 KV3 的新值只能放到 sector2,放到 sector2 后将其修改为正在使用状态;

  3. 执行删除 KV1 时,找到 KV1 的位置,将其修改为已删除状态,sector1 状态已经为脏状态,无需再做修改;

  4. 执行添加 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次。

  1. 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);
  1. 读写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);
}
  1. 测试结果如下图:

横坐标:boot times (单位:次数)

纵坐标:时间(单位:us)

红色线:easyflash 初始化耗时

绿色线:easyflash 写耗时

黄色线:读easyflash耗时

27.3.2. 2. 测试分析

  1. easyflash_init过程包含读和其他操作,故初始化时间与读时间相关。图中第一次出现尖峰现象说明此时easyflash在检查并格式化扇区,详见: 首次使用

  2. 读过程分析:由于easyflash4每write一次kv(写KV详细过程见: 添加KV),都会在old_kv地址后新增一个kv,再将old_kv标记为“delete”,所以每读一次kv,都需要遍历一遍kv,write次数越多,读耗时越长。

  3. 写过程分析:写之前都需要read找到kv(修改KV详细过程见: 修改KV),本次测试write在read之后,每read一次后easyflash会更新到cache,故write的时间并没有与read呈线性关系。

  4. 图中可见,在boottimes在688次左右时,读写操作时间“初始化”了,同时write的时间出现尖峰,此时触发了GC(触发GC过程详见: 触发GC),说明flash的大小已经快操作尽,只剩一个空闲sector。