Nordic--nrf52832--FDS(二)基本使用

註冊FDS

ret_code_t  fds_register(fds_cb_t cb); 

 該函數註冊 fds的事件處理函數,fds提供了寫/更新/刪除等api,不過這些api都是異步的,即調用後函數函數會立刻返回,但是實際的flash操作可能不會立刻執行。協議棧內部會在合適的時候去執行實際操作。並最終返回給上層事件,fds模塊內部處理後再返回 fds的事件,並調用fds_register函數註冊的這個回調函數。


初始化FDS

ret_code_t fds_init(void);

 初始化FDS模塊, FDS模塊首先應該調用 fds_register 之後才調用fds_init 來初始化fds模塊。因爲fds的初始化完成後也會返回 init 完成事件並調用回調函數。


FDS寫操作

ret_code_t  fds_record_write(fds_record_desc_t * const p_desc,
                fds_record_t      const * const p_record);

 寫記錄,第一次寫入一個記錄時需要用該函數,p_record 參數中需要給出該記錄的file id,record key以及記錄的內容指針。FDS內部不會緩存需要寫入flash的內容,所以開發者需要自己保證傳入的數據指針指向的內容在flash操作完成回調執行之前一直都是有效的

 第一次寫入其實也同時就是創建過程。所以fds模塊會返回這個創建的記錄的現相關信息。

 P_desc即爲fds返回的該記錄的描述符。其中的內容爲fds模塊內部使用。比如其中的record id字段,因爲file id和record key不要求唯一性,所以fds模塊內部定義了一個record id來保證每個記錄塊的唯一性,即使他們fiel id,record key相同fds模塊內部也可以同過record id來區分。


FDS其他操作

ret_code_t fds_reserve(fds_reserve_token_t * const p_token, uint16_t length_words);

ret_code_t fds_reserve_cancel(fds_reserve_token_t * const p_token);

ret_code_t fds_record_write_reserved(fds_record_desc_t * const p_desc,

                        fds_record_t const * const p_record,

                        fds_reserve_token_t const * const p_token);

 這三個函數放在一起說明,這三個函數的作用類似延遲寫的作用。有些應用場景,你明確知道你需要存某個內容,但是這個內容可能需要一定時間後你才能知道其真正內容。但是如果等到那個時間再存,可能flash中已經沒有空間來存儲你的內容了。所以fds提供了預定寫相關的操作。

 即可以通過fds_reserve 函數來直接申請預留一部分flash空間將來用。該函數返回的參數p_token即記錄了申請的空間。

 等將來需要實際寫入數據到之前申請預留的flash空間時,調用fds_record_write_reserved 函數並傳入 之前獲取到的p_token即可。 P_desc和P_record參數同fds_record_write函數

 如果後面不想寫了,也可以通過fds_reserve_cancel 釋放之前申請的預留空間。


FDS更新操作

ret_code_t  fds_record_update(fds_record_desc_t * const  p_desc,

                fds_record_t const * const p_record);

 更新記錄內容,FDS的更新並不是真的直接修改原記錄的內容,而是直接創建一個新的記錄,內容即爲新內容,即p_record參數指定的新內容。從FDS的更新角度來看,其實更新一個記錄等於就是創建一個記錄,只是多了一個無效舊記錄的操作。所以p_record參數中的file id,record key可以和舊記錄一樣也可以不一樣。如果和舊記錄不一樣那怎麼找到舊記錄去無效它? 其實就算和舊記錄一樣,FDS內部也不是依靠file id和record key去找這個舊記錄的,因爲fds不需要保證file id 和record key的唯一性,所以不能以這兩個參數去找。 FDS模塊內部是通過 record id這個內部使用的參數去找的。這個參數即在 p_desc 參數中。那個怎麼知道傳入的p_desc參數中的record id應該設置成什麼值?即怎麼知道舊記錄的record id? 其實不需要設置,因爲是更新操作,所以前面一定調用過fds_record_write 函數第一次去寫這個記錄。而fds_record_write 的第一個就是FDS模塊內部返回的關於這個記錄的內部信息,其中就有record id這個字段。

 所以綜上,更新一箇舊記錄的內容,首先需要知道其舊記錄的描述符,該描述符就是fds_record_write第一次寫時返回的。 之後設置新記錄的內容即可。 FDS內部直接創建一個新記錄填寫新內容。並利用舊記錄的描述符找到舊記錄然後去無效它。

 同時因爲update會直接創建一個新的記錄填寫新內容,所以以前fds_record_write返回的描述符就沒用了,因爲舊記錄已經無效了。所以update函數也同時會返回新記錄的描述符存在在p_desc 變量中。


