熊啟龍
(水利部淮河水利委員會水文局(信息中心),安徽 蚌埠 233001)
水文自動測報系統(tǒng)中遙測站的常用參數(shù)必須實現(xiàn)在站存儲,并且可以實時增加、修改、刪除和查詢。終端機作為遙測站的采集控制中心,通常都集成有非易失 Flash 固態(tài)存儲器。非易失 Flash 固態(tài)存儲器常用來存放運行參數(shù)、歷史數(shù)據(jù)、日志信息、過程文件等內(nèi)容,是遙測終端機重要的組成部分。
從微控制器(MCU)的角度看,遙測終端機中 Flash 可以分為以下 2 種:1)片內(nèi) Flash。片內(nèi)Flash 空間有限,一般不用來存放頻繁讀寫的數(shù)據(jù)。2)片外 Flash。片外 Flash 有 EEPROM,NOR Flash或 Nand Flash 等類型,采用通用標準接口與 MCU相連,可以用來存放頻繁讀寫的數(shù)據(jù)。水文自動測報系統(tǒng)中大多數(shù)遙測終端機采用片外 NOR Flash 存放系統(tǒng)運行參數(shù)和設(shè)備運行期間產(chǎn)生的數(shù)據(jù)[1]。
根據(jù) NOR Flash 的讀、寫和擦除特性,遙測終端機通常選用以下 1 種方式保存系統(tǒng)運行參數(shù):1)依次存放方式。在以 1 個固定地址開始的存儲區(qū)間內(nèi),依次存放系統(tǒng)運行參數(shù)[2],所有的系統(tǒng)運行參數(shù)組合在一起存放在 NOR Flash 中固定的 1 個或者多個連續(xù)扇區(qū)內(nèi)。根據(jù) NOR Flash 的擦寫特點,參數(shù)的修改、擦除和寫入必須以扇區(qū)為單位進行操作,此種操作方式?jīng)]有考慮 NOR Flash 的擦寫次數(shù)有限,頻繁擦寫固定的扇區(qū)可能會損壞該扇區(qū),使存儲器出現(xiàn)壞區(qū)。2)分塊管理方式。以扇區(qū)為單位,在固定存儲空間內(nèi)為每個扇區(qū)建立頭部信息,記錄每個扇區(qū)的擦除次數(shù)、存儲數(shù)據(jù)類型和分塊狀態(tài),存放在每個扇區(qū)起始地址處。系統(tǒng)根據(jù)每個扇區(qū)的頭部信息進行數(shù)據(jù)寫入讀取、資源回收和擦除次數(shù)均衡等操作,實現(xiàn)對固定的 NOR Flash 存儲空間以分塊管理的目的[3]。
遙測站系統(tǒng)運行參數(shù)是一種結(jié)構(gòu)化數(shù)據(jù),1 個參數(shù)可以用 1 個 KV(Key-Value)鍵值對進行描述,其中 Key 表示參數(shù)名,Value 表示參數(shù)數(shù)值,多個參數(shù)組成 KV 鍵值數(shù)據(jù)庫。在同一個 KV 數(shù)據(jù)庫中,Key 具有唯一性,KV 數(shù)據(jù)庫具有簡單易用、高效和可擴展性強等特點。當前,國內(nèi)外常用的主流嵌入式 KV 數(shù)據(jù)庫有 BerkeleyDB,Empress 和SQLite 等[4],這些嵌入式 KV 數(shù)據(jù)庫性能強大,支持高并發(fā)操作,通常運行在 Linux 和 FreeBSD 等操作系統(tǒng)平臺上,但是對于遙測終端機這種小型嵌入式設(shè)備來說,MCU 資源和性能有限,移植 Linux 和FreeBSD 等操作系統(tǒng),以及 BerkeleyDB,Empress,SQLite 等嵌入式 KV 數(shù)據(jù)庫非常困難,即使移植成功,也不能充分發(fā)揮高性能,因此需要針對遙測終端機的硬件特點和應(yīng)用場景,設(shè)計一個 KV 數(shù)據(jù)庫,滿足遙測站日常運行應(yīng)用需求。
在水文自動測報系統(tǒng)中,遙測終端機采用 NOR Flash 作為片外存儲器,存儲器資源和系統(tǒng)性能相對有限,運行過程中,需要頻繁對其進行讀寫、修改和刪除操作,且并發(fā)性操作要求不高,但是需要考慮存儲器各扇區(qū)的磨損均衡和系統(tǒng)在異常情況下的掉電恢復功能,為此,本研究以終端機中常用的NOR Flash 存儲器 W25Q256 為例,在資源有限的嵌入式終端設(shè)備中研究如何充分利用 W25Q256 的讀寫特點創(chuàng)建一個嵌入式 KV 鍵值數(shù)據(jù)庫,滿足水文自動測報系統(tǒng)中遙測站應(yīng)用場景的需求。
W25Q256 的容量為 32 MB,由 131 072 個可編程頁組成,每個頁的容量為 256 B,存儲器 1 次最多可編程 1 個頁,但是以 1 個扇區(qū)為單位進行擦除操作時,可以任意以字節(jié)為單位進行尋址讀取數(shù)據(jù)。
為保證寫入數(shù)據(jù)的正確性,通常在針對 1 個地址執(zhí)行編程操作前,都要對該地址所在的扇區(qū)進行擦除操作,擦除后該扇區(qū)內(nèi)的存儲單元數(shù)值將全部為初始值 FFH,再進行寫操作就可以正確完成。根據(jù) W25Q256 的物理特征,在對每個字節(jié)進行編程操作時,每個位可以通過編程由 1 變?yōu)?0,但是不可以從 0 變?yōu)?1。因此針對 1 個存儲空間可以重復寫入,最終寫入數(shù)值 = 要寫入的數(shù)值 & 存儲空間的原有數(shù)值。例如:在 Flash 起始地址為 1000H 的字節(jié)空間內(nèi)寫入二進制數(shù)值 1011 1111,該地址原有的二進制數(shù)值為 0111 1111,那么最終寫入的數(shù)值為0011 1111。
從以上結(jié)果看,W25Q256 每次編程的最小寫操作顆粒度為 1 bit,也就是針對 1 個編程操作的最小執(zhí)行單位是 1 bit,執(zhí)行結(jié)果要么是將 1 bit 的數(shù)值由1 變?yōu)?0,要么寫入結(jié)果仍然為 1,不存在第 3 種可能,這是一個很重要的概念。
NOR Flash 的擦除和編程次數(shù)是有限的,根據(jù)W25Q256 存儲器芯片的數(shù)據(jù)手冊,針對同一個地址的循環(huán)擦除和編程次數(shù)如果超出 100 000 次[5],存儲器芯片可能會損壞。
KV 鍵值數(shù)據(jù)庫內(nèi)的每一組 KV 鍵值對對應(yīng)遙測終端機的 1 個參數(shù),Key 具有唯一性,一旦保存,Key 值就不可被修改。
根據(jù)遙測終端機的應(yīng)用場景,要在 W25Q256存儲器創(chuàng)建嵌入式 KV 鍵值數(shù)據(jù)庫,必須根據(jù)W25Q256 存儲器的特點合理規(guī)劃,最終要實現(xiàn)以下功能:
1)實現(xiàn)系統(tǒng)運行參數(shù)的快速插入、修改和刪除操作;
2)要能根據(jù)參數(shù)名稱,快速檢索出數(shù)據(jù)庫中參數(shù)對應(yīng)的數(shù)值;
3)要考慮 NOR Flash 的磨損均衡操作[6]和系統(tǒng)運行參數(shù)的異常掉電恢復[7]功能。
實現(xiàn)系統(tǒng)運行參數(shù)的在站存儲操作,主要是在W25Q256 存儲器的一段空間內(nèi)建設(shè) KV 鍵值數(shù)據(jù)庫,實現(xiàn)任意參數(shù)的檢索讀取、新增寫入、刪除和修改。
在遙測終端機中實現(xiàn)參數(shù)的統(tǒng)一存放,就是在KV 鍵值數(shù)據(jù)庫中依次存放 KV 鍵值對,在針對每個扇區(qū)存放第 1 個 KV 鍵值對前,需要對該扇區(qū)進行格式化擦除操作,保證該扇區(qū)在首次寫入時數(shù)據(jù)全部為初始值 FFH。
為確保鍵值對操作的可靠性和數(shù)據(jù)的安全性,采用 C 語言為每個鍵值對定義一個描述結(jié)構(gòu)體,具體如下:
struct KV_data {
uint8_t status; /*KV 鍵值對狀態(tài)*/
uint32_t len; /*KV 鍵值對總長度,以字節(jié)為單位,為 Key 長度和 Value 長度之和 */
uint8_t key_len; /*Key 長度,鍵值對總長度為len,那么 Value 的長度為 len - key_len*/
uint32_t crc32;/*KV 鍵值對的 crc32 校驗數(shù)值*/
} 。
結(jié)構(gòu)體中 status 為 KV 鍵值對的當前狀態(tài),是實現(xiàn) KV 鍵值數(shù)據(jù)庫的關(guān)鍵,定義為如下 6 個數(shù)值(16 進制):
1)FFH,空白,表明該空間沒有使用,宏定義為 UNUSED;
2)7FH,表明該空間準備或者正在寫入內(nèi)容,宏定義為 PRE_WRITE;
3)3FH,表明該鍵值對已經(jīng)處于有效完好狀態(tài),宏定義為 WRITE;
4)1FH,表明該鍵值對正在準備刪除,宏定義為 PRE_DELETE;
5)0FH,表明該鍵值對已經(jīng)處于刪除狀態(tài),不能再被使用,宏定義為 DELETED;
6)00H,表明該鍵值對檢驗錯誤,為錯誤狀態(tài),宏定義為 ERR_HDR。
從 6 個狀態(tài)數(shù)值可以看出,相鄰的 2 個狀態(tài)數(shù)值只變化 1 bit,而且是從 1 變?yōu)?0,也就是W25Q256 的最小編程顆粒度。定義程序中每個 KV鍵值對的狀態(tài)變化順序只能從狀態(tài) 1 到 6 變化,不能逆向變化。根據(jù) W25Q256 的編程特點,針對 1 個KV 鍵值對的 status 狀態(tài)數(shù)值存放位置,可以重復編程,從狀態(tài) 1 到 6 依次順序或者跳越變化。
每個 KV 鍵值對在存放到 NOR Flash 存儲器前,一定要加上描述結(jié)構(gòu)體,再在其后空間存放Key 和 Value 數(shù)據(jù);在讀取 KV 鍵值對時,首先讀取該描述結(jié)構(gòu)體,再通過描述結(jié)構(gòu)體中信息讀取KV 鍵值對。
所有 KV 鍵值對都按照以上描述結(jié)構(gòu)體組成單元依次存放在 KV 鍵值數(shù)據(jù)庫中,從而實現(xiàn)系統(tǒng)運行參數(shù)的保存。
由于 W25Q256 的擦除和編程次數(shù)有限,當需要檢索 1 個系統(tǒng)運行參數(shù)時,不能采用在固定位置建立“索引”的方式快速定位檢索 1 個參數(shù)對應(yīng)的KV 鍵值對,而是采用循環(huán)讀取 KV 鍵值數(shù)據(jù)庫內(nèi)所有 KV 鍵值對的方式檢索參數(shù):從起始位置開始依次讀取 1 個有效完好狀態(tài)的 KV 鍵值對,當 Key 對應(yīng)的數(shù)值與要檢索的參數(shù)相等就返回該 KV 鍵值對,該 KV 鍵值對的 Value 數(shù)值即要查詢的參數(shù)數(shù)值。
當需要新增加 1 個系統(tǒng)運行參數(shù)時,首先在遙測終端機內(nèi)存中新建 1 個類型為 struct KV_data的 KV 鍵值描述結(jié)構(gòu)體,根據(jù)要新增寫入的參數(shù)對應(yīng) KV 鍵值對計算 CRC32 校驗數(shù)值,并填寫描述結(jié)構(gòu)體各字段,其中 status 狀態(tài)初始化為 UNUSEED(FFH);然后從 KV 鍵值數(shù)據(jù)庫存儲器空間定位到有空閑空間的扇區(qū),在該扇區(qū)的空閑起始地址處將該 KV 鍵值描述結(jié)構(gòu)體寫入,寫入完成后將 stauts數(shù)值修改為 PRE_WRITE(7FH);再在緊隨其后的空間開始將 Key 和 Value 依次復制,寫入完成后將status 數(shù)值修改為 WRITE(3FH),此時該 KV 鍵值對已經(jīng)處于有效完好狀態(tài),可以使用了。流程圖如圖1 所示。
圖1 新增寫入?yún)?shù)流程圖
對遙測終端機系統(tǒng)運行參數(shù)的刪除很簡單,在KV 鍵值數(shù)據(jù)庫中通過循環(huán)讀取 KV 鍵值對,檢索到該參數(shù)對應(yīng)的 KV 鍵值對描述結(jié)構(gòu)體,將其中 status字段修改為 DELETED(0FH),此時該鍵值對的狀態(tài)為刪除狀態(tài),不能再被使用了。
在遙測終端機中修改參數(shù),并不是直接對該參數(shù)對應(yīng)的 KV 鍵值對進行修改,而是采用如下步驟:
1)循環(huán)讀取數(shù)據(jù)庫中 KV 鍵值,對定位該參數(shù)對應(yīng)的 KV 鍵值對,將其狀態(tài)由 WRITE(3FH)修改為 PRE_DELETE(1FH);
2)在內(nèi)存中申請 1 個狀態(tài)為 UNUSED(FFH)的 KV 鍵值對描述結(jié)構(gòu)體,根據(jù)參數(shù)的修改內(nèi)容計算新修改參數(shù)的 CRC32 校驗數(shù)值并填寫描述結(jié)構(gòu)體各字段,將該描述結(jié)構(gòu)體在有空閑空間的扇區(qū)內(nèi)(與原有參數(shù) KV 鍵值對可能是同一個扇區(qū),也可能不是同一個扇區(qū))新增寫入,寫入完成后,將其狀態(tài)由 UNUSED(FFH)修改為 PRE_WRITE(7FH);
3)在描述結(jié)構(gòu)體后將新修改參數(shù)對應(yīng)的 KV鍵值對寫入,完成后將該描述結(jié)構(gòu)體的狀態(tài)由PRE_WRITE(7FH)修改為 WRITE(3FH),新修改的 KV 鍵值對處于有效完好狀態(tài);
4)將原有的 KV 鍵值對描述結(jié)構(gòu)體狀態(tài)由PRE_DELETE(1FH)修改為 DELETED(0FH),原有鍵值對為刪除失效狀態(tài)。
通過以上 4 個步驟,就可以完成參數(shù)對應(yīng)的 KV鍵值對的修改,其中每次狀態(tài)的改變都是針對該 KV鍵值對的描述結(jié)構(gòu)體中 status 狀態(tài)字段的重復寫入,每次只變化 1 bit,且是從 1 變化為 0。修改系統(tǒng)運行參數(shù)的流程圖如圖2 所示。
圖2 修改參數(shù)對流程圖
假設(shè)在新增、修改或者刪除參數(shù)的某個執(zhí)行步驟過程中,遙測終端機突然掉電,修改沒有完成,也就是新增加的 KV 鍵值對描述結(jié)構(gòu)體狀態(tài)可能為 UNUSED(FFH)或者 PRE_WRITE(7FH),而原有的參數(shù)對應(yīng) KV 鍵值對描述結(jié)構(gòu)體狀態(tài)為PRE_DELETE(1FH)。
系統(tǒng)重啟后,將對 KV 鍵值數(shù)據(jù)庫中所有的 KV鍵值對做如下操作:
1) 循環(huán)查詢數(shù)據(jù)庫中所有 KV 鍵值對的狀態(tài);
2) 如果發(fā)現(xiàn) 1 個 KV 鍵值對的描述結(jié)構(gòu)體狀態(tài)為 PRE_WRITE(7FH),表明該空間的鍵值對還沒有寫入完成,是無效數(shù)據(jù),應(yīng)立即將其 status 狀態(tài)數(shù)值修改為 ERR_HDR(00H),也就是為錯誤狀態(tài);
3)如果發(fā)現(xiàn) 1 個 KV 鍵值對的狀態(tài)為 PRE_DELETE(1FH),表明該 KV 鍵值對還沒有修改完成,因此需要將該 KV 鍵值對進行恢復。系統(tǒng)將在數(shù)據(jù)庫中新增 1 個參數(shù),該參數(shù)對應(yīng)的 KV 鍵值對數(shù)值與要恢復的 KV 鍵值對相同,新增完成后將原有 KV 鍵值的 status 狀態(tài)由 PRE_DELETE(1FH)修改為 DELETED(0FH),原有鍵值對失效。
通過以上 3 個步驟,實現(xiàn)在異常掉電情況下系統(tǒng)重啟后,對新增、修改或者刪除參數(shù)失敗的 KV鍵值對進行恢復。
在遙測終端機中基于 W25Q256 存儲器中一段固定的存儲空間內(nèi)建立 KV 鍵值數(shù)據(jù)庫,終端機在首次運行初始化后,將所有默認的系統(tǒng)運行參數(shù)依次新增寫入數(shù)據(jù)庫。在系統(tǒng)運行期間需要修改系統(tǒng)運行參數(shù)時,就修改數(shù)據(jù)庫中對應(yīng)的 KV 鍵值對;當需要刪除 1 個參數(shù)時,就刪除數(shù)據(jù)庫中對應(yīng)的 KV鍵值對。系統(tǒng)運行參數(shù)是系統(tǒng)穩(wěn)定運行的基礎(chǔ),如果數(shù)據(jù)出現(xiàn)異常,將會導致系統(tǒng)運行出現(xiàn)不可預知的錯誤,采用 KV 鍵值數(shù)據(jù)庫保存系統(tǒng)運行參數(shù),充分考慮到 NOR Flash 存儲器的特點和水文自動測報系統(tǒng)特殊的應(yīng)用場景,可以有效提高系統(tǒng)的穩(wěn)定可靠性。
KV 鍵值數(shù)據(jù)庫中參數(shù)的新增、修改和刪除操作都不可能存在對同一個固定地址頻繁操作的情況,實現(xiàn)機制是按照數(shù)據(jù)庫存儲空間內(nèi)地址依次增加而操作的,并沒有在固定地址建立索引,當需要針對 1 個參數(shù)操作時,采用循環(huán)方式依次檢索數(shù)據(jù)庫中每個 KV 鍵值對進行相應(yīng)操作,可保證 KV 鍵值數(shù)據(jù)庫存儲空間內(nèi)每個扇區(qū)被擦除和編程的概率是基本一樣的,達到磨損均衡的目的,能有效延長W25Q256 存儲器的使用壽命。
本研究充分利用 NOR Flash 的讀寫特點,基于W25Q256 存儲器實現(xiàn)了一個 KV 鍵值數(shù)據(jù)庫的設(shè)計,可對遙測終端機中系統(tǒng)運行參數(shù)進行新增、修改、刪除和查詢操作,從而使 W25Q256 的存儲性能有了較大的改善,而且數(shù)據(jù)的可靠性也有很大提高,其最主要的特點是磨損均衡和異常掉電恢復,特別適用于遙測終端機中 NOR Flash 存儲器無文件系統(tǒng)管理的參數(shù)存儲。
在實際運行中,對 KV 鍵值對修改和刪除過程中產(chǎn)生的狀態(tài)為 DELETED 或 ERR_HDR 的無用鍵值對占用的空間要及時回收,以更有效地利用存儲器空間。同時為加快訪問 KV 鍵值數(shù)據(jù)庫的速度,可以考慮在終端機內(nèi)存中建立緩存區(qū)間,將常用的 KV鍵值對放入緩沖區(qū),并建立 1 套緩存與 KV 鍵值數(shù)據(jù)庫的管理機制。今后,針對 KV 鍵值數(shù)據(jù)庫中資源回收和緩存機制功能需要做進一步研究和優(yōu)化。