移植案例与原理 - utils子系统之KV存储部件 (1) 原创 精华

zhushangyuan_
发布于 2022-1-30 08:32
浏览
1收藏

移植案例与原理 - utils子系统之KV存储部件

Utils子系统是OpenHarmony的公共基础库,存放OpenHarmony通用的基础组件。这些基础组件可被OpenHarmony各业务子系统及上层应用所使用。公共基础库在不同平台上提供的能力:

  • LiteOS-M内核:KV(key value)存储、文件操作、定时器、Dump系统属性。
  • LiteOS-A内核:KV(key value)存储、定时器、JS API(设备查询,数据存储)、Dump系统属性。

本文介绍下移植开发板时如何适配utils子系统之KV存储部件,并介绍下相关的运行机制原理。KV存储部件定义在utils\native\lite\。源代码目录如下:

utils/native/lite/              # 公共基础库根目录
├── file                        # 文件接口实现
├── hals                        # HAL目录
│   └── file                    # 文件操作硬件抽象层头文件
├── include                     # 公共基础库对外接口文件
├── js                          # JS API目录                 
│   └── builtin
│       ├── common
│       ├── deviceinfokit       # 设备信息Kit
│       ├── filekit             # 文件Kit
│       └── kvstorekit          # KV存储Kit
├── kal                         # KAL目录
│   └── timer                   # Timer的KAL实现
├── kv_store	                # KV存储实现
│   ├── innerkits               # KV存储内部接口
│   └── src	                    # KV存储源文件
├── memory
│   └── include                 # 内存池管理接口
├── os_dump                     # Dump系统属性
└── timer_task                  # Timer实现

1、KV存储部件适配示例

1.1 配置产品解决方案config.json

utils子系统之KV存储部件的适配示例可以参考vendor\ohemu\qemu_csky_mini_system_demo\config.json,代码片段如下。⑴处用于配置子系统的KV存储部件。⑵处指定在开发板目录中适配目录,这个适配目录下需要创建目录device\qemu\SmartL_E802\adapter\hals\utils\file\,之前的移植案例与原理文章中介绍过vendor_adapter_dir目录。对于KV存储部件,与适配syspara_lite部件类似,适配kv_store部件时,键值对会写到文件中。在轻量系统中,文件操作相关接口有POSIX接口与HalFiles接口这两套实现。部件文件utils\native\lite\kv_store\src\BUILD.gn中声明了enable_ohos_utils_native_lite_kv_store_use_posix_kv_api配置参数,默认值为true。当使用默认值或主动设置为true时,使用POSIX相关的接口,否则使用HalFiles相关的接口。如果使用HalFiles相关的接口,需要适配UtilsFile部件,参考之前的移植案例与原理文章即可。

      {
        "subsystem": "utils",
        "components": [
          { "component": "file", "features":[] },
⑴        { "component": "kv_store", "features":[] }
        ]
      }
    ],
⑵   "vendor_adapter_dir": "//device/qemu/SmartL_E802/adapter",

1.2 适配后运行示例代码

适配后,编写如下代码,就可以使用KV存储功能。下面是示例代码程序片段,实现保存键值,通过key键获取对应的值,删除键值等等。

// 存储/更新key对应数据项
const char key1[] = "key_sample";
const char defValue[] = "test case of key value store.";
int ret = UtilsSetValue(key1, defValue);

// 根据key获取对应数据项
char value1[32] = {0};
ret = UtilsGetValue(key1, value1, 32);

// 删除key对应数据项
UtilsDeleteValue(key1);

2、KV存储部件kvstore_common通用代码

2.1 结构体、函数声明、变量

在文件utils\native\lite\kv_store\src\kvstore_common\kvstore_common.h中声明了KV存储的函数,并定义了结构体KvItem。⑴处定义了键值的最大长度,⑵处的FEATURE_KV_CACHE宏开关,定义在utils\native\lite\include\utils_config.h,默认是定义了该宏的。⑶处定义的结构体,成员包含键值,以及前驱后继结构体指针。

⑴  #define MAX_KEY_LEN 32
    #define MAX_VALUE_LEN 128

    boolean IsValidChar(const char ch);
    boolean IsValidValue(const char* value, unsigned int len);
    boolean IsValidKey(const char* key);

⑵  #ifdef FEATURE_KV_CACHE
⑶  typedef struct KvItem_ {
        char* key;
        char* value;
        struct KvItem_* next;
        struct KvItem_* prev;
    } KvItem;

    void DeleteKVCache(const char* key);
    void AddKVCache(const char* key, const char* value, boolean isNew);
    int GetValueByCache(const char* key, char* value, unsigned int maxLen);
    int ClearKVCacheInner(void);
    #endif

