写点什么

OpenHarmony 移植:如何适配 utils 子系统之 KV 存储部件

  • 2022 年 2 月 25 日
  • 本文字数:14093 字

    阅读完需:约 46 分钟

本文分享自华为云社区《OpenHarmony移植案例与原理 - utils子系统之KV存储部件》,作者: zhushy。


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_CACHEstatic 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;}
复制代码

3、KV 存储部件对外接口

在文件 utils\native\lite\include\kv_store.h 中定义了 KV 存储部件对外接口,如下,支持从键值对缓存里读取键值,设置键值,删除键值,清除缓存等等。

int UtilsGetValue(const char* key, char* value, unsigned int len);
int UtilsSetValue(const char* key, const char* value);
int UtilsDeleteValue(const char* key);
#ifdef FEATURE_KV_CACHEint ClearKVCache(void);#endif
复制代码

在文件 utils\native\lite\kv_store\innerkits\kvstore_env.h 中定义了如下接口,在使用 POSIX 接口时,需要首先使用接口需要设置数据文件路径。使用 UtilsFile 接口时,不需要该接口。

int UtilsSetEnv(const char* path);
复制代码

4、KV 存储部件对应 POSIX 接口部分的代码

分析下 KV 存储部件对应 POSIX 接口部分的代码。我们知道对外接口有设置键值 UtilsSetValue、获取键值 UtilsGetValue、删除键值 UtilsDeleteValue 和清除缓存 ClearKVCache。我们先看看内部接口。

4.1 内部接口

4.1.1 GetResolvedPath 解析路径

函数 GetResolvedPath 用于解析文件路径,根据键名 key 组装存放值 value 的文件路径。需要 4 个参数,第一个参数 char* dataPath 为键值对保存的文件路径,在使用 KV 特性前由 UtilsSetEnv 函数设置到全局变量里 g_dataPath;第二个参数为键 char* key;第三个参数 char* resolvedPath 为解析后的路径,为输出参数;第 4 个参数 unsigned int len 为路径长度。看下代码,⑴处为解析的路径申请内存,⑵处拼装键值对的文件路径,格式为"XXX/kvstore/key"。⑶将相对路径转换成绝对路径,如果解析成功,会把文件路径解析到输出参数 resolvedPath。⑷处如果执行 realpath 函数出错,指定的文件不存在,会执行⑸把 keyPath 复制到输出函数 resolvedPath。

static int GetResolvedPath(const char* dataPath, const char* key, char* resolvedPath, unsigned int len){⑴  char* keyPath = (char *)malloc(MAX_KEY_PATH + 1);    if (keyPath == NULL) {        return EC_FAILURE;    }⑵  if (sprintf_s(keyPath, MAX_KEY_PATH + 1, "%s/%s/%s", dataPath, KVSTORE_PATH, key) < 0) {        free(keyPath);        return EC_FAILURE;    }⑶  if (realpath(keyPath, resolvedPath) != NULL) {        free(keyPath);        return EC_SUCCESS;    }⑷  if (errno == ENOENT) {⑸      if (strncpy_s(resolvedPath, len, keyPath, strlen(keyPath)) == EOK) {            free(keyPath);            return EC_SUCCESS;        }    }    free(keyPath);    return EC_FAILURE;}
复制代码

4.1.2 GetValueByFile 从文件中读取键值

函数 GetValueByFile 从文件中读取键对应的值,需要 4 个参数,第一个参数为键值文件存放的目录路径;第二个参数为键;第三个为输出参数,存放获取的键的值;第 4 个参数为输出参数的长度。该函数返回值为 EC_FAILURE 或成功获取的值的长度。⑴处获取对应键名 key 的文件路径,⑵处读取文件的状态信息。因为文件内容是键对应的值,⑶处表明如果值的大小大于等于参数 len,则返回错误码。等于也不行,需要 1 个字符长度存放 null 字符用于结尾。⑷处打开文件,然后读取文件,内容会存入输出参数 value 里。⑸处设置字符串结尾的 null 字符。

