第六章 APO文件目錄系統

 

            第六章  APO文件目錄系統與磁盤文件管理類的實現

 

追求:簡單啊!巨大啊!超光速啊!爽啊!

以下是初步方案:

      文件系統是對裝文件的磁盤空間的文件佈局、目錄樹組織的一種描述。有很多種文件系統,它們的空間接口都不一樣。即使同一種文件系統,由於在不同的設備上,其空間接口也不一樣。空間接口就是指文件系統空間與內存空間連結的方法集合。所以,文件系統類型.設備類型.空間接口.方法i.文件(參數),才能操作到具體的文件。所以,文件系統必須有一個超級塊,也就是MFT(卷的根目錄)。或簡單說,操作一個文件,必須將它對應的樹根也打開。MFT超級塊有文件系統類型號、設備號、連到每一個文件的路徑等等。不同的文件系統安裝到APO系統中的一個子目錄時,就加裝了MFT。這樣,其它文件系統也能在APO中使用;這跟unix/linux的VFS類似。

        爲了追求速度的極致,APO不得不採取極端設計。APO的數據總線速度是512GBPS;而SATA3接口的固態硬盤的速度只是6GBPS,相對說就太慢了。如果固態硬盤接口和內部總線也採用256位數據總線,即使有存儲芯片的限制;做到64GBPS應該不難,隨機讀取固態硬盤的一行數據速度可以做到256ME/S= 8GB/S。如果所有的文件系統中的文件名哈希值都統一對應映射到APO中的一個相應i節點;也就是說哈希節點就是i節點。APO卷有4G個i節點號,所以,最多有4G個文件或目錄。這樣,如果我們將所有的目錄文件和包含MFT的32個元文件全部放在固態硬盤,需要多大空間?一個i節點是2W,需要4G*2W = 32GB的i節點空間。一個目錄項是4E;那需要  4G*4E = 16GE = 512 GB 目錄項空間。同或不同文件名的相同哈希值的項是極少的,相同哈希值的列表文件佔空間不到64MB;其它元文件佔的空間更少。那些小文件(文件內容佔行數1到128行)全部集中在一個16GB的空間中,一個 XWJGL文件管理有將近16M個小文件。所以,固態硬盤有1TB的空間就夠用了。除了以上的文件,其它文件的內容都是放在普通硬盤的空間中。固態硬盤有連續頁、和連續數據塊的2種管理模式;APO最大支持4G個數據塊的空間(8PB)、32位的塊號;連續頁管理則需要空間4GY = 8MC = 16TB、32位的頁號。

     當用戶從控制檯,發“列出一個目錄內的所有文件”的消息;系統從該目錄文件的內容讀出一項項目錄項時,當然是希望目錄項能包含有全部相關的文件屬性。而不是,一個一個inode去I/O;磁盤尋道;那會很慢。儘管,只是從2個文件(目錄文件、MFT文件)中,讀取信息;相對於從多個文件讀取要快。但,如果只從一個目錄文件讀取信息,就不需來回磁盤尋道了。這個差別很大的,比如、目錄內的文件數爲10M個(1千萬個),linux的i節點爲128B;磁盤平均尋道時間爲10ms,即使不算訪問時間;就算1千萬個來回尋道時間也要耗時:100K秒 ~= 28小時。如果,只是從一個大文件讀取,那就快多了;尋道時間10ms,訪問時間:10M * 128B / 160MB/s = 8s。APO是使用固態硬盤保存目錄項,並且文件的屬性都在目錄項裏,一個目錄項4E;應用程序可以調用系統的戳穿方法(不用阻塞、不經過日誌文件服務進程、直達硬件驅動程序),直接讀入文件的所有目錄項到流容器,速度64M個目錄項/S;那麼只需0.16s就可全部列出一個目錄內的所有10M文件。那麼,速度就會比linux快63萬倍。

      當要刪除一個目錄時,如果目錄內有10M個文件。那麼,linux就需要從2個文件(目錄文件、MFT文件)中,來回磁盤尋道了、一個一個inode去I/O讀取信息、釋放磁盤空間、釋放i節點。我不清楚linux的釋放時間是多少?但,漫長的過程,加上不會全是一個進程獨佔;加上進程切換等,估計耗時會翻3倍多、達到100小時以上。APO的目錄項中就有文件內容的數據塊指針,所以、釋放磁盤空間就可在目錄文件進行。釋放文件中的一段連續數據空間,大約耗時20ns;就算平均一個文件有1.5個連續段,那也就是:30ns*10M = 0.3s,再加上讀取目錄項的0.16s;不到0.5s就可完成,速度就會比linux快70萬倍。APO釋放i節點?APO甚至不用刪除目錄項,只是在該目錄文件的父目錄文件的對應目錄項清0;相應的i節點置空。

      當要按照10級的路徑尋找和打開一個文件時,如果每級目錄節點下都有1M個目錄項;那麼,linux從根目錄開始一級一級往下尋找;每一級都要一個一個目錄項比較,就算使用了文件名字長度及後來增加的文件類型,用輕型比較加快了速度;而不是每次都要比較“長的文件名字”。每次比較,就算0.1us;那也要10*1M*0.1us = 1s的時間,嗯、勉強滿足使用要求。當每級目錄節點下都有10M個目錄項時,那就要暈倒了。我不知道linux爲何不用文件名字哈希值來做比較?APO是直接得到i節點的,APO的i節點就是文件名字的哈希值。管你是有n級的路徑,APO在ns級就可建立內存的v節點(包含文件的i節點和對應的目錄項)。所以,比起linux要快個幾十萬倍,是很自然的。

     當沒有路徑時,要遍歷整個目錄樹。咋辦?有更好的方法嗎?按樹狀目錄查找已經是比不按順序的查找要好的多了。還有好辦法?嗯,B+樹。B+樹能夠使資料保持有序,並擁有均勻的對數處理時間的插入和刪除動作。B樹的元素通常會自底向上插入,有別於多數自頂向下插入的二叉樹。6級的B+樹,可能7次i/o就能定位目標文件。當沒有路徑時,用文件名字的哈希值搜B+樹,幾次i/o就可定位到目標文件i節點號;這是不錯。但B+樹的索引節點指針需額外佔用磁盤空間,大約一個文件需6W,如果有2G文件時就可觀了,要多用12GW = 48GB的硬盤空間。B+樹只適合小量文件的場合。B+樹是自我分裂的,小量文件時,額外佔用磁盤空間不大;不過每次多花點時間吧。那也是要花時間和多佔空間啊,真是奇能淫技啊!改進了一些,過於複雜還是屬於小聰明;相比APO的方法還是差遠了。說真的、不是我想吹牛b;目前也不知道APO這種方法是否有缺陷。

      當我們查找名字第一位字符是x的所有文件時,哈希值沒用了,B+樹沒用了,unix/linux技窮了,只能遍歷整個目錄樹;磁頭頻繁移動,慢如烏龜了!APO也是隻能是遍所有的目錄文件。但APO的目錄文件都是放在固態硬盤,無需磁盤尋道;而APO的多功能硬件模塊可以在10ns比較4K個目錄項;就算總共有1G個目錄項,讀入、裝配4K個目錄項需要耗時4K/64M/s = 64us,那麼,總耗時16s。你可以試一下,WINDOWS、LINUX如果遍歷1G個目錄項需耗時多少?就算它們能一分鐘掃描1萬個目錄項並作比較,也要耗時1G/0.01M/60s = 1700小時。

      那個EXT4文件系統,不過是預留一些就近i節點給目錄文件,讓同一個目錄下的少部分文件i節點能靠在一起;稍微減少一些I/O操作。那些“叫獸”就鬼叫,搞得我都臉紅。

      APO文件系統就是一個卷描述文檔,提供了磁盤壓縮、數據加密、磁盤配額(用戶可以通過磁盤配額監視和限制磁盤空間的使用)、動態掛載其它文件系統、動態磁盤管理等功能,總共可有256T個64KE的塊,所以APO文件系統最大支持到256T*64KE=16EE=512EB的磁盤空間。有最大64K - 256個卷,卷中的每一個數據塊64KE,都有一個特定的序號,這個序號就叫做邏輯塊號LJID,邏輯塊號0指向卷中的第一個塊,每個卷總共有4G塊。最大支持256TE的磁盤空間。卷、或文件系統根目錄可能連結在不同的設備上,所以打開一個文件,它對應的文件系統根目錄也要打開。存放在塊中的簡單的字符排列數據稱爲流;每一個流都由起始塊號和尺寸來描述。節點描述符也稱爲元數據,按節點ID從0開始的順序的元數據流構成元數據文件MFT。最大文件2G*64KE = 128TE = 4PB。16位既是2字節2B或一個字符Z,2個字符爲1字W;一行E爲16個字符16Z或8個字8W,一個數據塊是512KW或64KE或2MB或512頁;一頁Y是128E或4KB或2KZ/1KW,一個單元C是64K個數據塊;一個卷J是64K個單元。

      流stream就是指很大的數據流、甚至無限的數據流;通常的文件數據、我們就看作是一個數據流。因爲內存空間有限,不可能完全裝入一個大的數據流;所以、通常是使用窗口,操作(讀、寫)數據流中的一部分;要操作另一部分時、則通過移動窗口、或移動流。而這種窗口、就稱爲數據流的位容器、簡稱流容器。本地內存的流容器有4種:連續行、連續扇區、連續頁、連續數據塊。磁盤空間的分配、釋放管理有3種:連續頁、連續數據塊、連續單元。本地內存空間的分配、釋放管理有3種:連續行、連續數據塊、連續頁。打開一個文件來操作,就是發送一條消息;必須給出流容器對象、文件位置、長度、文件和權限標誌、消息類型3.消息值(open、close等)。當收到允許回覆消息後,你就可以對流容器進行讀、寫操作了。不同的文件系統,只是對到日誌文件的消息的加工方式不同,多了一道翻譯工作,當然也就慢了些;磁盤設備驅動只是關聯到日誌文件。我們打開一個文件,無非是希望系統能把指定文件位置、長度的文件數據讀入到流容器、或從流容器回寫吧。Open一個文件、成功後,系統返回一個文件號;下次發消息,就要使用文件號了。這裏只是簡介,具體要往後看。

     一個i節點和一個相應目錄項在內存表現就是v節點。對於硬連接、文件名不同、其i節點就不同,也就是有不同的v節點號;但在v節點內的指向文件內容的指針是相同的。對於軟連接、同樣是有不同的v節點號;但在v節點內的指向文件內容的指針卻是指向被軟連接的文件的父目錄內容中的文件對應目錄項。可能有許多進程、或線程同時或先後打開同一個v節點,它們的文件號、文件位置、流容器等都是各自的進程、或線程管理的。對於記錄型文件,你可以要求打開文件的n個空記錄,編寫後添加、插入;不一定就是添加到文件尾部。對於流容器的操作,你要位、字節、字符、字、行操作都行;不就是賦值指令。