在文件utils\native\lite\kv_store\src\kvstore_common\kvstore_common.c中定义了内部全局变量,g_itemHeader、g_itemTail分别指向键值链表的首尾,g_sum记录键值对数量。

#ifdef FEATURE_KV_CACHE
static KvItem* g_itemHeader = NULL;
static KvItem* g_itemTail = NULL;
static int g_sum = 0;
#endif

2.2 键值有效性判断函数

函数IsValidKey、IsValidValue分别用于判断键、值是否为有效的。⑴处表明键值必须为小写的字符,数值,下划线或者点符号。使用IsValidValue判断值是否有效时,需要传入2个参数,一个是要判断的字符串值的指针,一个是长度len。⑵处获取字符串的个数,包含最后的null;不超过最大长度MAX_VALUE_LEN。然后进一步判断,如果长度为0,长度大于等于最大长度MAX_VALUE_LEN(因为需要末尾的null,等于也不行),或者大于参数中传递的长度时,都会返回FALSE,否则返回TRUE。使用IsValidKey判断键是否有效时,先调用函数IsValidValue确保长度是有效的,然后调用函数IsValidChar判断每一个字符都是有效的,只能是小写字符,数值或者点符号。

    boolean IsValidChar(const char ch)
    {
⑴      if (islower(ch) || isdigit(ch) || (ch == '_') || (ch == '.')) {
            return TRUE;
        }
        return FALSE;
    }

    boolean IsValidValue(const char* value, unsigned int len)
    {
        if (value == NULL) {
            return FALSE;
        }
⑵      size_t valueLen = strnlen(value, MAX_VALUE_LEN);
        if ((valueLen == 0) || (valueLen >= MAX_VALUE_LEN) || (valueLen >= len)) {
            return FALSE;
        }
        return TRUE;
    }

    boolean IsValidKey(const char* key)
    {
        if (!IsValidValue(key, MAX_KEY_LEN)) {
            return FALSE;
        }
        size_t keyLen = strnlen(key, MAX_KEY_LEN);
        for (size_t i = 0; i < keyLen; i++) {
            if (!IsValidChar(key[i])) {
                return FALSE;
            }
        }
        return TRUE;
    }

2.3 根据键删除值DeleteKVCache

⑴处的函数FreeItem释放结构体成员变量指针,结构体占用的内存。函数DeleteKVCache用于删除键参数对应的值。⑵处从键值对头部的第一个键值开始,循环键值链表,比对参数中的键和循环到的键。如果不相等,则循环下一个链表节点。如果一直不相等,并且循环到的节点为NULL,说明链表中不存在相同的键,直接返回不需要执行删除操作。如果执行到⑶,说明键值对中存在匹配的键,键值对总数减去1。⑷处对删键值后的数量的各种情况进行判断,如果键值对数量为0,键值对首尾指针设置为NULL;如果删除的是队首元素,队尾元素,队中元素,分别处理。⑸处释放要删除的结构体占用的内存。

⑴   static void FreeItem(KvItem* item)
    {
        if (item == NULL) {
            return;
        }
        if (item->key != NULL) {
            free(item->key);
        }
        if (item->value != NULL) {
            free(item->value);
        }
        free(item);
    }

    void DeleteKVCache(const char* key)
    {
        if (key == NULL || g_itemHeader == NULL) {
            return;
        }

⑵      KvItem* item = g_itemHeader;
        while (strcmp(key, item->key) != 0) {
            item = item->next;
            if (item == NULL) {
                return;
            }
        }
⑶      g_sum--;
⑷      if (g_sum == 0) {
            g_itemHeader = NULL;
            g_itemTail = NULL;
        } else if (item == g_itemHeader) {
            g_itemHeader = item->next;
            g_itemHeader->prev = NULL;
        } else if (item == g_itemTail) {
            g_itemTail = item->prev;
            g_itemTail->next = NULL;
        } else {
            item->prev->next = item->next;
            item->next->prev = item->prev;
        }
⑸      FreeItem(item);
    }

2.4 添加缓存AddKVCache

函数AddKVCache添加一对键值到缓存里。共三个参数,前两者为键和值;第三个参数boolean isNew为true时,会先尝试删除旧的键值对,只保留最新的键值数据。如果为false,可能存在键值相同的两个键值对,但是值不同。做完必要的参数非空校验后,执行⑴获取键、值的字符长度。⑵处处理是否删除旧的键值对数据。⑶处为键值对结构体申请内存区域,内存区域置空。⑷处为键、值分别申请内存区域,申请的时候多加1个字符长度用于保存null空字符。⑸处把参数传入的键值数据复制到键值对结构体对应的内存区域。⑹处理缓存内没有键值数据的情况。当缓存有键值信息时,新加入的放入键值对链表头部。⑻处当缓存数量大于最大缓存数时,依次从尾部删除。