static int GetValueByFile(const char* dataPath, const char* key, char* value, unsigned int len){    char* keyPath = (char *)malloc(PATH_MAX + 1);    if (keyPath == NULL) {        return EC_FAILURE;    }⑴  if (GetResolvedPath(dataPath, key, keyPath, PATH_MAX + 1) != EC_SUCCESS) {        free(keyPath);        return EC_FAILURE;    }    struct stat info = {0};⑵  if (stat(keyPath, &info) != F_OK) {        free(keyPath);        return EC_FAILURE;    }⑶  if (info.st_size >= len) {        free(keyPath);        return EC_FAILURE;    }⑷  int fd = open(keyPath, O_RDONLY, S_IRUSR);    free(keyPath);    keyPath = NULL;    if (fd < 0) {        return EC_FAILURE;    }    int ret = read(fd, value, info.st_size);    close(fd);    fd = -1;    if (ret < 0) {        return EC_FAILURE;    }⑸  value[info.st_size] = '\0';    return info.st_size;}
复制代码

4.1.3 SetValueToFile\DeleteValueFromFile 存入\删除键值

函数 SetValueToFile 同于把键值存入文件,函数 DeleteValueFromFile 则用于删除键值。⑴处根据键名获取存放值的文件路径 keyPath,⑵处打开文件,然后写入键名对应的值。在函数 DeleteValueFromFile 中,⑶处先组装路径,然后删除文件。

static int SetValueToFile(const char* dataPath, const char* key, const char* value){    char* keyPath = (char *)malloc(PATH_MAX + 1);    if (keyPath == NULL) {        return EC_FAILURE;    }⑴  if (GetResolvedPath(dataPath, key, keyPath, PATH_MAX + 1) != EC_SUCCESS) {        free(keyPath);        return EC_FAILURE;    }⑵  int fd = open(keyPath, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);    free(keyPath);    keyPath = NULL;    if (fd < 0) {        return EC_FAILURE;    }    int ret = write(fd, value, strlen(value));    close(fd);    fd = -1;    return (ret < 0) ? EC_FAILURE : EC_SUCCESS;}
static int DeleteValueFromFile(const char* dataPath, const char* key){ char* keyPath = (char *)malloc(MAX_KEY_PATH + 1); if (keyPath == NULL) { return EC_FAILURE; }⑶ if (sprintf_s(keyPath, MAX_KEY_PATH + 1, "%s/%s/%s", dataPath, KVSTORE_PATH, key) < 0) { free(keyPath); return EC_FAILURE; } int ret = unlink(keyPath); free(keyPath); return ret;}
复制代码

4.1.4 InitKv 创建 kvstore 目录

函数 InitKv 确保保存键值时,kvstore 目录被创建,用于存放键值文件。⑴处组装 kvstore 目录,⑵处使用 F_OK 参数判断目录是否存在,如果存在返回 EC_SUCCESS。否则执行⑶创建 kvstore 目录。

static int InitKv(const char* dataPath){    if (dataPath == NULL) {        return EC_FAILURE;    }    char* kvPath = (char *)malloc(MAX_KEY_PATH + 1);    if (kvPath == NULL) {        return EC_FAILURE;    }⑴  if (sprintf_s(kvPath, MAX_KEY_PATH + 1, "%s/%s", dataPath, KVSTORE_PATH) < 0) {        free(kvPath);        return EC_FAILURE;    }⑵  if (access(kvPath, F_OK) == F_OK) {        free(kvPath);        return EC_SUCCESS;    }⑶  if (mkdir(kvPath, S_IRUSR | S_IWUSR | S_IXUSR) != F_OK) {        free(kvPath);        return EC_FAILURE;    }    free(kvPath);    return EC_SUCCESS;}
复制代码

4.1.5 GetCurrentItem 获取当前的键值对数目