一、文件頭(inode索引i節點描述符)

 

    inode包含文件的元信息,目錄項則指出文件內容在磁盤中的佈局。APO系統內部不使用文件名,而使用inode號碼來識別文件。表面上,用戶通過文件名,打開文件。實際上,系統內部這個過程分成三步:首先,系統據這個文件名對應的哈希值,從哈希順序列表文件MFT中獲取inode信息;從而找到文件內容數據所在的block,讀出數據。MFT是一個數據表文件,表中每一項記錄就是一個inode;按照inode號碼(文件名字哈希值)排列。 MFT文件頭、其inode索引節點號爲0和卷的根目錄文件放在一起,本體也放在一起,構成自歸文件;MFT文件體就是inode索引節點列表了。

     考慮到本地內存空間有限;inode應儘可能小,APO的只是2W。如果系統支持最大打開16M個不同的文件,目錄項與inode合爲4E就需要64ME = 1M個數據塊。當然,內存一開始、只是一箇中模式位圖變量數據塊中的一個位圖變量被使用,加上4個vnode連續數據塊(即可裝64K個i節點和目錄項的內存v節點);以後,逐步增加。

文件頭(i節點描述符)定義: 2W;節點樹的節點數小於等於4G。

BU2W  inode{    // i節點描述符、文件名字哈希值。

union{ // 如果是目錄文件 i_dirnum.31-28 = 1110

   BU32  i_dirnum;// 低16位、目錄文件的第一個連續數據塊數或頁數。
// i_dirnum.31;  1、該i節點是有效的,0、該i節點是空的(無文件)。

// i_dirnum.30;  1、該i節點是唯一的,0、該i節點是有相同哈希值多項的。

// i_dirnum.29; 1、該i節點是目錄文件,0、該i節點是非目錄文件。

// i_dirnum.28; 1、該i節點是打開的,0、該i節點是未打開的。

//i_dirnum.27-16;  保留12位。

   BU32  i_mblockp;// 指向連續m個數據塊或頁的首指針。
};

union{ // 如果是非目錄文件 i_dirnum.31-28= 1100.

   BU32  i_dirnum;// 低28位、文件的目錄項在父目錄文件表中的項數號。

   BU32  i_fnode; // 父目錄文件i節點inode;父目錄文件名字哈希值。

};

union{ // 如果是有相同哈希值 i_dirnum.31-28= 10x0.

   BU32  i_dirnum; // 保留28位。

   BU32  i_itemp;// 是tname相同文件名字哈希值列表文件中的項指針。

};

union{ // 如果該i節點是打開的i_dirnum.31-28 = 10x1

   BU32  i_dirnum;//低24位是內存v節點表中的項數;v節點號vd。

   BU32  i_itemp;// 是tname相同文件名字哈希值列表文件中的項指針。

};
union{ // 如果該i節點是打開的i_dirnum.31-28 = 11x1

   BU32  i_dirnum;//低24位是內存v節點表中的項數;v節點號vd。

   BU32  不變; // 原來的BU32  i_fnode; 或是BU32  i_mblockp; 

};
}

 

       一個目錄下最多可以有256M個目錄項。 Tname中的項是最多有256個列字段,每一個列字段是一個i節點格式;所以、每一項需要64E。相同哈希值(同名字)的情形是極少的,但APO還是給出允許64K的這種項;每項可以有最多256個相同哈希值的文件i節點描述;Tname動態生成文件可能需要64個數據塊 = 128MB;還有一個64K位的位圖變量在系統變量區域。


     APO的32個元文件和目錄文件,都是放在固態硬盤;文件內容則放在普通硬盤;對文件名的搜索都是在us級。在i節點上,有父節點、目錄項數變量;可以起到快速定位的作用。


     MFT和其他31個文件一起(共32個),組成所謂的“Metafiles”(元文件,也就是System files系統文件)。它們的id號就是文件名的哈希值,是固定的、唯一的。用戶的文件(也包括目錄)的MFT中的ID號也是取決於文件名字的哈希值,ID號就是哈希值。當某文件被刪除時,與之對應的MFT記錄ID號將被空出來;如果此時再次添加文件,並且、文件名字的哈希值與空出的ID號相同,那麼、系統會填充這ID空位。如果,ID號非空、則需要申請Tname文件中的一項;並初始化該項、和ID號對應的i節點。無論簇的大小,文件頭記錄(i節點)大小都是2W。

        理論上$MFT在卷中的分配空間(佔0.0008%),8GW = 1GE = 16K 數據塊 = 32GB。$MFT在固態硬盤中會佔用一塊連續的空間,是最前面的連續16KC(塊) = 1GE的;之後是其它的31個元文件內容。以下是元文件的列表。


