摘要:本文介绍移植开发板时如何适配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.  
  6. // 根据key获取对应数据项
  7. char value1[32] = {0};
  8. ret = UtilsGetValue(key1, value1, 32);
  9.  
  10. // 删除key对应数据项
  11. 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.  
  4. boolean IsValidChar(const char ch);
  5. boolean IsValidValue(const char* value, unsigned int len);
  6. boolean IsValidKey(const char* key);
  7.  
  8. #ifdef FEATURE_KV_CACHE
  9. typedef struct KvItem_ {
  10. char* key;
  11. char* value;
  12. struct KvItem_* next;
  13. struct KvItem_* prev;
  14. } KvItem;
  15.  
  16. void DeleteKVCache(const char* key);
  17. void AddKVCache(const char* key, const char* value, boolean isNew);
  18. int GetValueByCache(const char* key, char* value, unsigned int maxLen);
  19. int ClearKVCacheInner(void);
  20. #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.  
  9. boolean IsValidValue(const char* value, unsigned int len)
  10. {
  11. if (value == NULL) {
  12. return FALSE;
  13. }
  14. size_t valueLen = strnlen(value, MAX_VALUE_LEN);
  15. if ((valueLen == 0) || (valueLen >= MAX_VALUE_LEN) || (valueLen >= len)) {
  16. return FALSE;
  17. }
  18. return TRUE;
  19. }
  20.  
  21. boolean IsValidKey(const char* key)
  22. {
  23. if (!IsValidValue(key, MAX_KEY_LEN)) {
  24. return FALSE;
  25. }
  26. size_t keyLen = strnlen(key, MAX_KEY_LEN);
  27. for (size_t i = 0; i < keyLen; i++) {
  28. if (!IsValidChar(key[i])) {
  29. return FALSE;
  30. }
  31. }
  32. return TRUE;
  33. }

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.  
  15. void DeleteKVCache(const char* key)
  16. {
  17. if (key == NULL || g_itemHeader == NULL) {
  18. return;
  19. }
  20.  
  21. KvItem* item = g_itemHeader;
  22. while (strcmp(key, item->key) != 0) {
  23. item = item->next;
  24. if (item == NULL) {
  25. return;
  26. }
  27. }
  28. g_sum--;
  29. if (g_sum == 0) {
  30. g_itemHeader = NULL;
  31. g_itemTail = NULL;
  32. } else if (item == g_itemHeader) {
  33. g_itemHeader = item->next;
  34. g_itemHeader->prev = NULL;
  35. } else if (item == g_itemTail) {
  36. g_itemTail = item->prev;
  37. g_itemTail->next = NULL;
  38. } else {
  39. item->prev->next = item->next;
  40. item->next->prev = item->prev;
  41. }
  42. FreeItem(item);
  43. }

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

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.  
  7. KvItem* item = g_itemHeader;
  8. while (strcmp(key, item->key) != 0) {
  9. item = item->next;
  10. if (item == NULL) {
  11. return EC_FAILURE;
  12. }
  13. }
  14. size_t valueLen = strnlen(item->value, MAX_VALUE_LEN);
  15. if (valueLen >= MAX_VALUE_LEN) {
  16. return EC_FAILURE;
  17. }
  18. if ((valueLen >= maxLen) || (strcpy_s(value, maxLen, item->value) != EOK)) {
  19. return EC_FAILURE;
  20. }
  21. return EC_SUCCESS;
  22. }

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.  
  16. return (g_sum != 0) ? EC_FAILURE : EC_SUCCESS;
  17. }

