0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

用于管理文件方法和數(shù)據(jù)結(jié)構(gòu)案例分析

UtFs_Zlgmcu7890 ? 來源:未知 ? 作者:劉勇 ? 2018-08-03 09:28 ? 次閱讀

本文導(dǎo)讀

文件系統(tǒng)是在存儲設(shè)備中(SD Card、NAND Flash…)組織文件的方法和數(shù)據(jù)結(jié)構(gòu),用于管理文件。AWorks定義了文件系統(tǒng)的通用接口,例如,打開、讀/寫、關(guān)閉文件等等。文件系統(tǒng)的具體實現(xiàn)可以自由選擇,例如,F(xiàn)AT、UFFS、YAFFS2 等等。

本文為《面向AWorks框架和接口編程》第三部分軟件篇——第11章文件系統(tǒng)——第1~5小節(jié):文件系統(tǒng)簡介、設(shè)備掛載管理、文件基本操作、目錄基本操作和微型數(shù)據(jù)庫。

本章導(dǎo)讀

文件系統(tǒng)是用于管理文件的方法和數(shù)據(jù)結(jié)構(gòu),文件和文件系統(tǒng)相關(guān)的數(shù)據(jù)存儲在實際的物理介質(zhì)中,例如,NAND FLASH,Nor FLASH或SD Card等。AWorks定義了文件系統(tǒng)的通用接口,無論底層使用何種文件系統(tǒng),比如常見的FAT16/32、UFFS或YAFFS2等,均可使用同一套接口進行文件相關(guān)的操作。

11.1 文件系統(tǒng)簡介

在文件系統(tǒng)中,存儲數(shù)據(jù)的基本單位是文件,即數(shù)據(jù)是按照一個一個文件的方式進行組織的。當(dāng)文件較多時,將導(dǎo)致文件繁多、不易分類、重名等問題,為此,在文件的基礎(chǔ)上,提出了目錄的概念,相當(dāng)于Windows系統(tǒng)中的文件夾,一個目錄中,可以包含多個文件。特別地,一個目錄中,除包含文件外,還可以包含子目錄,子目錄可以繼續(xù)包含子目錄。最上層的目錄被稱為根目錄,示意圖詳見圖11.1。

圖11.1 目錄結(jié)構(gòu)示意圖

圖中的目錄名和文件名僅用作示例,與實際系統(tǒng)的目錄樹結(jié)構(gòu)并不存在任何關(guān)聯(lián)。在AWorks中,根目錄使用斜杠(即:"/")表示,應(yīng)注意與反斜杠(即:"")進行區(qū)分,不可混用。存于根目錄中的文件,其完整路徑直接使用“斜杠 + 文件名”的形式表示,如圖11.1中的test1.txt文件,其路徑為"/test1.txt",若一個文件所在目錄不是根目錄時,則多個目錄之間要使用斜杠(即:"/")分隔,比如,test5.txt文件對應(yīng)的完整路徑應(yīng)表示為"/usr/base/test5.txt"。

AWorks中分隔符使用"/",這與UNIX/Linux的風(fēng)格是完全相同的,而與Windows則不相同,Windows中使用反斜杠""作為分隔符。

11.2 設(shè)備掛載管理

在圖11.1中,為用戶展示了一個目錄樹結(jié)構(gòu),圖中的每個文件都存放在相應(yīng)的目錄中,目錄是一個虛擬的邏輯概念,便于用戶按照邏輯路徑訪問各個文件。而實質(zhì)上,文件數(shù)據(jù)是存放在物理存儲介質(zhì)中的,例如,NAND FLASH,Nor FLASH或SD Card等。為此,就需要在系統(tǒng)中將目錄與某一物理存儲介質(zhì)相關(guān)聯(lián),用戶存放在某一目錄下的文件都自動保存到對應(yīng)的物理存儲介質(zhì)中,這種關(guān)聯(lián)操作可以通過“設(shè)備掛載”來實現(xiàn)。“設(shè)備掛載”用于將物理存儲介質(zhì)掛載到某一目錄,使該物理存儲介質(zhì)與該目錄對應(yīng),后續(xù)所有在該目錄下的文件(或子目錄)實際上都存儲到了與之對應(yīng)的存儲介質(zhì)中。

在一個系統(tǒng)中,往往存在多種存儲介質(zhì),例如,NAND FLASH,SD Card或外接的U盤等,無論存在多少存儲介質(zhì),對于用戶來講,其都是使用圖11.1所示的一個目錄樹來進行文件的管理,根目錄只會有一個,不會因為具有多個存儲介質(zhì)而產(chǎn)生多個根目錄。

實際應(yīng)用中,為了方便管理,可以進行更細的劃分,將一個硬件存儲介質(zhì)分成多個區(qū),此時,每個分區(qū)可以看作一個獨立的存儲介質(zhì),掛載到某一目錄。例如,一個SD卡的容量是2G,可以分成大小不同的4個分區(qū),例如,SD_S0(1G)、SD_S1(512M)、SD_S2(256M)、SD_S3(256M)。此時,4個分區(qū)可以當(dāng)作4個獨立的存儲介質(zhì),分別掛載到不同的目錄。

對于一個物理存儲介質(zhì),需要使用某一具體的文件系統(tǒng)對其中的文件數(shù)據(jù)進行管理,比如常見的FAT16/32、UFFS或YAFFS2等,它們各有優(yōu)缺點,針對特定的存儲介質(zhì),可以選擇合適的文件系統(tǒng)。如FAT16/32常用于U盤、SD卡中,UFFS和YAFFS2常用于NAND FLASH中。同一系統(tǒng)中,不同的物理存儲介質(zhì)可以使用不同的文件系統(tǒng)。一般來講,初次使用某一硬件存儲介質(zhì)時,需要進行格式化操作,指定使用的文件系統(tǒng),存儲一些與文件系統(tǒng)相關(guān)的初始數(shù)據(jù)。格式化后,才可將該設(shè)備掛載到目錄樹中。通常情況下,存儲設(shè)備掉電不會丟失數(shù)據(jù),因此,格式化操作僅需執(zhí)行一次,不需要每次上電都執(zhí)行。

AWorks提供了抽象的文件系統(tǒng)接口和框架,即使系統(tǒng)中不同存儲介質(zhì)使用了不同的文件系統(tǒng),對于用戶來講,仍然可以使用相同的接口進行文件相關(guān)的操作。

AWorks提供了管理硬件存儲設(shè)備的接口,相關(guān)接口的函數(shù)原型詳見表11.1。

表11.1 存儲設(shè)備管理相關(guān)接口(fs/aw_mount.h)

1. 格式化存儲設(shè)備

格式化存儲設(shè)備,以指定使用的文件系統(tǒng),并存儲一些與文件系統(tǒng)相關(guān)的初始數(shù)據(jù)。其函數(shù)原型為:

其中,dev_name為存儲設(shè)備的名字,fs_name為使用的文件系統(tǒng)的名字,fmt_arg為格式化相關(guān)的附加信息。返回值為標(biāo)準(zhǔn)的錯誤號,返回AW_OK時,表示格式化成功,否則,表示格式化失敗,可能是由于硬件設(shè)備不存在或文件系統(tǒng)不支持造成的。

存儲設(shè)備的名字與具體的物理存儲介質(zhì)相關(guān)。例如,常見的SD卡設(shè)備,其對應(yīng)的設(shè)備名為:/dev/sdx,其中,x為SD卡所處的SDIO總線序號,比如:0、1、2等。為便于敘述,這里將SD卡和TF卡統(tǒng)稱為SD卡存儲設(shè)備,SD卡就是常見的大卡,常用于相機中,如圖11.2(a)所示。

TF卡又稱Micro SD卡,體積比SD卡小,很多手機都配備了TF卡接口,用于擴展存儲容量,如圖11.2(b)所示。除了體積大小的區(qū)別外,SD卡在左側(cè)還有一個LOCK開關(guān),用于鎖定SD卡,從硬件上開啟寫保護,避免數(shù)據(jù)損壞。

圖11.2 SD卡與TF卡

在i.MX28x硬件平臺中,僅在SDIO0總線上設(shè)置了一個TF卡卡槽,因而僅能插入一張TF卡,不能插入SD卡。若正確插入了TF卡,則該TF卡對應(yīng)的設(shè)備名為"/dev/sd0"。為便于驗證后續(xù)程序,讀者可以準(zhǔn)備一張TF卡。

