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

x33g5p2x  于2022-02-28 转载在 Harmony  
字(19.3k)|赞(0)|评价(0)|浏览(519)

**摘要:**本文介绍移植开发板时如何适配utils子系统之KV存储部件,并介绍相关的运行机制原理。

本文分享自华为云社区《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\。源代码目录如下:

  1. utils/native/lite/ # 公共基础库根目录
  2. ├── file # 文件接口实现
  3. ├── hals # HAL目录
  4. └── file # 文件操作硬件抽象层头文件
  5. ├── include # 公共基础库对外接口文件
  6. ├── js # JS API目录
  7. └── builtin
  8. ├── common
  9. ├── deviceinfokit # 设备信息Kit
  10. ├── filekit # 文件Kit
  11. └── kvstorekit # KV存储Kit
  12. ├── kal # KAL目录
  13. └── timer # Timer的KAL实现
  14. ├── kv_store # KV存储实现
  15. ├── innerkits # KV存储内部接口
  16. └── src # KV存储源文件
  17. ├── memory
  18. └── include # 内存池管理接口
  19. ├── os_dump # Dump系统属性
  20. └── 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部件,参考之前的移植案例与原理文章即可。

  1. {
  2. "subsystem": "utils",
  3. "components": [
  4. { "component": "file", "features":[] },
  5. { "component": "kv_store", "features":[] }
  6. ]
  7. }
  8. ],
  9. "vendor_adapter_dir": "//device/qemu/SmartL_E802/adapter",

1.2 适配后运行示例代码

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

  1. // 存储/更新key对应数据项
  2. const char key1[] = "key_sample";
  3. const char defValue[] = "test case of key value store.";
  4. int ret = UtilsSetValue(key1, defValue);
  5. // 根据key获取对应数据项
  6. char value1[32] = {0};
  7. ret = UtilsGetValue(key1, value1, 32);
  8. // 删除key对应数据项
  9. 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,默认是定义了该宏的。⑶处定义的结构体,成员包含键值,以及前驱后继结构体指针。

  1. #define MAX_KEY_LEN 32
  2. #define MAX_VALUE_LEN 128
  3. boolean IsValidChar(const char ch);
  4. boolean IsValidValue(const char* value, unsigned int len);
  5. boolean IsValidKey(const char* key);
  6. #ifdef FEATURE_KV_CACHE
  7. typedef struct KvItem_ {
  8. char* key;
  9. char* value;
  10. struct KvItem_* next;
  11. struct KvItem_* prev;
  12. } KvItem;
  13. void DeleteKVCache(const char* key);
  14. void AddKVCache(const char* key, const char* value, boolean isNew);
  15. int GetValueByCache(const char* key, char* value, unsigned int maxLen);
  16. int ClearKVCacheInner(void);
  17. #endif

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

  1. #ifdef FEATURE_KV_CACHE
  2. static KvItem* g_itemHeader = NULL;
  3. static KvItem* g_itemTail = NULL;
  4. static int g_sum = 0;
  5. #endif

2.2 键值有效性判断函数

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

  1. boolean IsValidChar(const char ch)
  2. {
  3. if (islower(ch) || isdigit(ch) || (ch == '_') || (ch == '.')) {
  4. return TRUE;
  5. }
  6. return FALSE;
  7. }
  8. boolean IsValidValue(const char* value, unsigned int len)
  9. {
  10. if (value == NULL) {
  11. return FALSE;
  12. }
  13. size_t valueLen = strnlen(value, MAX_VALUE_LEN);
  14. if ((valueLen == 0) || (valueLen >= MAX_VALUE_LEN) || (valueLen >= len)) {
  15. return FALSE;
  16. }
  17. return TRUE;
  18. }
  19. boolean IsValidKey(const char* key)
  20. {
  21. if (!IsValidValue(key, MAX_KEY_LEN)) {
  22. return FALSE;
  23. }
  24. size_t keyLen = strnlen(key, MAX_KEY_LEN);
  25. for (size_t i = 0; i < keyLen; i++) {
  26. if (!IsValidChar(key[i])) {
  27. return FALSE;
  28. }
  29. }
  30. return TRUE;
  31. }