3、KV存储部件对外接口

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

  1. int UtilsGetValue(const char* key, char* value, unsigned int len);
  2.  
  3. int UtilsSetValue(const char* key, const char* value);
  4.  
  5. int UtilsDeleteValue(const char* key);
  6.  
  7. #ifdef FEATURE_KV_CACHE
  8. int ClearKVCache(void);
  9. #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.  
  23. static int DeleteValueFromFile(const char* dataPath, const char* key)
  24. {
  25. char* keyPath = (char *)malloc(MAX_KEY_PATH + 1);
  26. if (keyPath == NULL) {
  27. return EC_FAILURE;
  28. }
  29. if (sprintf_s(keyPath, MAX_KEY_PATH + 1, "%s/%s/%s", dataPath, KVSTORE_PATH, key) < 0) {
  30. free(keyPath);
  31. return EC_FAILURE;
  32. }
  33. int ret = unlink(keyPath);
  34. free(keyPath);
  35. return ret;
  36. }

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.  
  11. int UtilsSetEnv(const char* path)
  12. {
  13. if (path == NULL) {
  14. return EC_FAILURE;
  15. }
  16. pthread_mutex_lock(&g_kvGlobalMutex);
  17. int ret = strcpy_s(g_dataPath, MAX_KEY_PATH + 1, path);
  18. pthread_mutex_unlock(&g_kvGlobalMutex);
  19. return (ret != EOK) ? EC_FAILURE : EC_SUCCESS;
  20. }

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.  
  24. static int SetValueToFile(const char* key, const char* value)
  25. {
  26. int fd = UtilsFileOpen(key, O_RDWR_FS | O_CREAT_FS | O_TRUNC_FS, 0);
  27. if (fd < 0) {
  28. return EC_FAILURE;
  29. }
  30. int ret = UtilsFileWrite(fd, value, strlen(value));
  31. UtilsFileClose(fd);
  32. fd = -1;
  33. return (ret < 0) ? EC_FAILURE : EC_SUCCESS;
  34. }

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.  
  17. static int SetCurrentItem(const int num)
  18. {
  19. char value[KV_SUM_INDEX] = {0};
  20. if (sprintf_s(value, KV_SUM_INDEX, "%d", num) < 0) {
  21. return EC_FAILURE;
  22. }
  23. int fd = UtilsFileOpen(KV_SUM_FILE, O_RDWR_FS | O_CREAT_FS, 0);
  24. if (fd < 0) {
  25. return EC_FAILURE;
  26. }
  27. int ret = UtilsFileWrite(fd, value, KV_SUM_INDEX);
  28. UtilsFileClose(fd);
  29. fd = -1;
  30. return (ret < 0) ? EC_FAILURE : EC_SUCCESS;
  31. }

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.  
  21. return SetCurrentItem(currentNum);
  22. }

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.  
  16. #ifdef FEATURE_KV_CACHE
  17. int ClearKVCache(void)
  18. {
  19. return ClearKVCacheInner();
  20. }
  21. #endif
  22.  
  23. int UtilsSetEnv(const char* path)
  24. {
  25. return EC_SUCCESS;
  26. }

参考站点

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

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