除使用TF卡外,還可以使用U盤進行測試,在i.MX28x平臺中設(shè)置了USB接口,默認情況下,USB HOST1接口用于外接USB設(shè)備,USB HOST0接口用于外接USB主機,將自身模擬為一個USB設(shè)備??梢酝ㄟ^USB HOST1接口外接U盤,U盤在系統(tǒng)中對應(yīng)的存儲設(shè)備名為"/dev/ms0-ud0"。

在編程上,不同的物理存儲設(shè)備僅僅是設(shè)備名發(fā)生了變化,沒有其它任何區(qū)別,U盤和TF卡的具體細節(jié)差異用戶無需關(guān)心。為便于敘述,后文統(tǒng)一使用TF卡存儲設(shè)備進行舉例說明,若讀者使用的是U盤,僅需將范例程序中出現(xiàn)的TF卡設(shè)備名修改為"/dev/ms0-ud0"。

fs_name為文件系統(tǒng)的名字,比如:"vfat"、"uffs"、"yaffs"等,其代表了此硬件設(shè)備使用的具體文件系統(tǒng)。如使用FAT,則文件系統(tǒng)名為"vfat",其會自動根據(jù)存儲器特性選擇使用合適的FAT文件系統(tǒng):FAT12、FAT16或FAT32。

fmt_arg為格式化相關(guān)的信息,不同文件系統(tǒng)使用的信息可能會不同,其類型struct aw_fs_format_arg (fs/aw_fs_type.h)定義如下:

對于FAT文件系統(tǒng), 其管理的一個存儲設(shè)備或分區(qū)稱之為“卷”(volume),vol_name即為該卷指定一個卷名,當(dāng)前系統(tǒng)中,卷名僅作標(biāo)識,未用作其它特殊用途,可任意指定一個合理的名字,比如:"awdisk"。

在FAT文件系統(tǒng)中,存儲設(shè)備是以“分配單元”(allocation unit)為單位進行數(shù)據(jù)管理的, unit_size指定了每個分配單元的大小,分配單元在FAT中又被稱為“簇”(cluster)。

在允許范圍內(nèi),分配單元大小設(shè)置越大,讀寫速度越快,反之則越慢。但是,需要注意的是,分配單元越大,也越有可能造成空間的浪費,因為即便一個文件的大小遠遠小于分配單元大小,也會占用一個完整的分配單元。unit_size必須為硬件存儲設(shè)備扇區(qū)大?。ㄍǔ9潭?12)的整數(shù)倍,且倍數(shù)必須是2的冪,比如:1、2、4、8等,可以將unit_size設(shè)置為4096(512×8),通常情況下,unit_size的有效范圍為:512 ~ 32768。為了便于使用,避免設(shè)置錯誤,也可以將該值設(shè)置為0,系統(tǒng)將自動根據(jù)存儲器容量選擇一個合適的值。

FAT文件系統(tǒng)又可以細分為FAT12、FAT16、FAT32,它們最明顯的區(qū)別就是對分配單元進行尋址的位數(shù)不同,分別為12位、16位、32位。例如,對于FAT12,其使用12位地址對分配單元進行尋址,因此,理論上最大只能管理212=4096個分配單元(實際上,部分地址用作它用,管理的分配單元要略小于該值),若每個分配單元的大小為32K,則FAT12管理存儲設(shè)備的最大容量為:32K*4096 = 128M。同理,可得到FAT16管理的存儲設(shè)備最大容量為2G,雖然FAT32理論上可以管理的容量達到T級別,但實際中,當(dāng)存儲設(shè)備的容量超過32G時,不再建議使用FAT,例如,在Windows中,可以使用NTFS等其它文件系統(tǒng)。用戶無需明確指定使用何種FAT文件系統(tǒng),系統(tǒng)將根據(jù)設(shè)備容量自動進行選擇。

對于FAT文件系統(tǒng),flags標(biāo)志未使用,設(shè)置為0即可。基于此,格式化TF卡的范例程序詳見程序清單11.1。

程序清單11.1 式化范例程序

若未插入TF卡或剛插入TF卡但還未初始化完成,則"/dev/sd0"設(shè)備是不存在的,此時,aw_make_fs()函數(shù)將返回錯誤號:-AM_ENODEV。

由于程序在系統(tǒng)啟動后將立即執(zhí)行,TF卡可能還未及時插入或初始化完成。因此,程序中,當(dāng)格式化函數(shù)的返回值為-AM_ENODEV時,繼續(xù)重試。為便于測試,用戶應(yīng)盡快插入TF卡。格式化操作通常比較費時,作者在使用程序清單11.1所示程序格式化一張8G的TF卡時,耗時約8秒。

格式化僅需執(zhí)行一次,若本次格式化成功,則后續(xù)再進行其它操作時,無需再格式化。特別地,格式化操作會刪除存儲設(shè)備上所有的原始數(shù)據(jù),應(yīng)謹慎使用。

在程序清單11.1中,若TF卡還未準(zhǔn)備就緒(TF卡未插入或剛插入但還未初始化完成),則不斷重試。為了避免不斷重試,使程序邏輯更加清晰易懂,可以在上電后等待設(shè)備就緒后再執(zhí)行格式化操作,等待設(shè)備就緒的函數(shù)原型為(fs/aw_blk_dev.h):

該接口是用于等待一個塊設(shè)備準(zhǔn)備就緒,常見的U盤、SD卡、TF卡等都屬于塊設(shè)備(即在物理操作上,數(shù)據(jù)是以塊為單位,比如:512字節(jié),進行數(shù)據(jù)的寫入和讀取的,而不是以單個字節(jié)為單位)。其中p_name為設(shè)備名,例如,"/dev/sd0",timeout用于指定超時時間(單位為系統(tǒng)節(jié)拍)。

若設(shè)備已就緒,則直接返回AW_OK;若設(shè)備未就緒,則具體的行為與timeout的值相關(guān)。若timeout的值為AW_WAIT_FOREVER。則程序會阻塞于此,永久等待,直到設(shè)備準(zhǔn)備就緒;若值為AW_NO_WAIT,則不會阻塞,立即返回錯誤號-AW_EAGAIN;若值為一個正整數(shù),則表示最長的等待時間(單位為系統(tǒng)節(jié)拍),在超時時間內(nèi)設(shè)備就緒則返回AW_OK,否則,返回-AW_ETIME表示超時。

優(yōu)化程序清單11.1,避免不斷重試,僅當(dāng)設(shè)備就緒后再執(zhí)行格式化操作。范例程序詳見程序清單11.2。

程序清單11.2 格式化范例程序(設(shè)備就緒后再執(zhí)行格式化操作)

2. 掛載設(shè)備

為了使用目錄樹結(jié)構(gòu)管理文件,并將文件保存到存儲設(shè)備(如TF卡)中,必須將存儲設(shè)備掛載到某一目錄。掛載操作的函數(shù)原型為:

其中,mnt為掛載點,其為新建的一個目錄結(jié)點,使用全路徑表示,比如:"/test",其表示在根目錄下創(chuàng)建了一個名為test的掛載點,后續(xù)訪問"/test "目錄即表示訪問本次掛載的存儲設(shè)備;dev為存儲設(shè)備的名字,如果使用TF卡,則對應(yīng)的設(shè)備名為"/dev/sd0";fs為存儲設(shè)備使用的文件系統(tǒng)名,如果使用FAT文件系統(tǒng),則文件系統(tǒng)名為"vfat";flags為掛載時的一些選項標(biāo)識,當(dāng)前無可用標(biāo)識,預(yù)留給后續(xù)擴展使用,設(shè)置為0即可。返回值為標(biāo)準(zhǔn)的錯誤號,返回AW_OK時,表示掛載成功,否則,表示掛載失敗。掛載TF卡的范例程序詳見程序清單11.3。

程序清單11.3 掛載TF卡范例程序

注意,當(dāng)前掛載信息不會保存到存儲設(shè)備中,相關(guān)信息僅保留在內(nèi)存中,因此,每次重新上電時,都應(yīng)該執(zhí)行掛載操作。

掛載完畢后,即在根目錄下創(chuàng)建了一個test掛載點,對于用戶來講,后續(xù)即可在該目錄下進行文件相關(guān)的操作,例如,創(chuàng)建文件、讀取文件、寫入文件等操作。此外,還可以在該目錄下創(chuàng)建子目錄,這些文件和子目錄信息都存儲在TF卡中,掉電不會丟失。

3. 取消掛載

當(dāng)設(shè)備不再使用時,可以取消該設(shè)備的掛載,掛載點將刪除,用戶不可再訪問該目錄。取消掛載的函數(shù)原型為:

其中,path為路徑名,可以是設(shè)備名(比如:"/dev/sd0"),也可以是掛載點(即掛載時指定的mnt參數(shù),比如:"/test")。flags為掛載時的一些選項標(biāo)識,當(dāng)前無可用標(biāo)識,預(yù)留給后續(xù)擴展使用,設(shè)置為0即可。返回值為標(biāo)準(zhǔn)的錯誤號,返回AW_OK時,表示取消掛載成功,否則,表示取消掛載失敗。取消掛載的范例程序詳見程序清單11.4。

程序清單11.4 取消掛載范例程序

由于在掛載設(shè)備時,已經(jīng)將掛載點和設(shè)備名進行了關(guān)聯(lián),因此,在取消掛載時,只要知道掛載點和設(shè)備名中任何一個信息,均可得到完整的掛載信息,然后取消掛載。在程序清單11.4中,是通過掛載時使用的掛載點取消掛載,也可以通過設(shè)備名取消掛載,例如,將第19行代碼中的"/test"修改為"/dev/sd0"。

11.3 文件基本操作

文件相關(guān)的操作主要包括打開文件(創(chuàng)建文件)、關(guān)閉文件、讀取文件數(shù)據(jù)、寫入數(shù)據(jù)等。相關(guān)接口的原型詳見表11.2。

表11.2 文件基本操作相關(guān)接口

1.打開文件

打開或創(chuàng)建一個文件的函數(shù)原型為:

其中,path為包含文件名的完整路徑,如需在test目錄下創(chuàng)建一個fs_test.txt文件,則path應(yīng)為"/test/fs_test.txt",文件名建議使用8.3格式,即主文件名長度不超過8,擴展名長度不超過3。因為部分文件系統(tǒng)不支持更長的文件名,使用8.3格式的文件名兼容性更好。

oflag指定打開文件的方式,當(dāng)前支持的打開方式詳見表11.3。

表11.3 打開文件方式(io/aw_fcntl.h)

mode用于控制訪問權(quán)限,其類型為mode_t,mode_t是一個整數(shù)類型,實際類型與具體平臺相關(guān),通常情況下,其為32位無符號整數(shù)。在當(dāng)前系統(tǒng)中,mode為與POSIX標(biāo)準(zhǔn)接口兼容的參數(shù),目前沒有使用,可以設(shè)置為0。在支持該參數(shù)的平臺中,其用于控制用戶訪問文件的權(quán)限,僅當(dāng)創(chuàng)建文件時有效,有效位共計9位,每3位為1組,共計3組。3組分別控制3類用戶的權(quán)限:當(dāng)前用戶、組用戶、其它用戶。每組3位分別控制3種權(quán)限:讀、寫、執(zhí)行,相應(yīng)位為1,表明該類用戶具有相應(yīng)的權(quán)限。各組用戶權(quán)限的控制位詳見表11.4。

表11.4 權(quán)限控制

由于mode中每3位為一組,而3位二進制數(shù)據(jù)恰好可以使用1位八進制數(shù)據(jù)(0 ~ 7)表示。因此,為了便于閱讀,mode的值往往使用八進制表示。如0777(在C語言中,數(shù)據(jù)前加0表示八進制數(shù)據(jù)),表示所有用戶都具有讀、寫、執(zhí)行的權(quán)限。為了使應(yīng)用程序更具有兼容性,可以將mode的值設(shè)置為0777。

若文件打開成功,則返回文件的句柄,后續(xù)使用該句柄進行文件的讀寫操作。特別地,若返回值為-1,則表示打開文件失敗。如打開test目錄下的fs_test.txt文件(若不存在該文件,則創(chuàng)建該文件)的范例程序詳見程序清單11.5。

程序清單11.5 打開文件范例程序

2. 創(chuàng)建文件

創(chuàng)建文件的函數(shù)原型為:

其中,path為包含文件名的完整路徑,注意,創(chuàng)建文件時,需要保證path指定的路徑是有效的,若其父文件夾不存在,則會創(chuàng)建失敗。mode為文件的模式。其本質(zhì)上等效于:

由此可見,其是以只寫方式打開文件的,若文件不存在,由于使用了O_CREAT標(biāo)識,則創(chuàng)建文件,若文件已穿在,由于使用了O_TRUNC標(biāo)識,則會將原文件的內(nèi)容清空,長度截斷為0,相當(dāng)于創(chuàng)建了一個新文件。

若文件創(chuàng)建成功,則返回文件的句柄,后續(xù)使用該句柄進行文件的讀寫操作。特別地,若返回值為-1,則表示創(chuàng)建文件失敗。如在test目錄下創(chuàng)建一個fs_test2.txt文件的范例程序詳見程序清單11.6。

程序清單11.6 創(chuàng)建文件范例程序

3. 關(guān)閉文件

文件打開或創(chuàng)建后,若不再需要使用文件,則必須關(guān)閉該文件,文件關(guān)閉后,文件相關(guān)的數(shù)據(jù)才會被可靠的寫回硬件存儲設(shè)備。關(guān)閉文件的函數(shù)原型為:

其中,filedes為待關(guān)閉文件的句柄,其值是打開文件或創(chuàng)建文件時返回的文件句柄。返回值為錯誤號,若返回AW_OK,則表示關(guān)閉文件成功,否則,表示關(guān)閉文件失敗。范例程序詳見程序清單11.7。

程序清單11.7 關(guān)閉文件范例程序

4. 寫入數(shù)據(jù)

文件打開后,可以寫入數(shù)據(jù)至文件中,其函數(shù)原型為:

其中,filedes為文件句柄,buf為待寫入數(shù)據(jù)的緩沖區(qū),nbytes為寫入數(shù)據(jù)的字節(jié)數(shù)。返回值為成功寫入的字節(jié)數(shù),特別地,若返回值為負值,則表示寫入數(shù)據(jù)失敗,可能是由于打開文件的方式不對造成的,例如,打開文件時,使用了只讀的方式打開文件;若返回值小于nbytes,則可能是由于存儲設(shè)備容量不足造成的。

對于每個打開的文件,系統(tǒng)中都使用了一個與其關(guān)聯(lián)的整數(shù)變量來表示該文件的讀寫位置,其值為相對于文件起始位置的偏移量。文件讀寫位置將決定下一次讀/寫數(shù)據(jù)時的位置。打開文件時,若未指定O_APPEND標(biāo)志,則讀寫位置初始為0,否則,讀寫位置在文件結(jié)尾,例如,文件大小為10,則讀寫位置的值即為10。每次寫入數(shù)據(jù)完畢后,都將自動更新讀寫位置至本次寫入數(shù)據(jù)的尾部,例如,寫入10個數(shù)據(jù),則讀寫位置的值將自動增加10,以便下次寫入數(shù)據(jù)時,緊接著尾部繼續(xù)寫入。

例如,打開文件,寫入一串字符串,最后再關(guān)閉文件的完整范例程序詳見程序清單11.8。

程序清單11.8 寫入數(shù)據(jù)范例程序

若程序運行成功,則在TF卡中創(chuàng)建了一個fs_test.txt文件,同時,在文件中寫入了字符串"just for test:0123456789"。為了驗證操作是否成功,可以拔下TF卡,將TF卡通過讀卡器連接到PC上(Windows系統(tǒng)),通過PC查看TF卡中的內(nèi)容,可以看到TF卡目錄內(nèi)容和fs_test.txt文件內(nèi)容詳見圖11.3。

圖11.3 通過PC查看TF卡的內(nèi)容(1)

5. 讀取數(shù)據(jù)

文件打開后,可以從文件中讀取數(shù)據(jù),其函數(shù)原型為:

其中,filedes為文件句柄,buf為保存讀取數(shù)據(jù)的緩沖區(qū),nbytes為讀取數(shù)據(jù)的字節(jié)數(shù)。返回值為成功讀取的字節(jié)數(shù),特別地,若返回值為負值,則表示讀取數(shù)據(jù)失敗,可能是由于打開文件的方式不對造成的,例如,打開文件時,使用了只寫的方式打開文件;若返回值不小于0,但小于nbytes,則表示文件數(shù)據(jù)不足,已經(jīng)讀取至文件結(jié)尾。

每次讀取數(shù)據(jù)完畢后,都將自動更新讀寫位置至本次讀取數(shù)據(jù)的尾部,例如,讀取10個數(shù)據(jù),則讀寫位置的值將自動增加10,以便下次讀取數(shù)據(jù)時,緊接著尾部繼續(xù)讀取。