FDS刪除操作

ret_code_t fds_record_delete(fds_record_desc_t * const p_desc)

 該函數用來刪除某個記錄,這裏也實際並不是真的刪除,fds內部只是設置使這個記錄無效。同樣因爲fds並不要求file id和record key唯一性。所以刪除不能依據這兩個數據。而是需要用 fds_record_write 或者fds_record_update 函數返回的描述符。這個描述符中有記錄的唯一標識record id。Fds內部會依據這個值來查找記錄。

 還是因爲fds的file id和record key的不唯一性。所以當我們需要讀某個記錄時怎麼辦? 當然可以直接用write/update操作返回的變量p_desc,這個變量中除了有這個記錄的record id這個fds內部使用的記錄唯一標識。還有p_record,這個變量記載了這個記錄的存儲地址。

 上面的方法可以用,但是fds提供了讀相關的接口。另外fds有自己的存儲結構,如果直接用訪問地址的方式讀,你需要自己解析一下數據頭。總之直接用fds提供的讀api 更方便。

 另外應用場景中的確有多個記錄有相同的record key和file id。怎麼讀取他們?

Fds提供了下面的接口


FDS查找操作

ret_code_t fds_record_find(uint16_tfile_id,
              uint16_t record_key, fds_record_desc_t * const p_desc,

              fds_find_token_t  * const p_token);

 該接口通過file id和record key找到flash中符合file id和record key的第一個記錄,並通過p_desc返回這個記錄的描述符(包含了記錄的record id 和存儲地址)

 那麼如何找到剩下的相同file id和record key的記錄? 這需要用到該函數返回的p_token。

 這個結構體變量保存的是找到的這個記錄所在的頁和地址。

 那麼就明白了。找到了第一個記錄,並且知道了這個記錄所在的頁和地址。那麼下一個記錄直接從這隻有找就可以了。

 綜上,只需設置p_token初始值爲0,然後迭代調用這個函數,即可找到所有file id和record key相同的記錄了。

    memset(&ftok, 0x00, sizeof(fds_find_token_t));

   while (fds_record_find(FILE_ID, REC_KEY, &record_desc, &ftok) == FDS_SUCCESS)

    {

       // 找到了一個

    }

找到了記錄,那麼就可以通過返回的描述符來讀取記錄了。

   ret_code_t fds_record_open(fds_record_desc_t  * const p_desc,

                      fds_flash_record_t * const p_flash_record);

 

   ret_code_t fds_record_close(fds_record_desc_t * const p_desc);

 fds_record_open通過之前獲取到的描述符去打開這個記錄,並通過p_flash_record這個結構體返回這個記錄數據,包括它的file id,record key,長度,實際內容,如果使能了crc校驗還會有crc值。當對flash的訪問結束後通過fds_record_close來結束訪問。

 爲什訪問flash需要有一個open和colse操作?這與下面要說的fds的垃圾回收機制有關。上面提到過fds的刪除操作只是無效了這個記錄(update對於舊塊也有使其無效的操作),其實並沒有刪除。這樣的操作多了會導致flash中很多沒有釋放的無效記錄,那麼就沒有空間來存儲新的記錄了。Fds的垃圾回收機制會將一頁有這些無效記錄的髒頁中的有效數據寫入到一個全新的交換頁,之後這個交換頁就作爲數據頁了。而之前有無效記錄的數據頁會被直接整頁擦除然後作爲新的數據頁。