項號      元文件     功能       
0          $MFT     主文件列表    最多32GB

1          $MFTMirr 主文件表的部分鏡像   

2          $LJHU    垃圾回收表文件 

3          $

4          $tname   相同文件名字哈希值的列表文件。大約32MB

5          $lname   長文件名字的列表文件。大約16MB

6          $ACL     訪問控制表文件 

7          $Secure  安全文件     

8          $LogFile 日誌文件    

9          $BadClus 壞簇文件  

10         $UpCase  大寫文件

11         $

12         $UJKWT   磁盤數據塊式管理位圖文件     512MB

13         $YUGL    磁盤頁式管理位圖文件         512MB

14         $DYGL    磁盤單位管理位圖文件  16KB

15         $YLJU    硬連接計數文件

16         $XWJGL1  第一個小文件管理文件(1/8/32/128行)21.2GB

17—31     保留

 

    在APO中,因其前32個文件的重要性,對它們的MFT記錄在文件區有一個備份。APO將文件作爲屬性、屬性值的集合來處理。每個屬性由單個的流(stream)組成,即簡單的字符排列。嚴格的說,APO並不對件進行操作,而只對屬性流進行讀寫。如果第一個MFT記錄被破壞了,則APO就讀出第二個記錄找到MFT鏡像文件,鏡像文件的第一個記錄和MFT的第一個記錄完全相同。MFT和MFT鏡像文件的位置記錄在引導扇區中,引導扇區的一個副本放在邏輯磁盤的中間或末尾。

      在APO文件系統中,任何操作都可以被看成是一個“事件”。比如將一個文件從C盤複製到D盤,整個複製過程就是一個事件。事件日誌一直監督着整個操作,當它在目標地——D盤發現了完整文件,就會記錄下一個“已完成”的標記。假如複製中途斷電,事件日誌中就不會記錄“已完成”,APO可以在來電後重新完成剛纔的事件。事件日誌的作用不在於它能挽回損失,而在於它監督所有事件,從而讓系統永遠知道完成了哪些任務,那些任務還沒有完成,保證系統不會因爲斷電等突發事件發生紊亂,最大程度降低了破壞性。


    APO文件系統每次讀寫時,它都會檢查扇區正確與否。當讀取時發現錯誤,APO會報告這個錯誤;當向磁盤寫文件時發現錯誤,APO將會十分智能地換一個完好位置存儲數據,操作不會受到任何影響。在這兩種情況下,APO都會在壞扇區上作標記,以防今後被使用。這種工作模式可以使磁盤錯誤可以較早地被發現,避免災難性的事故發生。