例如,以只讀方式打開之前創(chuàng)建的/test/fs_test.txt文件,讀取文件內(nèi)容,以判斷讀寫是否正確的范例程序詳見程序清單11.9。

程序清單11.9 讀取數(shù)據(jù)范例程序

6. 改變文件讀寫位置

對于每個打開的文件,都有一個“讀寫位置(相對于文件起始位置的偏移量)”來表示下一次讀/寫數(shù)據(jù)時的起始位置,其值除在每次讀/寫數(shù)據(jù)時自動更新外,還可以使用aw_lseek()函數(shù)手動改變,aw_lseek()的函數(shù)原型為:

其中,filedes為文件句柄,offset為設(shè)置的偏移量,whence指定offset偏移量的基準(zhǔn)位置。返回值為新的“讀寫位置”。

若whence的值為SEEK_SET,表示offset偏移量是以文件起始位置為基準(zhǔn),就相當(dāng)于直接設(shè)置“讀寫位置”的值為offset。使用SEEK_SET的范例程序詳見程序清單11.10。

程序清單11.10 SEEK_SET范例程序

若whence的值為SEEK_CUR,表示offset偏移量是以當(dāng)前“讀寫位置”為基準(zhǔn),即將“讀寫位置”的值加上offset作為新的“讀寫位置”,offset可正可負,為正時,增大“讀寫位置”的值,表示“讀寫位置”向文件尾部方向移動,為負時,減小“讀寫位置”的值,表示“讀寫位置”向文件頭部方向移動。使用SEEK_CUR的范例程序詳見程序清單11.11。

程序清單11.11 SEEK_CUR范例程序

若whence的值為SEEK_END,表示offset偏移量是以文件尾部為基準(zhǔn),即將“讀寫位置”的值設(shè)置為文件大小加上offset作為新的“讀寫位置”。使用SEEK_END的范例程序詳見程序清單11.12。

程序清單11.12 SEEK_END范例程序

值得注意的是,若移動后的“讀寫位置”大于當(dāng)前文件大小,則移動的結(jié)果將與具體的文件系統(tǒng)相關(guān)。通常情況下,“讀寫位置”被重置為當(dāng)前文件大小,即原文件尾部,部分特殊情況下,也可能會擴充文件的大小??梢酝ㄟ^返回值判斷當(dāng)前實際的“讀寫位置”。出于兼容性考慮,不建議將“讀寫位置”移動至當(dāng)前文件的有效范圍之外。

可以修改程序清單11.9所示的程序,在讀取數(shù)據(jù)前,設(shè)定“讀寫位置”為14,然后讀取10個字符,即可僅讀取出字符串:"0123456789",范例程序詳見程序清單11.13。

程序清單11.13 讀取指定位置數(shù)據(jù)段的范例程序

7. 截斷文件

用于將文件截斷為指定長度,超出長度的內(nèi)容將被刪除,一般情況下,都通過文件描述符filedes指定要截斷的文件,其函數(shù)原型如下:

其中,filedes為文件句柄,length為新的文件長度,如果length小于原文件長度,文件將被截斷。返回值為AW_OK時,表示截斷成功,否則,表示截斷失敗。如將fs_test.txt文件長度截斷為14,以刪除結(jié)尾的字符串:"0123456789",則范例程序詳見程序清單11.14。

程序清單11.14 截斷文件范例程序

在程序清單11.14中,截斷一個文件主要分為3步:打開文件、截斷文件、關(guān)閉文件。為了簡化截斷文件的操作,AWorks提供了另外一個功能相同的接口,但其通過文件名指定要截斷的文件,使用起來更加便捷,其函數(shù)原型為:

其中,path為文件的路徑,length為新的文件長度。用戶無需在截斷文件前打開文件,該函數(shù)將在內(nèi)部打開文件,截斷后關(guān)閉文件。如使用該接口,則一行代碼即可實現(xiàn)與程序清單11.14相同的功能,即:

8. 修改文件名

該函數(shù)用于修改指定文件的文件名,函數(shù)原型為:

其中,oldpath為原文件名,newpath為新文件名。返回值為AW_OK時表示更名成功,否則表示更名失敗。修改"/test/fs_test.txt"為"/test/fs_test3.txt"的范例程序詳見程序清單11.15。

程序清單11.15 修改文件名范例程序

注意,若newpath指定的文件已存在,則該文件將會被覆蓋。特別地,若newpath與oldpath相同,則函數(shù)不做任何處理,直接返回成功。

9. 同步文件

通常情況下,在操作文件的過程中,文件相關(guān)的數(shù)據(jù)并不會立即寫入到存儲設(shè)備中,而是保存在內(nèi)存中,這樣可以在一定程度上提高數(shù)據(jù)讀寫的效率。在關(guān)閉文件時,再將該文件相關(guān)的所有數(shù)據(jù)保存到存儲設(shè)備中。在一些比較耗時的文件操作過程中,為了避免中途突然掉電導(dǎo)致數(shù)據(jù)丟失,也可以通過aw_fsync()函數(shù)立即執(zhí)行回寫操作,使文件相關(guān)的數(shù)據(jù)保存到存儲設(shè)備中。這就類似于在編寫一個Word文檔的過程中,需要時常點擊保存按鈕,避免突然掉電導(dǎo)致部分數(shù)據(jù)丟失。其函數(shù)原型為:

其中,filedes為文件句柄。返回值為AW_OK時表示同步成功,否則,表示同步失敗。范例程序詳見程序清單11.16。

程序清單11.16 同步文件范例程序

10. 刪除文件

當(dāng)一個文件不再使用,可以刪除該文件,刪除指定文件的函數(shù)原型為:

其中,path為文件的路徑。返回值為AW_OK時表示刪除成功,否則,表示刪除失敗。范例程序詳見程序清單11.17。

程序清單11.17 刪除文件范例程序

11. 獲取文件狀態(tài)信息

對于一個文件,除基本數(shù)據(jù)外(使用讀寫接口操作的數(shù)據(jù)),還具有一些與文件相關(guān)的其它信息,比如:文件實際大小、文件占用大小、修改時間、訪問權(quán)限等。這些信息統(tǒng)一被稱為狀態(tài)信息,可以通過aw_fstat()接口獲取文件的狀態(tài)信息,其函數(shù)原型為:

其中,fildes為文件句柄,buf為文件狀態(tài)信息的緩存。返回值為AW_OK時表示獲取成功,否則,表示獲取失敗。文件狀態(tài)信息的類型為struct aw_stat,其完整定義如下(io/sys/aw_stat.h):

其中的絕大部分成員在當(dāng)前系統(tǒng)中并未使用,預(yù)留給后續(xù)擴展。這里僅簡要介紹幾個常用的數(shù)據(jù)成員:st_mode、st_size、st_atim、st_mtim和st_ctim。

st_mode包含了文件類型及權(quán)限相關(guān)的信息,權(quán)限相關(guān)信息與創(chuàng)建文件時指定的mode參數(shù)一致。

st_size表示文件的實際長度。通常情況下,文件占用的磁盤空間會大于該值。如在FAT文件系統(tǒng)中,存儲文件的基本單元為“簇”,即使文件小于基本的存儲單元大小,也會占用一個完整的存儲單元。文件占用的磁盤空間可由st_blksize和st_blocks獲得,st_blksize表示存儲單元的大小,st_blocks表示文件占用的存儲單元個數(shù),兩者的乘積則表示文件占用的空間大小。

st_atim、st_mtim、st_ctim表示文件相關(guān)的時間,st_atim為最后訪問文件的時間,st_mtim為最后修改文件內(nèi)容的時間,st_ctim為最后修改文件狀態(tài)的時間。在部分平臺中,可能沒有嚴格細分這些時間,而是統(tǒng)一的使用一個時間表示,此時,各個時間的值將是相同的。這些時間的類型為struct timespec,其表示精確日歷時間,即:

精確日歷時間中包含了秒和納秒信息。可以通過將該時間轉(zhuǎn)換為細分時間,得到年、月、日、時、分、秒等信息。獲取文件狀態(tài)信息的范例程序詳見程序清單11.18。

程序清單11.18 獲取文件狀態(tài)信息的范例程序

和截斷文件類似,AWorks也提供了另外一個功能相同的接口,可以通過文件名指定要獲取狀態(tài)信息的文件,使用起來更加便捷,其函數(shù)原型為:

其中,path為文件路徑,buf為文件狀態(tài)信息的緩存。返回AW_OK時表示獲取成功,否則,表示獲取失敗??梢允褂迷摻涌诤喕绦蚯鍐?1.18的第21、22、29共計3含代碼,使用一行代碼代替,即:

12. 修改文件時間

一個文件的存取和修改時間可以用 aw_utime() 函數(shù)更改,其函數(shù)原型為:

其中,path為文件的路徑,times為設(shè)置的時間。struct aw_utimbuf類型的定義(io/aw_utime.h)如下:

其中,actime表示文件最近一次的訪問時間,modtime表示文件最近一次的內(nèi)容修改時間,它們的類型均為time_t,time_t是日歷之間類型,即actime和modtime均使用日歷時間表示。返回AW_OK時,表示時間設(shè)置成功,否則,表示時間設(shè)置失敗。如設(shè)置文件訪問時間和文件修改時間均為2016年8月26日09:32:30,則范例程序詳見程序清單11.19。

程序清單11.19 修改文件時間的范例程序

程序中,首先使用aw_utime()修改了文件時間,然后使用aw_stat()獲取文件的狀態(tài)信息,以查看時間是否設(shè)置成功。

11.4 目錄基本操作

目錄相關(guān)的操作主要包括創(chuàng)建目錄、打開目錄、讀取目錄、關(guān)閉目錄、刪除目錄等。相關(guān)接口的原型詳見表11.5。

表11.5 目錄基本操作相關(guān)接口

1. 創(chuàng)建目錄

創(chuàng)建一個空目錄,其函數(shù)原型為:

其中,path是待創(chuàng)建目錄的完整路徑,包括待創(chuàng)建目錄的父目錄和新目錄的名字,如需在"/test"目錄下創(chuàng)建一個"newdir"目錄,則path為"/test/newdir",注意,必須保證父目錄已存在,且父目錄下不存在即將創(chuàng)建的同名目錄。mode指定目錄相關(guān)的權(quán)限,默認使用0777。

返回值為標(biāo)準(zhǔn)的錯誤號,若返回AW_OK,表示新目錄創(chuàng)建成功,否則,表示新目錄創(chuàng)建失敗。如在"/test"目錄下創(chuàng)建一個"newdir"目錄,則范例程序詳見程序清單11.20。

程序清單11.20 創(chuàng)建目錄范例程序

若程序運行成功,則在TF卡中創(chuàng)建了一個newdir目錄。為了驗證操作是否成功,可以拔下TF卡,將TF卡通過讀卡器連接到PC上(Windows系統(tǒng)),通過PC查看TF卡中的內(nèi)容,TF卡目錄的內(nèi)容詳見圖11.4 (a),其中新增了newdir目錄,且newdir當(dāng)前是一個空目錄,詳見圖11.4(b)。

圖11.4 通過PC查看TF卡的內(nèi)容(2)

可以嘗試在newdir目錄中新建文件,例如:

2. 打開目錄

在目錄操作中,遍歷已存在的一個目錄是一種常見的操作,即遍歷一個目錄下所有的子項(包括文件和子目錄)。遍歷過程主要分為3個步驟:打開目錄、讀取目錄、關(guān)閉目錄。

在遍歷一個目錄前,首先需要打開該目錄,其函數(shù)原型為:

其中,dirname為目錄名,如需打開根目錄下的test目錄,則其值為"/test"。返回值為指向一個目錄對象的指針,目錄類型struct aw_dir 在io/aw_dirent.h文件中定義,其具體定義用戶無需掌握,僅需保存下該指針,后續(xù)使用該指針代表打開的目錄即可,特別地,若返回值為NULL,則表示目錄打開失敗。打開目錄的范例程序詳見程序清單11.21。

程序清單11.21 打開目錄范例程序

3. 讀取目錄

打開目錄后,即可依次讀取該目錄中的各個子項(文件或子目錄),獲得它們的名字等信息,其函數(shù)原型為:

其中,dirp為指向目錄對象的指針,可通過打開目錄獲得。返回值為目錄項指針,即為本次讀取的一條目錄項,其類型struct aw_dirent的定義如下:

其中,d_ino為項目序列號,其值為一個整數(shù),每個子項都有一個對應(yīng)的序列號,在一個目錄中,若共計有10個子項,則各子項的序列號依次為0 ~ 9。d_name為該子項的名字。

打開目錄后首次調(diào)用讀取目錄接口,獲得的子項序列號為0,要獲取一個目錄下所有的子項,可以多次調(diào)用讀取目錄接口,每次調(diào)用結(jié)束后,都會將序列號加1,下次讀取時將讀取到下一個子項。特別地,若讀取目錄的返回值為NULL,表示讀取結(jié)束。讀取一個目錄下所有子項的范例程序詳見程序清單11.22。

程序清單11.22 讀取目錄范例程序(1)

aw_readdir()接口通過返回值返回一個指向讀取目錄子項的指針,該指針指向的實際內(nèi)存是由系統(tǒng)內(nèi)部分配的靜態(tài)內(nèi)存,在多任務(wù)環(huán)境中,這份內(nèi)存是共享的,因此,該接口不是線程安全的,例如,在一個任務(wù)中讀取了一次目錄,則靜態(tài)內(nèi)存中存放了本次讀取的結(jié)果,其地址返回給用戶,用戶通過指針訪問讀取目錄的結(jié)果。若在這個過程中,另外一個任務(wù)也讀取了一次目錄,則內(nèi)存中的數(shù)據(jù)將發(fā)生改變,這將覆蓋前一個任務(wù)讀取目錄的結(jié)果。由此可見,當(dāng)多個任務(wù)同時訪問一個目錄時,必須使用互斥機制。

為此,AWorks提供了另外一個線程安全的接口:aw_readdir_r(),它們在功能上是完全一樣的,唯一的不同是,使用該接口讀取目錄時,讀取結(jié)果存儲在用戶提供的一段內(nèi)存中,其原型為:

其中, dirp為指向目錄對象的指針,可通過打開目錄獲得;entry為存儲讀取結(jié)果的緩存,result為一個二維指針,即一個指針的地址,程序執(zhí)行結(jié)束后,指針的值即被設(shè)置為指向本次讀取目錄的結(jié)果。

若返回值為AW_OK,則讀取成功,否則,讀取失敗。值得注意的是,當(dāng)目錄讀取未達結(jié)尾,成功讀取到一個子項時,由于讀取的結(jié)果存儲在entry指向的內(nèi)存中,因此,result的值被設(shè)置為entry(即:*result=entry),當(dāng)目錄讀取達到結(jié)尾時,result的值將被設(shè)置為NULL(即:*result=NULL)。范例程序詳見程序清單11.23。

程序清單11.23 讀取目錄范例程序(2)

4. 關(guān)閉目錄

若讀取目錄完畢,或不再需要讀取目錄時,則可以關(guān)閉目錄,其函數(shù)原型為:

其中,dirp為指向目錄對象的指針,可通過打開目錄獲得。返回值為AW_OK時表示關(guān)閉成功,否則,表示關(guān)閉失敗。

綜合打開目錄、讀取目錄、關(guān)閉目錄三個接口,遍歷一個目錄下所有子項的范例程序詳見程序清單11.24。

程序清單11.24 遍歷目錄范例程序

5. 刪除目錄

當(dāng)一個目錄不再使用,可以刪除該目錄,刪除指定目錄的函數(shù)原型為:

其中,path為目錄的路徑。返回值為AW_OK時表示刪除成功,否則,表示刪除失敗。該函數(shù)僅可用于刪除一個空目錄,若目錄非空,則刪除失敗。范例程序詳見程序清單11.25。

程序清單11.25 刪除目錄范例程序

11.5 微型數(shù)據(jù)庫

AWorks提供了一個基于文件系統(tǒng)實現(xiàn)的微型數(shù)據(jù)庫,用以管理信息記錄,每條記錄由“關(guān)鍵字”和“值”兩部分構(gòu)成,關(guān)鍵字可用于記錄的查找、刪除等。微型數(shù)據(jù)庫是基于哈希表原理實現(xiàn)的,具有簡潔、高效的特點。為了便于理解,本節(jié)首先對哈希表進行簡要介紹,然后再介紹微型數(shù)據(jù)庫的各個接口。

11.5.1 哈希表

在數(shù)據(jù)存儲應(yīng)用中,存儲的記錄往往都具有唯一的關(guān)鍵字,以便于管理。例如,為了管理學(xué)生信息(包含姓名、性別、身高、體重等),會為每個學(xué)生分配一個學(xué)號,通過學(xué)號就可以唯一的定位一個學(xué)生,這里的學(xué)號即為關(guān)鍵字。常見的身份證號也具有類似的作用。