所以爲了在訪問flash內容時,flash的數據不會變動。所以fds提供的讀操作會有open和close接口, open函數會打開這個記錄,之後訪問這個記錄內容。訪問結束後調用close來關閉訪問。實際上Open操作會設置fds內部標記,從而阻止垃圾回收對該記錄所在也做處理,保證在訪問期間flash數據不會變動。所以訪問完後需要調用close釋放,從而可以是垃圾回收能正常工作。

 綜上,如果想訪問所有相同file id 和record key的記錄塊的內容。則可以通過如下方式:

    memset(&ftok, 0x00, sizeof(fds_find_token_t));
    // 迭代查找所有file id和record key相同的記錄
    while (fds_record_find(FILE_ID, REC_KEY, &record_desc, &ftok) == FDS_SUCCESS)
    {
        if (fds_record_open(&record_desc, &flash_record) != FDS_SUCCESS)
        {
            //打開成功,訪問數據,flash_record保存了記錄的相關信息和內容。
            
            //訪問結束關閉記錄
            if (fds_record_close(&record_desc) != FDS_SUCCESS)
            {
                //錯誤處理
            }
        }
    }

關於查找記錄fds還提供了另外兩個接口,可以只通過file id或者record key來查找某個記錄,或者迭代查找其值相同的所有記錄。

    ret_code_t fds_record_find_by_key(uint16_t record_key,

                      fds_record_desc_t  *const p_desc,

                     fds_find_token_t  *const p_token);

     ret_code_t fds_record_find_in_file(uint16_t file_id,

                       fds_record_desc_t * const p_desc,

                     fds_find_token_t  * const p_token);

使用方法和上面介紹的fds_record_find 是一樣的。


FDS垃圾回收機制

ret_code_t fds_gc(void);

 fds 的刪除記錄/更新記錄相對以前的flash操作方式都比較快。因爲不論是直接的flash刪除記錄操作,還是更新操作中的無效舊記錄的操作。fds並不會真的去執行刪除操作,只是設置一下無效標記即將記錄的record key置位無效的0x0000表示該記錄無效。從而實現刪除的操作。

 這樣操作避免了flash局部更新時的麻煩,否則你需要去讀取整個flash的內容,修改局部後再寫回。

 而fds的機制則避免了這類麻煩,但帶來另一個問題,如果無效記錄累計越來越多,最終導致要寫一個新記錄時返回空間不足。這個時候就需要做一次總的回收,回收所有無效記錄佔用的flash 的空間。以便新紀錄寫入。

 Fds的回收機制需要一個額外的 交換頁,該頁是一個全新頁。在執行垃圾回收時,fds模塊會找到有無效記錄的髒頁,然後將該頁中的有效的記錄都寫到這個交換頁中,這個交換頁作爲新的數據頁。最後整頁擦除之前的髒頁,並將擦除後的髒頁作爲新的交換頁。並且繼續查找有無效數據的髒頁做相同處理。直到所有數據頁都沒有無效數據。

 PS:前面提到過fds_record_open函數會阻止垃圾回收的處理,如果一個頁中有無效數據,但是其中某個或多個有效數據被打開了並且還未釋放。那麼垃圾回收會跳過該頁不做處理。

 垃圾回收機制會將所有沒有打開記錄的有無效記錄的髒頁都做一次回收處理,釋放其中的無效記錄所佔的flash空間,所以fds的垃圾回收比較耗時,因此fds不會主動做垃圾回收的處理。因此需要開發這自己在必要的時候纔去調用。比如寫操作返回 FDS_ERR_NO_SPACE_IN_FLASH 錯誤時調用ret_code_t fds_gc(void); 進行無效記錄的空間釋放。並在收到垃圾回收完成的事件之後再進行寫操作。


FDS狀態獲取操作

    ret_code_t fds_stat(fds_stat_t * const p_stat);

 最後介紹一下fds的狀態獲取函數。該函數可以獲取當前fds模塊的總狀態。該函數返回的p_stat 記錄了fds管理的flash存儲空間的狀態。例如有多少個文件打開了,flash使用了多少,有多少個有效記錄,有多少個無效記錄,有多少空間可回收等信息。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章