2.3 根据键删除值DeleteKVCache

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

  1. static void FreeItem(KvItem* item)
  2. {
  3. if (item == NULL) {
  4. return;
  5. }
  6. if (item->key != NULL) {
  7. free(item->key);
  8. }
  9. if (item->value != NULL) {
  10. free(item->value);
  11. }
  12. free(item);
  13. }
  14. void DeleteKVCache(const char* key)
  15. {
  16. if (key == NULL || g_itemHeader == NULL) {
  17. return;
  18. }
  19. KvItem* item = g_itemHeader;
  20. while (strcmp(key, item->key) != 0) {
  21. item = item->next;
  22. if (item == NULL) {
  23. return;
  24. }
  25. }
  26. g_sum--;
  27. if (g_sum == 0) {
  28. g_itemHeader = NULL;
  29. g_itemTail = NULL;
  30. } else if (item == g_itemHeader) {
  31. g_itemHeader = item->next;
  32. g_itemHeader->prev = NULL;
  33. } else if (item == g_itemTail) {
  34. g_itemTail = item->prev;
  35. g_itemTail->next = NULL;
  36. } else {
  37. item->prev->next = item->next;
  38. item->next->prev = item->prev;
  39. }
  40. FreeItem(item);
  41. }

2.4 添加缓存AddKVCache

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

  1. void AddKVCache(const char* key, const char* value, boolean isNew)
  2. {
  3. if (key == NULL || value == NULL) {
  4. return;
  5. }
  6. size_t keyLen = strnlen(key, MAX_KEY_LEN);
  7. size_t valueLen = strnlen(value, MAX_VALUE_LEN);
  8. if ((keyLen >= MAX_KEY_LEN) || (valueLen >= MAX_VALUE_LEN)) {
  9. return;
  10. }
  11. if (isNew) {
  12. DeleteKVCache(key);
  13. }
  14. KvItem* item = (KvItem *)malloc(sizeof(KvItem));
  15. if (item == NULL) {
  16. return;
  17. }
  18. (void)memset_s(item, sizeof(KvItem), 0, sizeof(KvItem));
  19. item->key = (char *)malloc(keyLen + 1);
  20. item->value = (char *)malloc(valueLen + 1);
  21. if ((item->key == NULL) || (item->value == NULL)) {
  22. FreeItem(item);
  23. return;
  24. }
  25. if ((strcpy_s(item->key, keyLen + 1, key) != EOK) ||
  26. (strcpy_s(item->value, valueLen + 1, value) != EOK)) {
  27. FreeItem(item);
  28. return;
  29. }
  30. item->prev = NULL;
  31. item->next = NULL;
  32. if (g_itemHeader == NULL) {
  33. g_itemHeader = item;
  34. g_itemTail = item;
  35. g_sum++;
  36. return;
  37. }
  38. item->next = g_itemHeader;
  39. g_itemHeader->prev = item;
  40. g_itemHeader = item;
  41. g_sum++;
  42. while (g_sum > MAX_CACHE_SIZE) {
  43. KvItem* needDel = g_itemTail;
  44. g_itemTail = g_itemTail->prev;
  45. FreeItem(needDel);
  46. g_itemTail->next = NULL;
  47. g_sum--;
  48. }
  49. }

2.5 从缓存中获取值GetValueByCache

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

  1. int GetValueByCache(const char* key, char* value, unsigned int maxLen)
  2. {
  3. if (key == NULL || value == NULL || g_itemHeader == NULL) {
  4. return EC_FAILURE;
  5. }
  6. KvItem* item = g_itemHeader;
  7. while (strcmp(key, item->key) != 0) {
  8. item = item->next;
  9. if (item == NULL) {
  10. return EC_FAILURE;
  11. }
  12. }
  13. size_t valueLen = strnlen(item->value, MAX_VALUE_LEN);
  14. if (valueLen >= MAX_VALUE_LEN) {
  15. return EC_FAILURE;
  16. }
  17. if ((valueLen >= maxLen) || (strcpy_s(value, maxLen, item->value) != EOK)) {
  18. return EC_FAILURE;
  19. }
  20. return EC_SUCCESS;
  21. }

2.6 清除缓存ClearKVCacheInner

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

  1. int ClearKVCacheInner(void)
  2. {
  3. if (g_itemHeader == NULL) {
  4. return EC_SUCCESS;
  5. }
  6. KvItem* item = g_itemHeader;
  7. while (item != NULL) {
  8. KvItem* temp = item;
  9. item = item->next;
  10. FreeItem(temp);
  11. g_sum--;
  12. }
  13. g_itemHeader = NULL;
  14. g_itemTail = NULL;
  15. return (g_sum != 0) ? EC_FAILURE : EC_SUCCESS;
  16. }