訪問控制表(Access ControlList,ACL)不是把一個文件的用戶分爲四類(根用戶、文件主、同組用戶、其他用戶),而是對任何特定的用戶或用戶組,讓每個文件與特定的存取權限相關聯。


APO的數據大體上可分爲4個部分

(1) Partition boot sector(引導扇區,又稱BPB),此部分爲所有磁盤格式都共有。

(2) Master File Table(主文件列表,MFT),它是對捲上所有文件頭的記錄。

(3) System files(系統文件),APO系統一共有32個系統文件。

(4) File area(數據區),留給用戶的空間。

 

二、目錄項結構:

           APO以一種特殊類型的文件實現了目錄,這種文件的數據塊包含了類型爲APO_dir_entry的結構。每個目錄項4E,由兩部分組成:所包含文件的時間標識、數據塊指針,以及該文件對應的inode號碼等屬性。數據塊指針放在這裏,目的是當刪除一個大目錄時,比如該目錄下有一千萬個文件;那麼I/O只是對該目錄文件,而無需針對一個個獨立的文件i節點進行I/O,速度會相比linux快數十萬倍。其實刪除時,只是將該目錄文件指向回收站中的一個目錄文件項,而在該目錄文件的父目錄文件的對應目錄項清0;相應的非哈希值相同i節點置空;在回收站的回收操作纔開始釋放資源。但最終釋放資源時,如果每個目錄項都有對應的文件內容的數據塊指針、大小;那馬上就可釋放磁盤空間、和相應的inode號了。

BU4E APO_dir_entry{

  BU32 inode;  // 索引i節點號、文件名字哈希值。

  BU16 i_mode; // 描述文件的訪問權限;文件的讀、寫、執行權限 

// i_mode.15-13  ftype; 文件類型: 0-符號連接文件,

// 1-普通文件, 2-塊設備文件,3-字符設備文件,

// 4-文件系統根目錄型文件。

// i_mode.12  ACL;  文件訪問權限是否由ACL文件描述。

// i_mode.11-0 FWQX;文件訪問權限rwx-rwx-rwx-rwx、owner–root–grp-oth

  BU16 i_flags;  // 文件標誌。

// i_flags.15  MASK;      1、文件目錄項在列表時爲.隱藏

// i_flags.15  VISIBLE_FL; 1、文件內容可見。

// i_flags.13  SECRM_FL;    1、文件完全刪除,不可恢復。

// i_flags.12  IMMUYABLE_FL;1、文件不可更改、刪除。

// i_flags.11  NODUMP_FL;  1、文件不可生成“DUMP”文件。

// i_flags.10  NOATIME_FL; 1、文件不要打下時間標識。

// i_flags.9   APPEND_FL; 1、對文件的寫訪問只能是加在文件尾。

// i_flags.8   COMR_FL;  1、文件被壓縮過。

// i_flags.7   DMUB_FL;  1、記錄型文件是大模式管理,0、吸附式管理。

// i_flags.6   GIDINH_FL;1、新文件繼承目錄的組ID,0、繼承進程的。

// i_flags.5   CHOWN_FL;1、擁有者可更改組ID,0、只能超級用戶纔可更改。

// i_flags.4   SYNC_FL; 1、同步更新。

// i_flags.3   LFN_FL;    1、長的文件名字。

// i_flags.2   FILE_MOD;  1、記錄型文件,0、隨機文件。

// i_flags.1-0 MEM_MOD; 文件連續存儲模式:0行,1頁,2數據塊,3單位

}

  BU32 i_uid;     // 描述文件的擁有者標識。

  BU16 i_gid;     // 描述文件的用戶組標識。

  BU16 i_count;  // 到本目錄項的軟連接計數器。

  BU16 i_ycount; // 硬連接的計數器指針。

  BU16 i_size;// 描述文件最後塊或頁的剩餘行數或記錄大小(小文件時就行大小)

  BU32 i_blocks;// 描述文件的數據塊數或頁數或記錄數(小文件時就是0)。

  BU16 i_fop;   // 文件名長度和操作方法表指針號。
// i_fop.15-8  name_len; 高8位文件名字長度。
// i_fop.7-0  file_class_num; 低8位文件所屬的類號。

  BU16 i_mblock; // 描述文件的第一個連續數據塊數或連續頁數m。

  BU32 i_mblockp;// 指向第一個連續m個數據塊或頁或單位的首指針。

  BU64 i_ctime;  // 索引節點最後改變的時間,單位n秒。

  BU64 i_mtime;  // 文件最後修改時間標識,單位n秒。

  BU64 i_crtime; // 文件的出生時間標識,單位nS。

  BU64 i_atime;  // 文件最後訪問時間標識,單位n秒。

  BU16 name[32]; // 最大32個Unicode字符文件名字