void AddKVCache(const char* key, const char* value, boolean isNew)
{
    if (key == NULL || value == NULL) {
        return;
    }

⑴  size_t keyLen = strnlen(key, MAX_KEY_LEN);
    size_t valueLen = strnlen(value, MAX_VALUE_LEN);
    if ((keyLen >= MAX_KEY_LEN) || (valueLen >= MAX_VALUE_LEN)) {
        return;
    }
⑵  if (isNew) {
        DeleteKVCache(key);
    }
⑶  KvItem* item = (KvItem *)malloc(sizeof(KvItem));
    if (item == NULL) {
        return;
    }
    (void)memset_s(item, sizeof(KvItem), 0, sizeof(KvItem));
⑷  item->key = (char *)malloc(keyLen + 1);
    item->value = (char *)malloc(valueLen + 1);
    if ((item->key == NULL) || (item->value == NULL)) {
        FreeItem(item);
        return;
    }
⑸  if ((strcpy_s(item->key, keyLen + 1, key) != EOK) ||
        (strcpy_s(item->value, valueLen + 1, value) != EOK)) {
        FreeItem(item);
        return;
    }
    item->prev = NULL;
    item->next = NULL;
⑹  if (g_itemHeader == NULL) {
        g_itemHeader = item;
        g_itemTail = item;
        g_sum++;
        return;
    }
⑺  item->next = g_itemHeader;
    g_itemHeader->prev = item;
    g_itemHeader = item;
    g_sum++;
⑻  while (g_sum > MAX_CACHE_SIZE) {
        KvItem* needDel = g_itemTail;
        g_itemTail = g_itemTail->prev;
        FreeItem(needDel);
        g_itemTail->next = NULL;
        g_sum--;
    }
}

2.5 从缓存中获取值GetValueByCache

函数GetValueByCache用于从缓存中读取值。共三个参数,前两者为键和值,const char* ke为键,输入参数;char* value为输出参数,用于保存返回的值;第三个参数unsigned int maxLen用于限制获取的值的最大长度。该函数的返回值代表获取成功EC_SUCCESS或失败EC_FAILURE。做完必要的参数非空校验后,执行⑴循环键值对链表,获取对应键的键值结构体。如果获取不到,则返回EC_FAILURE;否则,执行⑵获取值的长度,当这个长度超出值的最大长度时,返回EC_FAILURE。⑶处,如果获取的值的长度超出参数传入的长度,不会截断,而是返回错误。从item->value把值复制到输出参数里,如果失败也会返回错误。

int GetValueByCache(const char* key, char* value, unsigned int maxLen)
{
    if (key == NULL || value == NULL || g_itemHeader == NULL) {
        return EC_FAILURE;
    }

    KvItem* item = g_itemHeader;
⑴  while (strcmp(key, item->key) != 0) {
        item = item->next;
        if (item == NULL) {
            return EC_FAILURE;
        }
    }
⑵  size_t valueLen = strnlen(item->value, MAX_VALUE_LEN);
    if (valueLen >= MAX_VALUE_LEN) {
        return EC_FAILURE;
    }
⑶  if ((valueLen >= maxLen) || (strcpy_s(value, maxLen, item->value) != EOK)) {
        return EC_FAILURE;
    }
    return EC_SUCCESS;
}

2.6 清除缓存ClearKVCacheInner

清除缓存函数ClearKVCacheInner会把缓存的键值对全部清空,返回清除成功或失败的返回值。⑴如果键值对链表头节点为空,返回成功。⑵处循环键值对链表每一个键值对元素,一一删除。每删除一个,执行⑶,把基础缓存的键值对数目减1。

int ClearKVCacheInner(void)
{
⑴  if (g_itemHeader == NULL) {
        return EC_SUCCESS;
    }
    KvItem* item = g_itemHeader;
⑵  while (item != NULL) {
        KvItem* temp = item;
        item = item->next;
        FreeItem(temp);
⑶      g_sum--;
    }
    g_itemHeader = NULL;
    g_itemTail = NULL;

    return (g_sum != 0) ? EC_FAILURE : EC_SUCCESS;
}

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2022-3-7 11:37:57修改
4
收藏 1
回复
举报
4条回复
按时间正序
/
按时间倒序
mb609898e2cfb86
mb609898e2cfb86

前排学习一波

回复
2022-1-30 09:10:10
zhushangyuan_
zhushangyuan_ 回复了 mb609898e2cfb86
前排学习一波

新年快乐……

回复
2022-1-30 09:59:13
Whyalone
Whyalone 回复了 zhushangyuan_
新年快乐……

新年快乐

 

回复
2022-1-30 10:51:45
zhushangyuan_
zhushangyuan_ 回复了 Whyalone
新年快乐

浩总新年快乐

回复
2022-1-30 11:34:45
回复
    相关推荐