函数 GetCurrentItem 用于获取当前的键值对数目。首先,组装目录路径"XXX/kvstore",然后执行⑴打开目录,然后读取目录项。⑵循环每一个目录项,判断键值对的数量。⑶处组装 kvstore 目录下每一个键的文件路径,然后获取每个文件的状态信息。⑷如果文件是常规普通文件,则键值对数量加 1。然后读取 kvstore 目录下的下一个目录项,依次循环。

static int GetCurrentItem(const char* dataPath){    char kvPath[MAX_KEY_PATH + 1] = {0};    if (sprintf_s(kvPath, MAX_KEY_PATH + 1, "%s/%s", dataPath, KVSTORE_PATH) < 0) {        return EC_FAILURE;    }⑴  DIR* fileDir = opendir(kvPath);    if (fileDir == NULL) {        return EC_FAILURE;    }    struct dirent* dir = readdir(fileDir);    int sum = 0;⑵  while (dir != NULL) {        char fullPath[MAX_KEY_PATH + 1] = {0};        struct stat info = {0};⑶      if (sprintf_s(fullPath, MAX_KEY_PATH + 1, "%s/%s", kvPath, dir->d_name) < 0) {            closedir(fileDir);            return EC_FAILURE;        }        if (stat(fullPath, &info) != 0) {            closedir(fileDir);            return EC_FAILURE;        }⑷      if (S_ISREG(info.st_mode)) {            sum++;        }        dir = readdir(fileDir);    }    closedir(fileDir);    return sum;}
复制代码

4.1.6 NewItem 判断是否新键值对

函数 NewItem 可以用于判断是否新的键值对。⑴处获取键名对应的文件路径,⑵处判断文件是否存在,存在则返回 FALSE;不存在键值对则返回 TRUE。

static boolean NewItem(const char* dataPath, const char* key){    char* keyPath = (char *)malloc(MAX_KEY_PATH + 1);    if (keyPath == NULL) {        return FALSE;    }⑴  if (sprintf_s(keyPath, MAX_KEY_PATH + 1, "%s/%s/%s", dataPath, KVSTORE_PATH, key) < 0) {        free(keyPath);        return FALSE;    }⑵  if (access(keyPath, F_OK) == F_OK) {        free(keyPath);        return FALSE;    }    free(keyPath);    return TRUE;}
复制代码

4.2 读取键值 UtilsGetValue

函数 UtilsSetValue 用于读取键名对应的值,第一个参数为输入参数键名,第二个参数为输出参数键名对应的值,第三个参数为值的字符串长度。⑴处获取键值对所在的路径,注意互斥锁的使用。如果支持键值缓存,则执行⑵尝试从缓存中读取。缓存中不能读取时,继续执行⑶从文件中读取。如果读取成功,则执行⑷,加入缓存中,注意第三个参数为 FALSE。读取时,会把读取到的键值对,放到缓存的键值对链表的头部,但不删除之前的键值对数据。