// 或30個U字符文件名字 + 長名字項(指向256U字符)數。

}


         APO支持硬連接文件,硬連接文件i節點通常與源文件的i節點不同;即使刪除源文件i節點、源文件的內容也不會消失,由硬連接指向。這類型的文件數不多;所以APO只支持最多64K個的硬連接文件。YLJU硬連接計數列表文件有一個64K位的位圖變量、一個16位的空閒數變量、一個4KE、即64KZ的硬連接計數數組變量count[64k]。小模式管理,用戶要建立一個對某個i節點的連接文件;那麼,先查該i節點的目錄項中的(i_ycount) = 0?是0,則需要分配一個連接計數位圖序號;i_ycount = 位圖序號,count.i_ycount = 2 ,COPY該i節點及目錄項到新建連接文件的部分i節點項和目錄項中去,完成。如果非0,count.i_ycount+,COPY該i節點及目錄項到新建連接文件的部分i節點項和目錄項中去,完成。當刪除一個IMMUYABLE_FL = 0文件時,如count.i_ycount ≠ 0;那麼,count.i_ycount-;如果結果爲0,釋放APO的i節點;調用i_fop類號,所指向的文件系統釋放磁盤空間方法,釋放文件系統的i節點、磁盤空間。如果APO_dir_entry.i_flags.SECRM_FL = 1;那麼,該目錄項清0;否則保留。如果,要知道所有到某個目錄項的軟連接、或到某個文件的硬連接;那隻能搜索MFT文件了。

      有關文件權限這裏就不多說了,這方面linux上是資料很多介紹。那麼,用戶進程是如何訪問根權限的passwd文件?APO系統會提供一個專門的系統方法。該方法只能是訪問屬於用戶的那一部分字段。其實,APO分爲多級;司令只有一個root根;長老級即是root根組、只有200個成員;軍團長級10個成員,每個管理100個大隊;一個大隊有60個小組長。4G個用戶號中的高16位爲0的用戶號,就對應這些官吏。這樣,就會有多級的不同權限的passwd文件。任一個用戶,必定屬於一個小組;登錄時,只是檢查在相應小組的passwd文件;就不必是根權限的passwd文件。當然,用戶可以指定級別組登錄;你要以根用戶登錄,只能指定是長老級組纔行。


     文件內容的組織是比較複雜的,通常二進制文件是看作流stream。有一定結構的看作是記錄文件;如MFT文件,一個記錄是2W代表一個i節點。這種記錄文件通常是不定的刪除、或增加一條記錄;所以它們的存儲都伴隨着管理位圖。一種方法是管理位圖獨立,另一種方法是管理位圖吸附。獨立的就是我們說大模式管理了,吸附模式就是在每個內容數據塊的頭部開闢位圖變量區。比如目錄文件,剛開始只有一個文件時,我們分配16行,可裝一個文件目錄項和.、..目錄項;空出一個。當增加到3個文件時,重新申請爲32行,原來的刪除;空出3個。當增加到200個文件時,重新申請爲連續4頁,在.目錄項配256位管理位圖;就可在256個目錄項中增、或減了。當增加到2000個文件時,重新申請爲連續4個數據塊,在第一個數據塊的頭部配64K位管理位圖變量(佔256行、64個目錄項);還有下一個連續數據塊指針,位圖變量的空閒數等;就可在64K個目錄項中增、或減了。目錄項到了1M,就類似鏈表結構了;所以,你要簡單就吸附式;要速度就大模式管理。APO中的目錄文件是採取動態生成的吸附模式。動態生成是操作系統的常態,而小模式管理是最常用的方式。


三、進程打開文件表

 