3、KV存储部件对外接口

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

  1. int UtilsGetValue(const char* key, char* value, unsigned int len);
  2. int UtilsSetValue(const char* key, const char* value);
  3. int UtilsDeleteValue(const char* key);
  4. #ifdef FEATURE_KV_CACHE
  5. int ClearKVCache(void);
  6. #endif

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

  1. 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。

  1. static int GetResolvedPath(const char* dataPath, const char* key, char* resolvedPath, unsigned int len)
  2. {
  3. char* keyPath = (char *)malloc(MAX_KEY_PATH + 1);
  4. if (keyPath == NULL) {
  5. return EC_FAILURE;
  6. }
  7. if (sprintf_s(keyPath, MAX_KEY_PATH + 1, "%s/%s/%s", dataPath, KVSTORE_PATH, key) < 0) {
  8. free(keyPath);
  9. return EC_FAILURE;
  10. }
  11. if (realpath(keyPath, resolvedPath) != NULL) {
  12. free(keyPath);
  13. return EC_SUCCESS;
  14. }
  15. if (errno == ENOENT) {
  16. if (strncpy_s(resolvedPath, len, keyPath, strlen(keyPath)) == EOK) {
  17. free(keyPath);
  18. return EC_SUCCESS;
  19. }
  20. }
  21. free(keyPath);
  22. return EC_FAILURE;
  23. }

4.1.2 GetValueByFile从文件中读取键值

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

  1. static int GetValueByFile(const char* dataPath, const char* key, char* value, unsigned int len)
  2. {
  3. char* keyPath = (char *)malloc(PATH_MAX + 1);
  4. if (keyPath == NULL) {
  5. return EC_FAILURE;
  6. }
  7. if (GetResolvedPath(dataPath, key, keyPath, PATH_MAX + 1) != EC_SUCCESS) {
  8. free(keyPath);
  9. return EC_FAILURE;
  10. }
  11. struct stat info = {0};
  12. if (stat(keyPath, &info) != F_OK) {
  13. free(keyPath);
  14. return EC_FAILURE;
  15. }
  16. if (info.st_size >= len) {
  17. free(keyPath);
  18. return EC_FAILURE;
  19. }
  20. int fd = open(keyPath, O_RDONLY, S_IRUSR);
  21. free(keyPath);
  22. keyPath = NULL;
  23. if (fd < 0) {
  24. return EC_FAILURE;
  25. }
  26. int ret = read(fd, value, info.st_size);
  27. close(fd);
  28. fd = -1;
  29. if (ret < 0) {
  30. return EC_FAILURE;
  31. }
  32. value[info.st_size] = '\0';
  33. return info.st_size;
  34. }

