CS140e: (3) 文件系統

這次作業的主題是 FAT32 文件系統的實現。

第一部分是實現 memory allocator, 這樣之後就可以用需要在堆上分配內存的數據結構了。但是在這之前要實現一個更好的 panic 取代之前一 panic 就 abort 的行為。這裡就引入了 Language Items 的概念,就是通過標註,通知編譯器在特定情況下插入對我們選擇的函數的調用。完成後 panic 就可以顯示一些有用的信息,方便調試了。過程中發現因為從 ttywrite 發送完 kernel image 到 screen 接上去之間有時間差,會錯過一些輸出,所以我修改了下 bootloader, 收到 kernel 後不立即啟動,等 screen 接上按任意鍵啟動,這樣就不會錯過任何輸出了。

接下來,要實現對 ARM tags 的讀取,以獲得可用內存的信息。讀取的過程中要用到 union, 這是一個 unsafe 的東西,也進行了封裝,可以說這已經成為了這門課程的一種習慣了。

之後為 allocator 實現了一些工具函數,包括對齊和獲取可用內存的範圍。期間對比了 C 的allocator 介面和 Rust 的 allocator 介面,個人認為 Rust 中申請者可以指定對齊,無疑比起 C 欽定一個對齊值更靈活,但是這樣的靈活性也給 allocator 實現帶來了不小的麻煩。Rust 中申請者需要自己記住申請內存的有關信息(大小,對齊)以在釋放時提供,其實邏輯上是更合理的,也簡化了 allocator 的職責。

接下來是實現 allocator 了,實現的 allocator 最終通過 #[global_allocator] 的標註註冊。一共實現了兩種 allocator, 第一個是最 naive 的,無腦向後面分配,完全不回收。第二種使用一系列鏈表維護不同大小類別的空間。(這裡用到了 intrusive linked list 其實為之後留下了坑)利用 #[path] 標註可以在兩種實現間切換,可以說是非常巧妙了。

第二大部分是實現 FAT32, 主要是照著 FAT32 文檔實現,因為 FAT32 畢竟是實際應用的文件系統,還是有點麻煩的,花了不少時間。這裡也體現出了 CS140e 的試驗性,一度給出的測試是錯的,似乎是標準實現沒有正確處理被刪掉的文件,列出目錄內容的時候也會列出,讓人有點困擾。

第三部分首先是需要封裝 SD card driver, 屬於簡單的 Rust FFI 教學。值得注意的是, sd_readsector() 如果出錯,會將錯誤信息寫入全局變數 sd_err, 這不是線程安全的,所以需要加鎖保護一下。

接下來需要讓 SD card driver 和之前的 FAT32 相結合,本來感覺不複雜,但是真正上樹莓派測試卻出現了詭異的錯誤,運行到一半就跑不下去了。我原以為是 FAT32 實現的問題,但將 SD 卡用 dd 做成鏡像到本地測試(因為樹莓派上調試不便),卻發現一切正常。用 panic/kprintln 調了一天,終於把出錯範圍縮小到 memory allocator 里 intrusive linked list 的某處 unsafe 里的一個內存寫。原來 arm 對非對齊的內存訪問是會 fault 的。那麼測試時又為何不會出現這個問題呢?因為 x86 不對齊也可以訪問。因為 intrusive linked list 會在分配的地址空間寫入一個 usize, 因此每塊分配的內存都至少需要滿足 usize 的大小和對齊。修復後便可以正常工作了。(中間有個小插曲,我一度認為 64 位下 usize 是 4 bytes, 但實際是 8 bytes)但是回顧 FAT32 的實現,對齊問題並沒有根本上解決:實現中 block 在內存中是以 [u8] 存儲,其對齊為 1, 這樣轉換為 FAT32 的一些 struct 後,對某些 field 的讀取可能是非對齊的。不過由於這些 field 的對齊要求都沒有 usize 嚴,所以並不會暴露出來。由於我不知道怎麼限制 [u8] 的對齊,所以這個修復還處於坑掉的狀態。希望知道怎麼解決的朋友教我一下。

最後一部分是實現 cd, ls, pwd 和 cat 這幾個命令,有了之前的鋪墊,實現都不複雜。

通過調試這個作業的經歷,我深深感受到了 unsafe 其實不好寫,要寫對需要考慮的東西很多,尤其是需要跨平台的時候。想想之前寫的東西,又有多少是真正 safe 的呢。


推薦閱讀:

TAG:Rust編程語言 | 樹莓派RaspberryPi | 操作系統 |