int UtilsGetValue(const char* key, char* value, unsigned int len){    if (!IsValidKey(key) || (value == NULL) || (len > MAX_GET_VALUE_LEN)) {        return EC_INVALID;    }    pthread_mutex_lock(&g_kvGlobalMutex);⑴  const char* dataPath = g_dataPath;    if (dataPath == NULL) {        pthread_mutex_unlock(&g_kvGlobalMutex);        return EC_FAILURE;    }#ifdef FEATURE_KV_CACHE⑵  if (GetValueByCache(key, value, len) == EC_SUCCESS) {        pthread_mutex_unlock(&g_kvGlobalMutex);        return EC_SUCCESS;    }#endif⑶  int ret = GetValueByFile(dataPath, key, value, len);    if (ret < 0) {        pthread_mutex_unlock(&g_kvGlobalMutex);        return EC_FAILURE;    }#ifdef FEATURE_KV_CACHE⑷  AddKVCache(key, value, FALSE);#endif    pthread_mutex_unlock(&g_kvGlobalMutex);    return ret;}
复制代码

4.3 设置键值 UtilsGetValue

函数 UtilsSetValue 用于保存一对键值,⑴处确保 kvstore 目录存在,不存在则创建。⑵处用于获取 kvstore 目录下键值对的数目。g_getKvSum 默认为 FALSE,只需要获取一次即可,键值对数目保存在全局变量 g_kvSum。⑶处判断是否新的键值对,如果键值对数目超过缓存允许的最大数,并且需要设置的是新的缓存则返回 EC_FAILURE。⑷处把键值对保存到文件中,如果支持缓存,还需要存入缓存中。注意 AddKVCache 存入缓存的第三方参数为 TRUE,会先删除之前同一个键名对应的键值对。⑸处如果是新的键值对,键值对数目需要加 1。

int UtilsSetValue(const char* key, const char* value){    if (!IsValidKey(key) || !IsValidValue(value, MAX_VALUE_LEN)) {        return EC_INVALID;    }    pthread_mutex_lock(&g_kvGlobalMutex);    const char* dataPath = g_dataPath;⑴  int ret = InitKv(dataPath);    if (ret != EC_SUCCESS) {        g_getKvSum = FALSE;        pthread_mutex_unlock(&g_kvGlobalMutex);        return EC_FAILURE;    }⑵  if (!g_getKvSum) {        g_kvSum = GetCurrentItem(dataPath);        if (g_kvSum < 0) {            pthread_mutex_unlock(&g_kvGlobalMutex);            return EC_FAILURE;        }        g_getKvSum = TRUE;    }⑶  boolean newItem = NewItem(dataPath, key);    if ((g_kvSum >= MAX_KV_SUM) && newItem) {        pthread_mutex_unlock(&g_kvGlobalMutex);        return EC_FAILURE;    }⑷  ret = SetValueToFile(dataPath, key, value);    if (ret == EC_SUCCESS) {#ifdef FEATURE_KV_CACHE        AddKVCache(key, value, TRUE);#endif        if (newItem) {⑸          g_kvSum++;        }    }    pthread_mutex_unlock(&g_kvGlobalMutex);    return ret;}
复制代码

4.4 删除键值 UtilsDeleteValue

函数 UtilsDeleteValue 用于删除一对键值。⑴处如果支持键值缓存,则首先尝试从缓存中删除键值对。⑵处从文件中删除键值,如果删除超过,键值对数目减 1。

int UtilsDeleteValue(const char* key){    if (!IsValidKey(key)) {        return EC_INVALID;    }    pthread_mutex_lock(&g_kvGlobalMutex);    const char* dataPath = g_dataPath;    if (dataPath == NULL) {        pthread_mutex_unlock(&g_kvGlobalMutex);        return EC_FAILURE;    }#ifdef FEATURE_KV_CACHE⑴  DeleteKVCache(key);#endif⑵  int ret = DeleteValueFromFile(dataPath, key);    if (ret == EC_SUCCESS) {        g_kvSum--;    }    pthread_mutex_unlock(&g_kvGlobalMutex);    return ret;}
复制代码

4.5 清除键值缓存 ClearKVCache 和设置缓存路径 UtilsSetEnv

函数 ClearKVCache 用于清除缓存,直接调用接口 ClearKVCacheInner 完成。函数 UtilsSetEnv 用于设置键值对的保存路径,维护在全局变量 g_dataPath 里。

#ifdef FEATURE_KV_CACHEint ClearKVCache(void){    pthread_mutex_lock(&g_kvGlobalMutex);    int ret = ClearKVCacheInner();    pthread_mutex_unlock(&g_kvGlobalMutex);    return ret;}#endif
int UtilsSetEnv(const char* path){ if (path == NULL) { return EC_FAILURE; } pthread_mutex_lock(&g_kvGlobalMutex); int ret = strcpy_s(g_dataPath, MAX_KEY_PATH + 1, path); pthread_mutex_unlock(&g_kvGlobalMutex); return (ret != EOK) ? EC_FAILURE : EC_SUCCESS;}
复制代码

5、KV 存储部件对应 UtilsFile 接口部分的代码

分析下 KV 存储部件对应 UtilsFile 接口部分的代码。我们知道对外接口有设置键值 UtilsSetValue、获取键值 UtilsGetValue、删除键值 UtilsDeleteValue 和清除缓存 ClearKVCache。我们先看看内部接口,这些接口调用的全部是 UtilsFile 接口,没有使用 POSIX 的文件接口。

5.1 内部接口

5.1.1 GetValueByFile 和 SetValueToFile 从文件中读写键值

函数 GetValueByFile 用于从文件中读取键值,⑴处获取键名对应的键值文件的大小,如果文件大于等于参数中指定的长度 len,返回 EC_FAILURE。等于也不行,末尾需要放置一个空字符。⑵处打开文件,然后读取文件,读取的内容放入变量 value 里。⑶处末尾添加 null 空字符,然后返回获取的字符的长度。函数 SetValueToFile 用于把键值保存到文件里,⑷处调用 UtilsFile 接口打开,然后写入到文件里。

static int GetValueByFile(const char* key, char* value, unsigned int len){    unsigned int valueLen = 0;⑴  if (UtilsFileStat(key, &valueLen) != EC_SUCCESS) {        return EC_FAILURE;    }    if (valueLen >= len) {        return EC_FAILURE;    }⑵  int fd = UtilsFileOpen(key, O_RDONLY_FS, 0);    if (fd < 0) {        return EC_FAILURE;    }    int ret = UtilsFileRead(fd, value, valueLen);    UtilsFileClose(fd);    fd = -1;    if (ret < 0) {        return EC_FAILURE;    }⑶  value[valueLen] = '\0';    return valueLen;}
static int SetValueToFile(const char* key, const char* value){⑷ int fd = UtilsFileOpen(key, O_RDWR_FS | O_CREAT_FS | O_TRUNC_FS, 0); if (fd < 0) { return EC_FAILURE; } int ret = UtilsFileWrite(fd, value, strlen(value)); UtilsFileClose(fd); fd = -1; return (ret < 0) ? EC_FAILURE : EC_SUCCESS;}
复制代码

5.1.2 GetCurrentItem 和 SetCurrentItem 获取设置键值对数量

函数 GetCurrentItem 用于获取键值对数量,⑴处可以看出,键值对数目保存在文件 KV_FILE_SUM 里。从文件里读取的键值对数量会放入⑵处的字符串里,字符串的长度为 4,所以键值对的数量能是 K 级。然后执行 UtilsFileRead 读取文件内容,然后通过 atoi 函数转换为数值。函数 SetCurrentItem 用于更新键值对数量,保存到文件里。⑷处把整形的参数转换为字符串,然后打开文件 KV_FILE_SUM,并写入。

#define KV_SUM_FILE        "KV_FILE_SUM"#define KV_SUM_INDEX       4......static int GetCurrentItem(void){⑴  int fd = UtilsFileOpen(KV_SUM_FILE, O_RDWR_FS, 0);    if (fd < 0) {        return 0;    }⑵  char value[KV_SUM_INDEX] = {0};    int ret = UtilsFileRead(fd, value, KV_SUM_INDEX);    UtilsFileClose(fd);    fd = -1;⑶  return (ret < 0) ? 0 : atoi(value);}
static int SetCurrentItem(const int num){ char value[KV_SUM_INDEX] = {0};⑷ if (sprintf_s(value, KV_SUM_INDEX, "%d", num) < 0) { return EC_FAILURE; } int fd = UtilsFileOpen(KV_SUM_FILE, O_RDWR_FS | O_CREAT_FS, 0); if (fd < 0) { return EC_FAILURE; } int ret = UtilsFileWrite(fd, value, KV_SUM_INDEX); UtilsFileClose(fd); fd = -1; return (ret < 0) ? EC_FAILURE : EC_SUCCESS;}
复制代码

5.1.3 NewItem 判断是否新键值对

函数 NewItem 用于判断给定的键名是否新的键值对,是否已经存在同样的键名。调用函数 UtilsFileOpen,如果能打开,说明文件已经存在,否则不存在。

static boolean NewItem(const char* key){    int fd = UtilsFileOpen(key, O_RDONLY_FS, 0);    if (fd < 0) {        return TRUE;    }    UtilsFileClose(fd);    return FALSE;}
复制代码

5.2 获取键值 UtilsGetValue

函数 UtilsGetValue 用于从文件中读取键值,传入键名 key,读出的值保存在参数 value,len 设置读取的值的长度。如果支持键值对缓存,则执行⑴尝试从缓存中读取,否则执行⑵从文件中读取。读取成功后,会执行⑶把读取的键值加入缓存。

int UtilsGetValue(const char* key, char* value, unsigned int len){    if (!IsValidKey(key) || (value == NULL) || (len > MAX_GET_VALUE_LEN)) {        return EC_INVALID;    }#ifdef FEATURE_KV_CACHE⑴  if (GetValueByCache(key, value, len) == EC_SUCCESS) {        return EC_SUCCESS;    }#endif⑵  int ret = GetValueByFile(key, value, len);    if (ret < 0) {        return EC_FAILURE;    }#ifdef FEATURE_KV_CACHE⑶  AddKVCache(key, value, FALSE);#endif    return ret;}
复制代码

5.3 设置键值 UtilsGetValue

函数 UtilsGetValue 用于设置键值对到文件。⑴处获取已有的键值对的数目,⑵处判断要设置的键值是否已经存在。⑶处如果键值对数量已经大于等于允许的最大值,并且要是新增的键值对,则然后 EC_FAILURE。⑷处保存键值对到文件,如果支持缓存,则加入缓存。⑸处更新键值对数量。

int UtilsSetValue(const char* key, const char* value){    if (!IsValidKey(key) || !IsValidValue(value, MAX_VALUE_LEN)) {        return EC_INVALID;    }⑴  int currentNum = GetCurrentItem();⑵  boolean newItem = NewItem(key);⑶  if ((currentNum >= MAX_KV_SUM) && newItem) {        return EC_FAILURE;    }⑷  int ret = SetValueToFile(key, value);    if (ret == EC_SUCCESS) {#ifdef FEATURE_KV_CACHE        AddKVCache(key, value, TRUE);#endif        if (newItem) {            currentNum++;        }    }
⑸ return SetCurrentItem(currentNum);}
复制代码

5.4 删除键值 UtilsDeleteValue 等

函数 UtilsDeleteValue 用于删除键值文件,如果支持缓存则先从缓存中删除。执行⑴删除文件,⑵处更新键值对数目。函数 ClearKVCache 用于清除缓存。对于使用 UtilsFile 接口时,不需要 UtilsSetEnv 函数。

int UtilsDeleteValue(const char* key){    if (!IsValidKey(key)) {        return EC_INVALID;    }#ifdef FEATURE_KV_CACHE    DeleteKVCache(key);#endif⑴  int ret = UtilsFileDelete(key);    if (ret == EC_SUCCESS) {⑵      ret = SetCurrentItem(GetCurrentItem() - 1);    }    return ret;}
#ifdef FEATURE_KV_CACHEint ClearKVCache(void){ return ClearKVCacheInner();}#endif
int UtilsSetEnv(const char* path){ return EC_SUCCESS;}
复制代码

参考站点

参考了下述站点,或者推荐读者阅读下述站点了解更多信息。


点击关注,第一时间了解华为云新鲜技术~


发布于: 刚刚阅读数: 2
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
OpenHarmony移植:如何适配utils子系统之KV存储部件