假設(shè)需要設(shè)計一個信息管理系統(tǒng),用于管理學(xué)生信息,可以將每條學(xué)生信息看作一條記錄。

一條記錄包含學(xué)號、姓名、性別、身高、體重等信息,可定義與學(xué)生記錄對應(yīng)的結(jié)構(gòu)體類型,詳見程序清單11.26。

程序清單11.26 學(xué)生信息類型定義

作為一個信息管理系統(tǒng),首先要能夠?qū)崿F(xiàn)學(xué)生記錄的存儲,基于文件系統(tǒng)接口可以很容易實現(xiàn):直接將記錄寫入到文件中即可。增加一條學(xué)生記錄的范例程序詳見程序清單11.27。

程序清單11.27 增加一條學(xué)生記錄

程序中,假定了存儲學(xué)生記錄的文件名為:"/test/students.txt",這就要求系統(tǒng)中,將/test目錄掛載到特定的存儲器,比如SD卡或U盤等。增加一條記錄的實現(xiàn)非常簡單,僅僅是將p_info指向的學(xué)生記錄順序的存入了文件尾部,因此,使用這種方法增加學(xué)生記錄的效率還是比較高的。

如果使用student_add()函數(shù)增加了若干學(xué)生記錄,將學(xué)生記錄存儲在了文件中,接下來,最常見的操作就是根據(jù)學(xué)號查詢學(xué)生信息?;趯W(xué)生信息的存儲方式,可以實現(xiàn)一個學(xué)生信息查詢函數(shù),范例程序詳見程序清單11.28。

程序清單11.28 根據(jù)學(xué)號查詢學(xué)生記錄

由此可見,程序僅僅從文件頭部順序讀取文件學(xué)生記錄,逐一與待查找的學(xué)號進行比對,直到查找到與學(xué)號完全一致的學(xué)生記錄。

上述簡單的范例程序?qū)崿F(xiàn)了記錄的添加和查找。由于查找學(xué)生記錄是采用順序查找的方式,隨著學(xué)生記錄的增加,查找效率將逐步降低。例如,一所大學(xué)往往有幾萬學(xué)生,則使用該系統(tǒng)管理學(xué)生記錄時,一次簡單的查找則可能要順序?qū)Ρ壬先f次學(xué)號,顯然,效率不高。

如何以更高的效率實現(xiàn)查找呢?在查找算法中,非常經(jīng)典高效的算法是“二分法查找”,按10000條記錄算,最多也只需要比較14次(log210000)。但是,使用“二分法查找”的前提是信息必須有序排列,即要求學(xué)生記錄必須按照學(xué)號從大到小或從小到大的順序進行存儲,這就導(dǎo)致在添加學(xué)生信息時,必須將學(xué)生記錄按照學(xué)號順序,插入到指定位置,而不能像程序清單11.27那樣,簡單的將信息添加至文件尾部。對于文件操作來講,插入操作是非常繁瑣的,若已經(jīng)有大量學(xué)生記錄按學(xué)號順序存儲在文件中,在此基礎(chǔ)上再插入一條記錄到這些記錄的中間某個位置,則需要將其后的所有記錄后移,以預(yù)留出一條記錄的存儲空間,這意味著需要將后續(xù)所有記錄讀出,再重新寫入到其后的地址中。由此可見,雖然使用這種方法可以提高查找效率,卻犧牲了添加信息的效率。

“順序查找”管理方式犧牲了查找記錄的效率,“二分法查找”犧牲了寫入記錄的效率。能否將二者折中一下呢?“二分法查找”的本質(zhì)是每次縮小一半的查找范圍,基于縮小查找范圍的思想,可以嘗試縮小每次“順序查找”的范圍。同樣以10000條記錄為例,為了縮小每次“順序查找”的范圍,將記錄分為兩個部分,例如,制定以下規(guī)則:學(xué)號小于某一值時,作為第一部分存儲在某一文件中;反之,作為第二部分存儲在另外一個文件中。

如此一來,在寫入記錄時,只需要多一條判斷語句,對性能并沒太大影響。而在查找時,只要根據(jù)學(xué)號判斷出記錄應(yīng)存儲在哪一個文件中,然后按照順序查找的方式進行查找即可。此時,若用于分界的學(xué)號選擇恰當(dāng),使兩個部分的學(xué)生記錄數(shù)量基本相同,則順序查找需要比較的次數(shù)就從最大的10000次降低到了約5000次。由此可見,通過一個簡單的方法,將信息分別存儲在兩個文件中,就可以明顯地提高查找效率。

為了繼續(xù)提高查找的效率,還可以將記錄分為更多的部分,比如,分成250個部分,序號為0~ 249,若劃分規(guī)則恰當(dāng),使各個部分的學(xué)生記錄數(shù)量基本相同,則每個部分的記錄數(shù)目就約為40條,此時,順序查找需要比較的次數(shù)就僅需約40次即可!示意圖詳見圖11.5。

圖11.5 將記錄分為多個部分

圖11.5可以看作大小為250的“哈希表”,“哈希表”的每個表項對應(yīng)了一部分記錄。哈希表的核心思想是將一個很大范圍的關(guān)鍵字空間(例如,學(xué)號為6字節(jié),6字節(jié)數(shù)據(jù)共計48位,其表示的數(shù)值空間大小為:248,約280萬億,是一個相當(dāng)大的范圍),映射到一個較小的空間(范圍序號:0 ~ 249,大小為250)。由于是大范圍映射到小范圍,因此可能有一部分關(guān)鍵字(學(xué)號)映射到同一個表項中,也就是每個表項可能包含多條記錄。

哈希表的關(guān)鍵是確定映射關(guān)系,即如何將關(guān)鍵字(學(xué)號)映射到表項的序號,也就是將所有記錄劃分為多個部分的具體規(guī)則。當(dāng)寫入或查找一條記錄時,可以通過映射關(guān)系確定該記錄屬于哪一部分。這個映射關(guān)系對應(yīng)的函數(shù)即為“哈希函數(shù)”,其作用就是將學(xué)號轉(zhuǎn)換為哈希表的表項序號。例如,假定學(xué)號是均勻分布的,則可以將6字節(jié)學(xué)號直接求和再對250取余,進而得到一個0 ~ 249的數(shù),范例程序詳見程序清單11.29。

程序清單11.29 映射關(guān)系——通過學(xué)號得到分組索引

db_id_to_idx()函數(shù)就是“哈希函數(shù)”,哈希函數(shù)的結(jié)果(分組索引)稱之為“哈希值”。

“哈希函數(shù)”是整個哈希表的關(guān)鍵,哈希函數(shù)應(yīng)盡可能確保記錄均勻的分布到各個表項中,不能差異太大,極端地,若哈希函數(shù)選擇有誤,將所有記錄分布到了一個表項中,那這樣的哈希表將沒有任何意義,因為每次查找記錄又回到了最初的狀態(tài):遍歷所有記錄。

實際應(yīng)用中,記錄往往是動態(tài)管理的,可以隨時動態(tài)添加、刪除。因此,每一部分(哈希表的表項)包含的記錄數(shù)也會動態(tài)增加或減少。為了便于動態(tài)管理每一部分的記錄,各部分可以使用鏈表管理該部分中可能存在的多條記錄,示意圖詳見圖11.6,圖中所示的鏈?zhǔn)焦1斫Y(jié)構(gòu)就是AWorks中微型數(shù)據(jù)庫原理。

圖11.6 鏈?zhǔn)焦1斫Y(jié)構(gòu)

11.5.2 微型數(shù)據(jù)庫接口

前面簡要介紹了哈希表的原理,AWorks提供了一個基于哈希表思想實現(xiàn)的微型數(shù)據(jù)庫,提供了增加、刪除、查找等接口,相關(guān)接口的原型詳見表11.6。

表11.6 微型數(shù)據(jù)庫接口(aw_db_micro_hash_kv.h)

微型數(shù)據(jù)庫相關(guān)的文件和接口以“aw_db_micro_hash_kv”作為命名空間,其中,“aw_”表示AWorks,“db”表示數(shù)據(jù)庫(data base),“micro”表示微型,hash_kv表示基于的是hash關(guān)鍵字和值的思想。

1. 定義數(shù)據(jù)庫實例

