1. 程式人生 > 其它 >OpenHarmony移植:如何適配utils子系統之KV儲存部件

OpenHarmony移植:如何適配utils子系統之KV儲存部件

摘要:本文介紹移植開發板時如何適配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\。原始碼目錄如下:

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;
}

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;
    }
#endifint 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);
#endifint 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_CACHE
int 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;
    }
#endifint 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);
#endifint ret = UtilsFileDelete(key);
    if (ret == EC_SUCCESS) {
⑵      ret = SetCurrentItem(GetCurrentItem() - 1);
    }
    return ret;
}

#ifdef FEATURE_KV_CACHE
int ClearKVCache(void)
{
    return ClearKVCacheInner();
}
#endif

int UtilsSetEnv(const char* path)
{
    return EC_SUCCESS;
}

參考站點

參考了下述站點,或者推薦讀者閱讀下述站點了解更多資訊。

點選關注,第一時間瞭解華為雲新鮮技術~