OpenHarmony移植:如何适配utils子系统之KV存储部件的更多相关文章

  1. OpenHarmony移植案例与原理:startup子系统之syspara_lite系统属性部件

    摘要:本文介绍下移植开发板时如何适配系统属性部件syspara_lite,并介绍下相关的运行机制原理. 本文分享自华为云社区<openharmony移植案例与原理 - startup子系统之sy ...

  2. 【OpenHarmony移植案例与原理】XTS子系统之应用兼容性测试用例开发

    摘要:本文主要介绍ACTS应用兼容性测试用例开发编译. 本文分享自华为云社区<移植案例与原理 - XTS子系统之应用兼容性测试用例开发>,作者: zhushy . XTS(X Test S ...

  3. Redis与KV存储(RocksDB)融合之编码方式

    Redis与KV存储(RocksDB)融合之编码方式 简介 Redis 是目前 NoSQL 领域的当红炸子鸡,它象一把瑞士军刀,小巧.锋利.实用,特别适合解决一些使用传统关系数据库难以解决的问题.Re ...

  4. 基于淘宝开源Tair分布式KV存储引擎的整合部署

    一.前言 Tair支撑了淘宝几乎所有系统的缓存信息(Tair = Taobao Pair,Pair即Key-Value键值对),内置了三个存储引擎:mdb(默认,类似于Memcache).rdb(类似 ...

  5. Android实战开发租赁管理软件(适配UI,数据的存储,多线程下载)课程分享

    亲爱的网友,我这里有套课程想和大家分享,假设对这个课程有兴趣的,能够加我的QQ2059055336和我联系. 课程内容简单介绍 我们软件是基于移动设备的.所以我们必定的选择了安卓作为我们的开发工具.课 ...

  6. 编写你的第一个 Java 版 Raft 分布式 KV 存储

    前言 本文旨在讲述如何使用 Java 语言实现基于 Raft 算法的,分布式的,KV 结构的存储项目.该项目的背景是为了深入理解 Raft 算法,从而深刻理解分布式环境下数据强一致性该如何实现:该项目 ...

  7. 高性能kv存储之Redis、Redis Cluster、Pika:如何应对4000亿的日访问量?

    一.背景介绍 随着360公司业务发展,业务使用kv存储的需求越来越大.为了应对kv存储需求爆发式的增长和多使用场景的需求,360web平台部致力于打造一个全方位,适用于多场景需求的kv解决方案.目前, ...

  8. 服务注册发现consul之四: 分布式锁之四:基于Consul的KV存储和分布式信号量实现分布式锁

    一.基于key/value实现 我们在构建分布式系统的时候,经常需要控制对共享资源的互斥访问.这个时候我们就涉及到分布式锁(也称为全局锁)的实现,基于目前的各种工具,我们已经有了大量的实现方式,比如: ...

  9. 谈谈KV存储集群的设计要点

    版权声明:本文由廖念波原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/150 来源:腾云阁 https://www.qclo ...

随机推荐

  1. Kotlin 协程一 —— 全面了解 Kotlin 协程

    一.协程的一些前置知识 1.1 进程和线程 1.1.1基本定义 1.1.2为什么要有线程 1.1.3 进程与线程的区别 1.2 协作式与抢占式 1.2.1 协作式 1.2.2 抢占式 1.3 协程 二 ...

  2. 利用栈实现括号匹配(python语言)

    原理: 右括号总是与最近的左括号匹配 --- 栈的后进先出 从左往右遍历字符串,遇到左括号就入栈,遇到右括号时,就出栈一个元素与其配对 当栈为空时,遇到右括号,则此右括号无与之匹配的左括号 当最终右括 ...

  3. [USB波形分析] 全速USB波形数据分析(二)

    在上一篇文章全速USB波形数据分析(一)介绍了全速USB的数据包(Packet)的组成,数据的类型等基本知识.这篇文章介绍USB的几种传输方式 事务(Transaction) USB协议定义了三种不同 ...

  4. java类的反射机制

    1.获得一个类的类对象有哪些方式? - 方法1:类型.class,例如:String.class- 方法2:对象.getClass(),例如:"hello".getClass()- ...

  5. 程序员必备的编程助手!SmartCoder助你轻松集成HMS Core

    当开发者在集成HMS Core遇到一些疑问时,需要翻阅官网文档,反复查看集成说明或者API调用说明,或者研究GitHub上的开源示例代码,花费较多的时间,在IDE环境和网页浏览器之间反复切换也会耗费很 ...

  6. 使用Outlook欺骗性云附件进行网络钓鱼

    滥用Microsoft365 Outlook 云附件的方式发送恶意文件,使恶意可执行云附件规避云查杀检测 介绍 在本文中,我们将探讨如何滥用 O365 上的云附件功能使可执行文件(或任何其他文件类型) ...

  7. python32day

    内容回顾 操作系统的历史 多道操作系统 分时操作系统 实时操作系统 进程 线程 并行和并发 同步和异步 阻塞和非阻塞 今日内容 进程的三状态图 进程的调度算法 给所有进程分配资源或者分配CPU使用权的 ...

  8. HTTP状态码1XX深入理解

    前段时间看了<御赐小仵作>,里面有很多细节很有心.看了一些评论都是:终于在剧里能够看到真正在搞事业.发了工资第一时间还钱的正常人了.我印象比较深的是王府才能吃上的葡萄.觉得非常合理.剧里说 ...

  9. 学习JAVAWEB第十六天

    今天做了一个简单的登陆界面,HTML+CSS太不熟悉了,明天还得接着做

  10. Log4j2日志技术总结

    前言 现在流行是SLF4j和Log4j2组合的日志技术,但为了日志技术归类,故前因后果都将做一下介绍. 市场上流行的日志框架 JUL java util logging Java开发团队开发,Jdk原 ...