所有接口的第一個參數(shù)均為aw_db_micro_hash_kv_t類型的指針,用于指向待操作的數(shù)據(jù)庫實例。該類型的具體定義(如具體包含哪些成員)用戶無需關(guān)心,僅需在使用微型數(shù)據(jù)庫前,使用該類型定義一個數(shù)據(jù)庫實例即可,例如:

其中,students_db的地址即可作為各個接口p_db參數(shù)的實參傳遞。

2. 初始化

在使用數(shù)據(jù)庫前,需要完成數(shù)據(jù)庫的初始化,以指定哈希表大小、關(guān)鍵字長度、值長度以及存儲整個數(shù)據(jù)庫的文件名等信息。其函數(shù)原型為:

其中,p_db指向待初始化的數(shù)據(jù)庫實例。size表示哈希表的大小,即表項的數(shù)目,如需設(shè)計圖11.6所示的哈希表,由于其哈希表的大小為250,則該值應(yīng)設(shè)置為250。key_size為關(guān)鍵字長度,例如,以學(xué)號為關(guān)鍵字,由于學(xué)號的長度為6字節(jié),則該值應(yīng)設(shè)置為6。

value_size表示記錄中值的長度,以學(xué)生記錄為例,一條學(xué)生記錄包含學(xué)號、姓名、性別、身高、體重等信息。最初的學(xué)生記錄對應(yīng)的結(jié)構(gòu)體類型詳見程序清單11.26。在使用微型數(shù)據(jù)庫時,關(guān)鍵字和值是不同的兩個部分,均會被存儲到文件中。因此,可以將關(guān)鍵字學(xué)號分離出來,剩余的信息作為一條學(xué)生記錄的“值”。詳見程序清單11.30。

程序清單11.30 學(xué)生記錄信息類型(不包括關(guān)鍵字——學(xué)號)

基于此,value_size的值則應(yīng)設(shè)置為:sizeof(student_t)。

pfn_hash用于指定一個哈希函數(shù),其作用是將關(guān)鍵字轉(zhuǎn)換為一個哈希值,哈希值即為哈希表的索引。其類型aw_db_micro_hash_kv_func_t定義如下:

該類型為一個函數(shù)指針類型,其指向函數(shù)的形參為關(guān)鍵字,返回值為哈希值(類型為無符號整數(shù)),哈希值將作為哈希表的索引。通過對哈希表的介紹可知,哈希函數(shù)的選擇直接決定了記錄的分布,必須盡可能地確保所有記錄均勻地分布在各個表項中。不同的關(guān)鍵字數(shù)據(jù)具有不同的分布特性,因此,哈希函數(shù)需要由用戶根據(jù)實際情況提供,簡單的實現(xiàn)可以將關(guān)鍵字按字節(jié)求和后再對哈希表大小取余,詳見程序清單11.31。

程序清單11.31 簡易的哈希函數(shù)實現(xiàn)

其中,函數(shù)名hash_func_id_to_idx即可作為pfn_hash的實參傳遞。

file_name用于指定存儲該數(shù)據(jù)庫信息及所有記錄的文件名,若一個系統(tǒng)中存在多個數(shù)據(jù)庫(定義了多個數(shù)據(jù)庫實例),則在初始化各個數(shù)據(jù)庫時,為各個數(shù)據(jù)庫指定的文件名應(yīng)該不同,以使各個數(shù)據(jù)庫使用不同的文件存儲數(shù)據(jù)。在程序清單11.27和程序清單11.28所示的簡易范例程序中,每次寫入記錄或查找記錄前,都會執(zhí)行一次打開文件操作,并在寫入或查找結(jié)束后關(guān)閉文件。在實際應(yīng)用中,可能會頻繁的執(zhí)行寫入、查找等操作,這樣就會導(dǎo)致頻繁的打開、關(guān)閉文件,降低了程序運行的效率,為此,AWorks提供的數(shù)據(jù)庫僅僅在初始化時打開文件,后續(xù)其它操作均無需再執(zhí)行打開文件操作。若初始化時指定的文件名對應(yīng)文件已經(jīng)存在,則僅僅打開該文件,然后再該文件的基礎(chǔ)上進行記錄的管理(添加、刪除等);若文件名對應(yīng)文件不存在,則創(chuàng)建該文件,以建立一個全新的數(shù)據(jù)庫(數(shù)據(jù)庫中沒有任何有效記錄)。初始化數(shù)據(jù)庫的范例程序詳見程序清單11.32。

程序清單11.32 初始化數(shù)據(jù)庫范例程序

3. 增加一條記錄

該函數(shù)用于向數(shù)據(jù)庫中增加一條記錄,其函數(shù)原型為:

其中,p_db指向已經(jīng)初始化的數(shù)據(jù)庫實例。p_key指向本次增加記錄的關(guān)鍵字,其長度必須與初始化指定的key_size一致。p_value指向本次增加記錄的具體“值”。例如,待增加一條學(xué)生記錄,其各項信息詳見表11.7。

表11.7 待增加的學(xué)生信息舉例

則向數(shù)據(jù)庫中增加該學(xué)生記錄的范例程序詳見程序清單11.33。

程序清單11.33 增加記錄范例程序

4. 根據(jù)關(guān)鍵字查找記錄

向數(shù)據(jù)庫中添加記錄后,可以根據(jù)關(guān)鍵字查找記錄的詳細信息,查找記錄的函數(shù)原型為:

其中,p_db指向數(shù)據(jù)庫實例。p_key為輸入?yún)?shù),指向本次查找記錄的關(guān)鍵字,關(guān)鍵字長度必須與初始化時指定的key_size一致。p_value為輸出參數(shù),返回本次查找到的記錄。

例如,若使用程序清單11.33所示的范例程序增加了一條學(xué)生記錄,作為測試,可以使用查找記錄接口查找學(xué)號為201644700239的記錄,以查看其對應(yīng)的“值”信息是否與寫入的信息一致。范例程序詳見程序清單11.34。

程序清單11.34 根據(jù)關(guān)鍵字查找記錄的范例程序

程序中,為了避免使用aw_kprintf()打印浮點數(shù),將身高和體重分為整數(shù)部分和小數(shù)部分打印,最終等效于保留2位小數(shù)的浮點數(shù)打印效果。

5. 刪除一條記錄

通過該接口可以刪除數(shù)據(jù)庫中的一條記錄,其函數(shù)原型為:

其中,p_db指向數(shù)據(jù)庫實例。p_key指向本次需要刪除記錄的關(guān)鍵字,關(guān)鍵字長度必須與初始化時指定的key_size一致。

例如,若使用程序清單11.33所示的范例程序增加了一條學(xué)生記錄,作為測試,可以將學(xué)號為201644700239的記錄刪除。范例程序詳見程序清單11.35。

程序清單11.35 根據(jù)關(guān)鍵字刪除記錄的范例程序

使用程序清單11.35所示程序刪除記錄后,若再查找學(xué)號為201644700239的記錄,將會查找失敗。

6. 解初始化

與數(shù)據(jù)庫初始化函數(shù)對應(yīng)。當(dāng)暫時不使用一個數(shù)據(jù)庫時,可以執(zhí)行該函數(shù),以釋放相關(guān)資源。注意,解初始化后,數(shù)據(jù)庫中已經(jīng)存儲的內(nèi)容保持不變。解初始化函數(shù)的原型為:

其中,p_db指向數(shù)據(jù)庫實例。例如,在一次應(yīng)用中,添加了100個學(xué)生記錄,添加結(jié)束后,暫時不再使用該數(shù)據(jù)庫,則可以解初始化該數(shù)據(jù)庫,范例程序詳見程序清單11.36。

程序清單11.36 解初始化范例程序

在初始化函數(shù)的介紹中提到,為了避免頻繁的打開文件和關(guān)閉文件,在初始化時打開了文件,使得在添加記錄、刪除記錄、查找記錄時,無需再打開文件。與初始化函數(shù)對應(yīng),在解初始化函數(shù)中,關(guān)閉了文件。關(guān)閉文件后,文件中的內(nèi)容保持不變。如需再次使用該數(shù)據(jù)庫,則應(yīng)該重新執(zhí)行一次初始化操作。

上面介紹了各個接口的使用方法,基于這些接口,可以實現(xiàn)一個學(xué)生記錄管理的綜合范例程序,詳見程序清單11.37。

程序清單11.37 微型數(shù)據(jù)庫綜合范例程序