內存緩衝區、就是指一段內存空間;與位容器的意思是一樣的,我更喜歡用後者。很多系統的磁盤I/O都使用自己的緩衝區;而這樣一來,磁盤文件內容是先到磁盤緩衝區、之後,內核再拷貝緩衝區到用戶進程空間的緩衝區,或回寫、也是類似的過程。這很不合理、浪費時間、空間。應該使用多大的流容器才合適;磁盤設備驅動是不清楚的,只是按照固定的容器大小來安排;只有編寫應用程序的編程員才清楚。所以,磁盤設備驅動使用的位容器大小應該是由應用程序來定才合理;比如、1PB大小的數據庫文件,我們可以安排一個4GB大小的流容器來操控,只是將該流容器的文件描述符提交給磁盤設備驅動;通知磁盤設備驅動直接用該流容器作爲緩衝區,你將容器灌滿後回消息給我。這樣才合理,文件流是直達用戶空間,而無需多一道拷貝。用戶進程操作完該流容器後,不外是給磁盤設備驅動發一個刷新消息就行了。速度、合理、簡潔、節省內存空間是編程要素啊。

      在APO環境中,多個進程可同時讀一個文件。爲了使得每個進程都能夠按自己的步調讀文件,每個進程必須有自己的文件位置指針,這樣纔不會受到其他進程的影響。所以,我們需要一個進程打開文件表;表中的每一項對應一個文件描述符,表項有文件位置、流容器信息、文件狀態標誌等變量。即使在同一個進程中打開一個文件兩次,也會得到兩個具有獨立文件位置的文件描述符。APO的進程打開文件表與UINX等還是有較大區別的;主要的在於APO的文件I/O、標準I/O方法庫是合一的,即是隻需一套方法;也不需要使用read、write等的方法;APO就是要簡單、和實用。用戶可以直接用文件描述符操作流容器,如R1 = fd.1.W;就是把相連流容器的字空間中的第一個字賦值給寄存器R1。 如fd.199.Z= 3; 就是把3賦值給相連流容器的字符空間中的第199個字符;效果與直接用Gi.A2...Aj.流容器名.199.Z;是一樣的(假設流容器是Gi.A2...Ai下的成員變量)。前者,如果硬件不好實現的話、只能是編譯成一條系統API調用指令setfd( fd.199.Z, 3 ); 那佔據應用程序代碼空間只是1W、但需要10多個ns啊。後者,編譯器直接編譯成一條變量賦值指令,只是3W、3ns。而後者的寫法上也是可以縮短的,寫成Gi..Aj.流容器名.199.Z; 把中間去掉,如果你覺得是不會有同名的時侯,你還可寫成:流容器名.199.Z = 3; 流容器與文件號fd雖然是對應的,但編譯器不知道fd的值啊;也可能會一個流容器對應多個文件號、但一個文件號只能對應一個流容器。所以,我們的應用程序還是操作自己聲明的變量空間吧,文件號fd就用於系統API調用,從而獲得、或設置文件屬性等。

     當然、我們還可以對流容器做複雜的變換、或運算、或字符串處理、或格式化等等;這些都是ns指令級,無需像UNIX的低效率、每次I/O操作都要調用系統方法(read、write等)。我們也可以用fseek方法來改變文件的位置,再rflush(讀磁盤沖洗流容器);或修改完流容器後、用wflush(將流容器刷新到磁盤)。

      APO的系統方法調用,並不是現代操作系統那樣複雜和需要大的時間損耗,需要軟中斷遠程調用;需要從用戶空間陷入到內核空間,權限級別、堆棧和寄存器都要轉換等等,完成後要經過複雜的運動纔回到用戶空間。其實、APO程序中調用系統方法和調用應用程序內的方法,基本上是沒有什麼差別的,系統方法還是使用用戶堆棧、寄存器是使用公用的R0-R4、和系統的R24-R31(用戶通常是R8-R23)。主要的區別是用戶程序需要操作系統管理的變量時,只能調用系統的公有方法來進行;用戶程序只能是行走在自己的時空中,而系統方法可以在宇宙中橫行。系統方法可以將結果返回到R0、R24-R31寄存器中,用戶程序一樣可以讀取R24-R31、R0;但不建議用戶程序去使用R24-R31,因爲系統中斷程序可能會再次改變他們的值。系統中斷只是打斷用戶進程、或線程,不會打斷系統方法,對於系統方法是延時中斷、直到系統方法完成回到用戶程序才真正中斷;所以、系統方法都是原子的。


     APO中的一個進程最多有64K個文件描述符,再大我覺得真的沒必要;要知道,描述符也是要佔用內存資源啊。至於如網絡進程等,是否也使用文件描述符、甚至可達到1G個的文件描述符;還是使用別的方式、還在探討中。一個進程最初只是256個描述符、和256個進程打開文件表項,不夠再動態增長(每次增加256個描述符)。0、1、2號描述符通常在進程打開時對應輸入、輸出、錯誤標準流。與UINX不同,APO的進程打開表項、和系統文件打開表項是合一的;表項也包含了流容器的描述;打開一個文件總是和流容器關聯的。

    文件描述符和打開文件表項之間,也可以使用硬連接、或符號連接;硬連接情形就類同UINX下的子進程的表項和父進程相同;軟連接情形就類同UINX下的dup函數。我也很想完全兼容UNIX,但他們的庫也搞得太囉嗦了。

Process_XMUB{  // 進程小模式屬性表; 1.5KE + 32E 系統管理區域。
   BU256E dx_table{  // 對象頭列表,成爲當前進程時,基址在A0寄存器。
// 992個對象號,前面160個是隻讀;後面832個是可讀、寫。32位對象號的
// 高22位是標誌,低10位纔是對象號。
   BU2W [32] lf_tab;// 類方法表,0是本類、1-31是方法庫DLL。
   BU2W [128] thread_lf_tab;//線程類方法表,128個線程組run()入口和長度。
   BU2W [64] dx_tab;// 靜態對象表,
   BU2W [768] gx_tab;// 共享(動態)對象表。
   BU256 signal;     // 256個信號位圖。
   BU256 [3]  gx_no_WT; // 768個公共動態共享對象號位圖變量。
   BU1K  sblocked;  // 屏蔽碼(對應信號、動態變量、對象位圖)。
}
   BU64K  Thread_WT; // 64K個線程優先級位圖。
   BU64K tblocked;  // 線程屏蔽碼。
   BU64K Thread_RUN_WT;// 線程運行位圖,Thread_WT BIC tblocked後的結果。
   BU1W  [256] FTEP;// 256個進程打開文件表項數組的首指針數組。
   BU256 [256] fd_WT;// 256個256位的位圖變量數組;對應64K個文件描述符。
   BU1E  [256] FTE;// 進程最初的256個打開文件表項。0、1、2已經分配
}

 

     打開文件表項尋址:(Process_XMUB.FTEP.fd.15-8).fd.7-0.E,才能真正的指向文件號fd對應的打開文件表項行首址。這將由編譯器和硬件完成,我們要操作流容器,必須是fd.某某;否則,編譯器只是當成對象尋址。我們不應隨意給fd賦值,只能通過open方法;否則,會因沒有安裝fd對應的文件表項(FTEP.i = 0)、而異常中斷退出進程。( A0 + FTEP + fd.15-8)的內容爲0、意味着對應的256個打開文件表項數組指針沒有安裝。前2項是固定的、已知的,硬件只是判定加上fd的高8位後的內容是否爲0吧;即使非0、已經安裝,硬件還需判斷加上fd的低8位後、第0字內容是否爲0。對於fd.5.W 編譯器是編譯成一條4W(4字)的指令,執行時間4ns。fd.5.W編譯後的實際指令操作是:(((A0 + FTEP + fd.15-8).(fd.7-0)).1.W).5.W,即是fd對應的打開文件表項的第1字的內容纔是流容器的首指針,這是一種3次間接的尋址。第一次是得到進程打開文件表項數組的首指針(A0 + FTEP +fd.15-8),第2次是首指針加上fd的低8位後、得到具體的打開文件表項指針,第3次是打開文件表項的第一字的指針以字空間爲參考的偏移第5個字的內容;硬件要實現這點是很容易的。除了FTEP.0是打開進程時,就已經安裝外;其它FTEP.i都是0。而(A0 +FTEP.0)的內容就指向Process_XMUB.FTE;FTE.0、.1、.2也是打開進程時,就已經安裝;對應3個標準流。APO系統中每個進程都有三個預先定義並自動打開的流,它們是:stdin、stdout和stderr; 分別代表標準輸入、標準輸出以及錯誤輸出。


