OpenHarmony移植案例与原理 - utils子系统之KV存储部件(1)
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;
}
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_CACHE
int 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;
}
- 点赞
- 收藏
- 关注作者
评论(0)