測試程序主要由test_db_micro_hash_kv()完成,在aw_min()主程序中,僅僅是簡單調(diào)用了該函數(shù)。在test_db_micro_hash_kv()函數(shù)中,首先初始化了一個數(shù)據(jù)庫,然后向其中添加了100個學(xué)生記錄(為了快捷產(chǎn)生這些記錄,以隨機數(shù)的方式自動生成,隨機數(shù)無實際意義,僅供測試使用。同時,為了保證添加的學(xué)生學(xué)號唯一,在添加記錄前,通過搜索接口查看是否存在該學(xué)號的學(xué)生,若存在,則不再重復(fù)添加)。接著測試了查找接口,查找最后添加的學(xué)生記錄。然后測試刪除接口,刪除了最后添加的學(xué)生記錄,刪除成功后,再次查找將會失敗。最后,所有操作執(zhí)行完畢,解初始化數(shù)據(jù)庫。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11212

    瀏覽量

    208721
  • 數(shù)據(jù)結(jié)構(gòu)

    關(guān)注

    3

    文章

    569

    瀏覽量

    40072
  • AWorks
    +關(guān)注

    關(guān)注

    1

    文章

    16

    瀏覽量

    5680

原文標(biāo)題:AWorks軟件篇 — 文件系統(tǒng)

文章出處:【微信號:Zlgmcu7890,微信公眾號:周立功單片機】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    嵌入式常用數(shù)據(jù)結(jié)構(gòu)有哪些

    在嵌入式編程中,數(shù)據(jù)結(jié)構(gòu)的選擇和使用對于程序的性能、內(nèi)存管理以及開發(fā)效率都具有重要影響。嵌入式系統(tǒng)由于資源受限(如處理器速度、內(nèi)存大小等),因此對數(shù)據(jù)結(jié)構(gòu)的選擇和使用尤為關(guān)鍵。以下是嵌入式編程中常用的幾種
    的頭像 發(fā)表于 09-02 15:25 ?362次閱讀

    網(wǎng)絡(luò)爬蟲,Python和數(shù)據(jù)分析

    電子發(fā)燒友網(wǎng)站提供《網(wǎng)絡(luò)爬蟲,Python和數(shù)據(jù)分析.pdf》資料免費下載
    發(fā)表于 07-13 09:27 ?1次下載

    探索編程世界的七大數(shù)據(jù)結(jié)構(gòu)

    結(jié)構(gòu)就像是一顆倒掛的小樹,有根、有枝、有葉。它是一種非線性的數(shù)據(jù)結(jié)構(gòu),以層級的方式存儲數(shù)據(jù),頂部是根節(jié)點,底部是葉節(jié)點。
    的頭像 發(fā)表于 04-16 12:04 ?345次閱讀

    TASKING編譯器是否可以將數(shù)據(jù)結(jié)構(gòu)設(shè)置為 \"打包\"?

    TASKING 編譯器是否可以將數(shù)據(jù)結(jié)構(gòu)設(shè)置為 \"打包\"? GCC 很早以前就提供了這種可能性,可以將__attribute__((packed))與對齊指令結(jié)合使用。 對于
    發(fā)表于 03-05 06:00

    矢量與柵格數(shù)據(jù)結(jié)構(gòu)各有什么特征

    矢量數(shù)據(jù)結(jié)構(gòu)和柵格數(shù)據(jù)結(jié)構(gòu)是地理信息系統(tǒng)(GIS)中最常用的兩種數(shù)據(jù)結(jié)構(gòu)。它們在存儲和表示地理要素上有著不同的方法和特征。在接下來的文章中,我們將詳細介紹這兩種
    的頭像 發(fā)表于 02-25 15:06 ?2247次閱讀

    區(qū)塊鏈?zhǔn)鞘裁礃拥?b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)組織

    區(qū)塊鏈?zhǔn)且环N特殊的數(shù)據(jù)結(jié)構(gòu),它以分布式、去中心化的方式組織和存儲數(shù)據(jù)。區(qū)塊鏈的核心原理是將數(shù)據(jù)分布在網(wǎng)絡(luò)的各個節(jié)點上,通過密碼學(xué)算法保證數(shù)據(jù)的安全和可靠性。在區(qū)塊鏈上,
    的頭像 發(fā)表于 01-11 10:57 ?1858次閱讀

    C語言數(shù)據(jù)結(jié)構(gòu)之跳表詳解

    大家好,今天分享一篇C語言數(shù)據(jù)結(jié)構(gòu)相關(guān)的文章--跳表。
    的頭像 發(fā)表于 12-29 09:32 ?780次閱讀
    C語言<b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>之跳表詳解

    redis數(shù)據(jù)結(jié)構(gòu)的底層實現(xiàn)

    Redis是一種內(nèi)存鍵值數(shù)據(jù)庫,常用于緩存、消息隊列、實時數(shù)據(jù)分析等場景。它的高性能得益于其精心設(shè)計的數(shù)據(jù)結(jié)構(gòu)和底層實現(xiàn)。本文將詳細介紹Redis常用的
    的頭像 發(fā)表于 12-05 10:14 ?575次閱讀

    不同數(shù)據(jù)結(jié)構(gòu)的定義代碼

    數(shù)據(jù)結(jié)構(gòu)是相互之間存在一種或多種特定關(guān)系的數(shù)據(jù)元素的集合。
    的頭像 發(fā)表于 11-29 14:13 ?609次閱讀

    python列表和數(shù)組的區(qū)別

    Python是一種功能強大的編程語言,為開發(fā)者提供了許多數(shù)據(jù)結(jié)構(gòu)來處理和操作數(shù)據(jù)。其中,列表和數(shù)組是常用的數(shù)據(jù)結(jié)構(gòu),用于存儲和組織一系列元素
    的頭像 發(fā)表于 11-21 15:13 ?2209次閱讀

    redis的數(shù)據(jù)結(jié)構(gòu)一般分為哪幾種?

    緩存、計數(shù)器、分布式鎖等。字符串類型支持很多操作,如設(shè)置、獲取、刪除、追加等。 哈希表(Hashes): 哈希表是 Redis 提供的一個鍵值對的數(shù)據(jù)結(jié)構(gòu),它類似于一個字典,可以存儲多個字段和值的映射關(guān)系。哈希表適用于存儲對象,每個字段代表對象的一個屬性,而值則存
    的頭像 發(fā)表于 11-16 11:19 ?404次閱讀

    redis的五種數(shù)據(jù)類型底層數(shù)據(jù)結(jié)構(gòu)

    Redis是一種內(nèi)存數(shù)據(jù)存儲系統(tǒng),支持多種數(shù)據(jù)結(jié)構(gòu)。這些數(shù)據(jù)結(jié)構(gòu)不僅可以滿足常見的存儲需求,還能夠通過其底層數(shù)據(jù)結(jié)構(gòu)提供高效的操作和查詢。以下是Redis中常用的五種
    的頭像 發(fā)表于 11-16 11:18 ?672次閱讀

    無鎖CAS如何實現(xiàn)各種無鎖的數(shù)據(jù)結(jié)構(gòu)

    ,可用于在多線程編程中實現(xiàn)不被打斷的數(shù)據(jù)交換操作,從而避免多線程同時改寫某?數(shù)據(jù)時由于執(zhí)行順序不確定性以及中斷的不可預(yù)知性產(chǎn)?的數(shù)據(jù)不一致問題 有了CAS,我們就可以用它來實現(xiàn)各種無鎖
    的頭像 發(fā)表于 11-13 15:38 ?728次閱讀
    無鎖CAS如何實現(xiàn)各種無鎖的<b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>

    定時器的實現(xiàn)數(shù)據(jù)結(jié)構(gòu)選擇

    在后端的開發(fā)中,定時器有很廣泛的應(yīng)用。 比如: 心跳檢測 倒計時 游戲開發(fā)的技能冷卻 redis的鍵值的有效期等等,都會使用到定時器。 定時器的實現(xiàn)數(shù)據(jù)結(jié)構(gòu)選擇 紅黑樹 對于增刪查,時間復(fù)雜度為O
    的頭像 發(fā)表于 11-13 14:22 ?490次閱讀
    定時器的實現(xiàn)<b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>選擇

    ringbuffer數(shù)據(jù)結(jié)構(gòu)介紹

    最近在研究srsLTE的代碼,其中就發(fā)現(xiàn)一個有意思的數(shù)據(jù)結(jié)構(gòu)------ringbuffer。 雖然,這是一個很基本的數(shù)據(jù)結(jié)構(gòu),但時,它在LTE這種通信協(xié)議棧系統(tǒng)中卻大行其道,也是很容易被協(xié)議
    的頭像 發(fā)表于 11-13 10:44 ?1506次閱讀
    ringbuffer<b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>介紹