打開文件表項介紹:
BU1E FTE{ // 1E的打開文件表項。file table entry 文件表項(FTE)

   BU32 vnode_p;  // v節點指針。

   BU32 fstream_p;  // 文件流容器本地內存空間指針。

   BU32  fstream_len;// 流容器對象的大小。單位E

   BU48  f_pos;  // 文件在磁盤中的當前位置。

   BU16  sflags; // 文件狀態標誌。

// sflags.15  FD_CLOEXEC;1、close_on_exec;調用相關exec時,關閉文件

// sflags.14  STREAM_LOCK;  1、鎖住流容器,0、解鎖。

// sflags.13  STREAM_ALLOW; 1、使用流容器,0、禁止使用。

// sflags.12  STREAM_T;  1、帶頭部的流容器,0、無頭部。

// sflags.11  STREAM_MD; 流容器格式:1、全緩衝,0、行緩衝。

// sflags.10  STREAM_S; 1、流容器是用戶定義,0、它方定義。

// sflags.9-8STREAM_MOD;流容器大小模式:0行,1扇區、2頁,3數據塊

// sflags.7   O_ASYNC;  異步I/O。

// sflags.6   O_RSYNC;  同步讀、寫。

// sflags.5   O_DSYNC;  等待寫完成(僅數據)。

// sflags.4   O_SYNC;  等待寫完成(數據和屬性)。

// sflags.3   O_NONBLCOK; 非阻塞模式。

// sflags.2   O_APPEND; 1、對文件的寫訪問只能是加在文件尾。

// sflags.1   QNOTE_FL; 1、軟連接標誌, 0、非軟連接。

// sflags.0   O_EOF;   1、報告文件結束,0、 否。

   BU16 STREAM_NUM;   // 16位流容器內的記錄數。

   BU16 STREAM_AVA_LEN;// 流容器的尾部無效長度(單位E)。

   BU16  fd_count; // 軟連接的引用計數。

   BU16 allow_err; // 低8位錯誤代號、高8位權限、許可標誌。

// allow_err.15  FERR ; 1、操作文件出錯指示,0、否。

// allow_err.14 root;  1、是根用戶,0、否。

// allow_err.13  owner; 1、是用戶擁有者,0、否。

// allow_err.12  grp;   1、是組用戶,0、否。

// allow_err.11 Y_OK; 1、許可進程在目錄中刪除、或新建一個文件,0、否。

// allow_err.10 RD_OK; 1、許可進程讀文件內容,0、否。

// allow_err.9   WR_OK; 1、許可進程寫文件內容,0、否。

// allow_err.8   X_OK;  1、許可進程執行該文件,0、否。

   BU32 tmpfname; // 臨時文件名字指針

}

        可能有2個進程使用同一個流容器,所以、進程可以鎖住流容器、或作爲查詢另一個進程是否完成對流容器的操作;流容器可用於進程間的通信,只需通過消息傳送文件號來建立流容器共享。文件是記錄型時,我們通過查詢方法能得到一些符合條件的記錄放在流容器中;這就需要存放記錄號的地方,帶頭部的流容器就可以實現了。當修改完這些記錄後,我們需要發散回送磁盤;指明瞭帶頭部的流容器標誌,磁盤驅動就會自動發散回送磁盤了。在數據庫中增加、刪除一條或多條記錄等,都需要使用帶頭部的流容器。一些小型的文件(小於一個扇區16E),流容器可以爲連續行大小(1E—15E);因爲磁盤驅動使用連續扇區做單位,不匹配會導致磁盤緩衝區與流容器之間的COPY、造成性能損失;這是沒法的。當流容器大於一個扇區時,請儘量使用連續扇區大小的流容器;這能提升性能。更大的流容器可以使用連續頁(8個扇區/頁)、或連續數據塊(512頁/數據塊)做大小單位;可能會有些內存空間損失(不用的空間爲0),但提高了速度也是值得的。其它標誌是爲了稍爲兼容UNIX而設。

       軟連接標誌爲1,說明本文件號的有效打開文件表項在第一個字的低16位爲引用的fd文件號所指的打開文件表項。除第一個字變量vnode_p低半字、和標誌外,本打開文件表項的其餘變量無效。

         流容器通常是由用戶定義,但也可能是它方定義的、如數據庫服務進程來定義等;記錄型文件通常是由數據庫服務進程來管理。流容器頭部可以設計爲適合數據庫應用,如記錄的數據結構、對象、表等的定義放入流容器頭部;在頭部的頂端,通常是放記錄號數組,目的是給磁盤驅動程序用於散射讀、或寫、經查詢後的相關記錄。查詢表文件的記錄通常是1W的哈希值、或1Z;對於查詢表文件、我們是當作一個二進制流文件;所以、我們需要牽涉到多個文件流之間的聯合操作。查詢表文件的查詢結果輸出,可能就到達另一些記錄型文件的流容器頭部。所以,我們是需要聲明一個保存文件號的數組變量;如果,你要打開上萬個對應到同一個流容器的文件組;那APO是支持靜態編譯器打開的,你可以把它們放在同一個子目錄下;你只要聲明打開這子目錄下的所有文件,剩下的就是編譯器的事情了。APO的日誌文件服務進程就有一些完全支持數據庫服務的線程。APO的數據庫服務支持多字段記錄型表文件、單字段(可以是結構、表、文本對象等)型表文件、日誌文件、大模式的記錄刪除、增加管理文件等等。用戶進程通常是以消息、文件號(流容器)與日誌文件服務進程交互。流容器的尾部無效長度是指、因爲磁盤驅動是以扇區爲單位來讀寫的,與數據記錄不一定能完全匹配造成;用戶是不能操作該區域的,否則、你的下一條數據記錄會被改動。你可能聲明瞭一個巨大的流容器來裝一些記錄,但流容器內的記錄數最多隻能是64K條。文件和流的指示標誌字節、用於文件操作錯誤指示器、文件結束指示器等等。應注意,爲了效率、記錄或結構字段的大小是以行爲單位的。