4.1.3 SetValueToFile\DeleteValueFromFile存入\删除键值

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

  1. static int SetValueToFile(const char* dataPath, const char* key, const char* value)
  2. {
  3. char* keyPath = (char *)malloc(PATH_MAX + 1);
  4. if (keyPath == NULL) {
  5. return EC_FAILURE;
  6. }
  7. if (GetResolvedPath(dataPath, key, keyPath, PATH_MAX + 1) != EC_SUCCESS) {
  8. free(keyPath);
  9. return EC_FAILURE;
  10. }
  11. int fd = open(keyPath, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
  12. free(keyPath);
  13. keyPath = NULL;
  14. if (fd < 0) {
  15. return EC_FAILURE;
  16. }
  17. int ret = write(fd, value, strlen(value));
  18. close(fd);
  19. fd = -1;
  20. return (ret < 0) ? EC_FAILURE : EC_SUCCESS;
  21. }
  22. static int DeleteValueFromFile(const char* dataPath, const char* key)
  23. {
  24. char* keyPath = (char *)malloc(MAX_KEY_PATH + 1);
  25. if (keyPath == NULL) {
  26. return EC_FAILURE;
  27. }
  28. if (sprintf_s(keyPath, MAX_KEY_PATH + 1, "%s/%s/%s", dataPath, KVSTORE_PATH, key) < 0) {
  29. free(keyPath);
  30. return EC_FAILURE;
  31. }
  32. int ret = unlink(keyPath);
  33. free(keyPath);
  34. return ret;
  35. }

4.1.4 InitKv创建kvstore目录

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

  1. static int InitKv(const char* dataPath)
  2. {
  3. if (dataPath == NULL) {
  4. return EC_FAILURE;
  5. }
  6. char* kvPath = (char *)malloc(MAX_KEY_PATH + 1);
  7. if (kvPath == NULL) {
  8. return EC_FAILURE;
  9. }
  10. if (sprintf_s(kvPath, MAX_KEY_PATH + 1, "%s/%s", dataPath, KVSTORE_PATH) < 0) {
  11. free(kvPath);
  12. return EC_FAILURE;
  13. }
  14. if (access(kvPath, F_OK) == F_OK) {
  15. free(kvPath);
  16. return EC_SUCCESS;
  17. }
  18. if (mkdir(kvPath, S_IRUSR | S_IWUSR | S_IXUSR) != F_OK) {
  19. free(kvPath);
  20. return EC_FAILURE;
  21. }
  22. free(kvPath);
  23. return EC_SUCCESS;
  24. }

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

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

  1. static int GetCurrentItem(const char* dataPath)
  2. {
  3. char kvPath[MAX_KEY_PATH + 1] = {0};
  4. if (sprintf_s(kvPath, MAX_KEY_PATH + 1, "%s/%s", dataPath, KVSTORE_PATH) < 0) {
  5. return EC_FAILURE;
  6. }
  7. DIR* fileDir = opendir(kvPath);
  8. if (fileDir == NULL) {
  9. return EC_FAILURE;
  10. }
  11. struct dirent* dir = readdir(fileDir);
  12. int sum = 0;
  13. while (dir != NULL) {
  14. char fullPath[MAX_KEY_PATH + 1] = {0};
  15. struct stat info = {0};
  16. if (sprintf_s(fullPath, MAX_KEY_PATH + 1, "%s/%s", kvPath, dir->d_name) < 0) {
  17. closedir(fileDir);
  18. return EC_FAILURE;
  19. }
  20. if (stat(fullPath, &info) != 0) {
  21. closedir(fileDir);
  22. return EC_FAILURE;
  23. }
  24. if (S_ISREG(info.st_mode)) {
  25. sum++;
  26. }
  27. dir = readdir(fileDir);
  28. }
  29. closedir(fileDir);
  30. return sum;
  31. }

4.1.6 NewItem判断是否新键值对

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

  1. static boolean NewItem(const char* dataPath, const char* key)
  2. {
  3. char* keyPath = (char *)malloc(MAX_KEY_PATH + 1);
  4. if (keyPath == NULL) {
  5. return FALSE;
  6. }
  7. if (sprintf_s(keyPath, MAX_KEY_PATH + 1, "%s/%s/%s", dataPath, KVSTORE_PATH, key) < 0) {
  8. free(keyPath);
  9. return FALSE;
  10. }
  11. if (access(keyPath, F_OK) == F_OK) {
  12. free(keyPath);
  13. return FALSE;
  14. }
  15. free(keyPath);
  16. return TRUE;
  17. }

4.2 读取键值UtilsGetValue

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

  1. int UtilsGetValue(const char* key, char* value, unsigned int len)
  2. {
  3. if (!IsValidKey(key) || (value == NULL) || (len > MAX_GET_VALUE_LEN)) {
  4. return EC_INVALID;
  5. }
  6. pthread_mutex_lock(&g_kvGlobalMutex);
  7. const char* dataPath = g_dataPath;
  8. if (dataPath == NULL) {
  9. pthread_mutex_unlock(&g_kvGlobalMutex);
  10. return EC_FAILURE;
  11. }
  12. #ifdef FEATURE_KV_CACHE
  13. if (GetValueByCache(key, value, len) == EC_SUCCESS) {
  14. pthread_mutex_unlock(&g_kvGlobalMutex);
  15. return EC_SUCCESS;
  16. }
  17. #endif
  18. int ret = GetValueByFile(dataPath, key, value, len);
  19. if (ret < 0) {
  20. pthread_mutex_unlock(&g_kvGlobalMutex);
  21. return EC_FAILURE;
  22. }
  23. #ifdef FEATURE_KV_CACHE
  24. AddKVCache(key, value, FALSE);
  25. #endif
  26. pthread_mutex_unlock(&g_kvGlobalMutex);
  27. return ret;
  28. }

4.3 设置键值UtilsGetValue

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

  1. int UtilsSetValue(const char* key, const char* value)
  2. {
  3. if (!IsValidKey(key) || !IsValidValue(value, MAX_VALUE_LEN)) {
  4. return EC_INVALID;
  5. }
  6. pthread_mutex_lock(&g_kvGlobalMutex);
  7. const char* dataPath = g_dataPath;
  8. int ret = InitKv(dataPath);
  9. if (ret != EC_SUCCESS) {
  10. g_getKvSum = FALSE;
  11. pthread_mutex_unlock(&g_kvGlobalMutex);
  12. return EC_FAILURE;
  13. }
  14. if (!g_getKvSum) {
  15. g_kvSum = GetCurrentItem(dataPath);
  16. if (g_kvSum < 0) {
  17. pthread_mutex_unlock(&g_kvGlobalMutex);
  18. return EC_FAILURE;
  19. }
  20. g_getKvSum = TRUE;
  21. }
  22. boolean newItem = NewItem(dataPath, key);
  23. if ((g_kvSum >= MAX_KV_SUM) && newItem) {
  24. pthread_mutex_unlock(&g_kvGlobalMutex);
  25. return EC_FAILURE;
  26. }
  27. ret = SetValueToFile(dataPath, key, value);
  28. if (ret == EC_SUCCESS) {
  29. #ifdef FEATURE_KV_CACHE
  30. AddKVCache(key, value, TRUE);
  31. #endif
  32. if (newItem) {
  33. g_kvSum++;
  34. }
  35. }
  36. pthread_mutex_unlock(&g_kvGlobalMutex);
  37. return ret;
  38. }

4.4 删除键值UtilsDeleteValue

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

  1. int UtilsDeleteValue(const char* key)
  2. {
  3. if (!IsValidKey(key)) {
  4. return EC_INVALID;
  5. }
  6. pthread_mutex_lock(&g_kvGlobalMutex);
  7. const char* dataPath = g_dataPath;
  8. if (dataPath == NULL) {
  9. pthread_mutex_unlock(&g_kvGlobalMutex);
  10. return EC_FAILURE;
  11. }
  12. #ifdef FEATURE_KV_CACHE
  13. DeleteKVCache(key);
  14. #endif
  15. int ret = DeleteValueFromFile(dataPath, key);
  16. if (ret == EC_SUCCESS) {
  17. g_kvSum--;
  18. }
  19. pthread_mutex_unlock(&g_kvGlobalMutex);
  20. return ret;
  21. }

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

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

  1. #ifdef FEATURE_KV_CACHE
  2. int ClearKVCache(void)
  3. {
  4. pthread_mutex_lock(&g_kvGlobalMutex);
  5. int ret = ClearKVCacheInner();
  6. pthread_mutex_unlock(&g_kvGlobalMutex);
  7. return ret;
  8. }
  9. #endif
  10. int UtilsSetEnv(const char* path)
  11. {
  12. if (path == NULL) {
  13. return EC_FAILURE;
  14. }
  15. pthread_mutex_lock(&g_kvGlobalMutex);
  16. int ret = strcpy_s(g_dataPath, MAX_KEY_PATH + 1, path);
  17. pthread_mutex_unlock(&g_kvGlobalMutex);
  18. return (ret != EOK) ? EC_FAILURE : EC_SUCCESS;
  19. }

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接口打开,然后写入到文件里。

  1. static int GetValueByFile(const char* key, char* value, unsigned int len)
  2. {
  3. unsigned int valueLen = 0;
  4. if (UtilsFileStat(key, &valueLen) != EC_SUCCESS) {
  5. return EC_FAILURE;
  6. }
  7. if (valueLen >= len) {
  8. return EC_FAILURE;
  9. }
  10. int fd = UtilsFileOpen(key, O_RDONLY_FS, 0);
  11. if (fd < 0) {
  12. return EC_FAILURE;
  13. }
  14. int ret = UtilsFileRead(fd, value, valueLen);
  15. UtilsFileClose(fd);
  16. fd = -1;
  17. if (ret < 0) {
  18. return EC_FAILURE;
  19. }
  20. value[valueLen] = '\0';
  21. return valueLen;
  22. }
  23. static int SetValueToFile(const char* key, const char* value)
  24. {
  25. int fd = UtilsFileOpen(key, O_RDWR_FS | O_CREAT_FS | O_TRUNC_FS, 0);
  26. if (fd < 0) {
  27. return EC_FAILURE;
  28. }
  29. int ret = UtilsFileWrite(fd, value, strlen(value));
  30. UtilsFileClose(fd);
  31. fd = -1;
  32. return (ret < 0) ? EC_FAILURE : EC_SUCCESS;
  33. }

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

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

  1. #define KV_SUM_FILE "KV_FILE_SUM"
  2. #define KV_SUM_INDEX 4
  3. ......
  4. static int GetCurrentItem(void)
  5. {
  6. int fd = UtilsFileOpen(KV_SUM_FILE, O_RDWR_FS, 0);
  7. if (fd < 0) {
  8. return 0;
  9. }
  10. char value[KV_SUM_INDEX] = {0};
  11. int ret = UtilsFileRead(fd, value, KV_SUM_INDEX);
  12. UtilsFileClose(fd);
  13. fd = -1;
  14. return (ret < 0) ? 0 : atoi(value);
  15. }
  16. static int SetCurrentItem(const int num)
  17. {
  18. char value[KV_SUM_INDEX] = {0};
  19. if (sprintf_s(value, KV_SUM_INDEX, "%d", num) < 0) {
  20. return EC_FAILURE;
  21. }
  22. int fd = UtilsFileOpen(KV_SUM_FILE, O_RDWR_FS | O_CREAT_FS, 0);
  23. if (fd < 0) {
  24. return EC_FAILURE;
  25. }
  26. int ret = UtilsFileWrite(fd, value, KV_SUM_INDEX);
  27. UtilsFileClose(fd);
  28. fd = -1;
  29. return (ret < 0) ? EC_FAILURE : EC_SUCCESS;
  30. }

5.1.3 NewItem判断是否新键值对

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

  1. static boolean NewItem(const char* key)
  2. {
  3. int fd = UtilsFileOpen(key, O_RDONLY_FS, 0);
  4. if (fd < 0) {
  5. return TRUE;
  6. }
  7. UtilsFileClose(fd);
  8. return FALSE;
  9. }

5.2 获取键值UtilsGetValue

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

  1. int UtilsGetValue(const char* key, char* value, unsigned int len)
  2. {
  3. if (!IsValidKey(key) || (value == NULL) || (len > MAX_GET_VALUE_LEN)) {
  4. return EC_INVALID;
  5. }
  6. #ifdef FEATURE_KV_CACHE
  7. if (GetValueByCache(key, value, len) == EC_SUCCESS) {
  8. return EC_SUCCESS;
  9. }
  10. #endif
  11. int ret = GetValueByFile(key, value, len);
  12. if (ret < 0) {
  13. return EC_FAILURE;
  14. }
  15. #ifdef FEATURE_KV_CACHE
  16. AddKVCache(key, value, FALSE);
  17. #endif
  18. return ret;
  19. }

5.3 设置键值UtilsGetValue

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

  1. int UtilsSetValue(const char* key, const char* value)
  2. {
  3. if (!IsValidKey(key) || !IsValidValue(value, MAX_VALUE_LEN)) {
  4. return EC_INVALID;
  5. }
  6. int currentNum = GetCurrentItem();
  7. boolean newItem = NewItem(key);
  8. if ((currentNum >= MAX_KV_SUM) && newItem) {
  9. return EC_FAILURE;
  10. }
  11. int ret = SetValueToFile(key, value);
  12. if (ret == EC_SUCCESS) {
  13. #ifdef FEATURE_KV_CACHE
  14. AddKVCache(key, value, TRUE);
  15. #endif
  16. if (newItem) {
  17. currentNum++;
  18. }
  19. }
  20. return SetCurrentItem(currentNum);
  21. }

5.4 删除键值UtilsDeleteValue等

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

  1. int UtilsDeleteValue(const char* key)
  2. {
  3. if (!IsValidKey(key)) {
  4. return EC_INVALID;
  5. }
  6. #ifdef FEATURE_KV_CACHE
  7. DeleteKVCache(key);
  8. #endif
  9. int ret = UtilsFileDelete(key);
  10. if (ret == EC_SUCCESS) {
  11. ret = SetCurrentItem(GetCurrentItem() - 1);
  12. }
  13. return ret;
  14. }
  15. #ifdef FEATURE_KV_CACHE
  16. int ClearKVCache(void)
  17. {
  18. return ClearKVCacheInner();
  19. }
  20. #endif
  21. int UtilsSetEnv(const char* path)
  22. {
  23. return EC_SUCCESS;
  24. }

参考站点

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

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

相关文章