四、文件內存v節點、進程v節點


      當我們打開一個文件時,在本地內存建立一個v節點;v節點就是文件i節點與對應目錄項的組合。目錄項中的2E文件名字就不放進v節點內了,相應位置是放進i節點的一部分。每個進程可最多打開64K個文件、對應64K個文件號;相應8K個進程、系統打開的文件數最大512M個文件,這是理論上的;實際上很多進程的文件號使用量較少、有時候、多個文件號映射到同一個v節點。所以、系統只是最多可以打開16M個v節點,需要256個64K位的位圖變量、剛好1個位圖變量數據塊;中模式管理。文件號fp就是對應64K位的位圖的位序號;打開一個文件時,如果其i節點已經是打開的、即是指v節點是已經分配了;在該打開的i節點中有v節點號。file_vnode_tab + 4*vp 就是對應fp的v節點在本地內存的首指針vnode_p;我們只須爲該文件分配文件號、打開文件表項、並初始化就行了。如果否、那麼就要申請一個v節點號、並初始化i節點、v節點、打開文件表項FTE。Open一個文件是一件相對複雜的過程:裝入臨時v節點、權限檢查、創建或刪除、否分配文件號、分配v節點號、打開文件表項FTE、初始化、等等。

BU4E  file_vnode{ // 文件對應的本地內存v節點。

   BU32 vnode;    // 索引i節點號、文件名字哈希值。

   BU16 v_mode;   // 描述文件的訪問權限;文件的讀、寫、執行權限

   BU16 v_flags;  // 文件標誌。

   BU32 v_uid;     // 描述文件創建時的擁有者標識。

   BU16 v_gid;     // 描述文件創建時的用戶組標識。

   BU16 v_count;  // 指向該目錄項的軟連接計數器。

   BU16 v_ycount; // 硬連接的計數器指針。

   BU16 v_size;   // 描述文件最後塊或頁的剩餘行數(小文件時就是行大小)

   BU32 v_blocks; // 描述文件的數據塊數或頁數(小文件時就是0)。

   BU16 v_fop;    // 高8位文件名字長度和低8位文件所屬的類號。

   BU16 v_mblocks;// 描述文件的第一個連續數據塊數或連續頁數。

   BU32 v_mvblock;// 指向第一個連續m個數據塊或頁或行的首指針。

   BU64 v_ctime;  // 索引節點最後改變的時間,單位n秒。

   BU64 v_mtime;  // 文件最後修改時間標識,單位n秒。

   BU64 v_crtime; // 文件的出生時間標識,單位nS。

   BU64 v_atime;  // 文件最後訪問時間標識,單位n秒。

   BU32 v_finode; // 父目錄文件i節點inode。父目錄文件名字哈希值

   BU32 v_n;      // 文件的目錄項在父目錄文件表中的項數號。

   BU32 v_fvp;    // 父目錄文件i節點的v節點號。

   BU16 v_yycount;// 本v節點的引用計數器。

   BU16 v_fdn;    // 指向本v節點的文件號數。

   BU32 [12] v_zubd; //暫時保留

}

 

進程v節點:8K*8E = 64KE 所有的8K個進程剛好佔用1個數據塊(塊號32)。

BU8E  Process_vnode{// 進程v節點。

  BU4E  APO_dir_entry;// 進程i節點對應的目錄項。

  BU1E  Process_inode{ // 進程i節點。

   BU32 v_finode; // 父目錄文件i節點inode。父目錄文件名字哈希值

   BU32 v_n;      // 文件的目錄項在父目錄文件表中的項數號。

   BU32 v_fvp;    // 父目錄文件i節點的v節點號。

   BU32 MFT_fdv; // 進程所屬MFT的i節點的打開v節點號

   BU32 pwd_fdv; // 進程當前工作目錄的打開v節點號。

   BU32 DDRDV;  // 進程的共享DDR數據空間數據塊地址

   BU16 yycount;// 本進程的引用計數器。

   BU16 DSstart;// 進程的數據段(變量屬性表)的開始地址(塊號)。

   BU16 pid; // 進程號;也是打開本進程文件的標識號( < 8K )。

   BU16 counter;//任務運行時間計數(滴答數),運行時間片,動態優先級值。

}

  BU1W [8]  p_uid;// 進程用戶號數組。1E

  BU1Z [16] p_gid;// 進程組號數組。1E

  BU1E Process_task{// 進程控制塊。

   BU32utime;   // 用戶態運行時間(滴答數)。

   BU32alarm;   // 報警定時值(滴答數)。

   BU16 priority;// 優先級,開始時 counter = priority;越大運行越長。

   BU16 fs_n; // 進程打開的文件數。

   BU16 class_n;// 進程的類數。

   BU16 dx_n; // 進程的對象數。

   BU16 dtbl_n;  // 進程的動態變量數。

   BU16 Thread_n;//進程的線程數。

   BU3W 暫時保留;

}

}

        

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