華爲的鴻蒙系統開源以後第一個想看的模塊就是 FS 模塊,想了解一下它的 IO 路徑與 linux 的區別。如今鴻蒙開源的倉庫中有兩個內核系統,一個是 liteos_a 系統,一個是 liteos_m 系統。二者的區別主要是適應的場景不同,liteos_a 系統適用於硬件資源更加豐富的場景,好比 CPU 更強,內存更大;而 liteos_m 系統則適用於 IoT 設備,相對來講硬件資源比較弱一些。因此咱們就拿 liteos_a 系統來分析一下它的 IO 棧吧,畢竟它應對的場景更加複雜一些。node
鴻蒙系統 liteos_a Kernel 的下載地址在這:https://gitee.com/openharmony/kernel_liteos_a。linux
1.FS 源碼結構
下載內核源碼後發現 fs 目錄下彷佛缺乏不少東西。git
當時以爲好奇怪,啥都沒有,那它的 shell 相關命令是怎麼使用 fs 模塊進行讀寫的呢?因而發現鴻蒙的 FS 模塊主要是從 Nuttx (注:Nuttx 是 Apache 正在孵化的實時操做系統內核)那裏借用了 FS 的相關實現。這是從內核的 fs.h 引用的路徑發現的,它引用的路徑內容以下:算法
../../../../../third_party/NuttX/include/nuttx/fs/fs.hshell
因此咱們須要找到這個模塊,在 gitee 的倉庫中搜索 Nuttx 發現的確有這個倉庫,因此咱們須要聯合兩個倉庫的代碼一塊兒解讀 IO 棧的源碼。Nuttx 的倉庫地址爲:https://gitee.com/openharmony/third_party_NuttX。緩存
咱們來看一下 Nuttx 的目錄結構:架構
能夠發現 FS 的具體實現都在這個 Nuttx 倉庫內。接下來咱們來看看鴻蒙系統的 IO 棧吧,由於 IO 棧的路徑比較多,因此咱們選取塊設備(block device)的路徑來分析。函數
2. IO 總體架構
鴻蒙系統關於塊設備的 IO 棧路徑總體架構以下圖所示: oop
總體 IO 流程以下:源碼分析
- 上層應用會在用戶態下調用 read / write 接口,這會觸發系統調用(syscall)進入內核態;
- 系統調用往下調用 VFS 的接口,如 read 則對應 read,write 對應 write;
- VFS 這層會根據 fd 對應的 file 結構拿出超級塊的 inode,利用這個 inode 繼續往下調用具體 driver 的 read / write 接口;
- 在塊設備的場景下,它是利用字符設備的驅動做爲它的代理,也就是 driver 下面的 bch。鴻蒙系統的設備驅動中並無塊設備的驅動,因此它作了一層 block_proxy,不管是字符設備仍是塊設備的 IO 都會通過 bch 驅動。數據所位於的扇區以及偏移量(offset)計算位於這層;
- IO 往下走會有一層緩存,叫 bcache。bcache 採用紅黑樹管理這些緩存的數據;
- IO 再往下走就是塊設備的驅動,內核沒有通用的塊設備驅動實現,它應該是由不一樣的廠商來實現的。
3.鴻蒙 IO 流程源碼解讀
讀寫流程大體同樣,咱們就看一下鴻蒙的讀數據流程吧。因爲函數的源碼比較長,全貼出來也不太好,因此太長的源碼我只將關鍵的部分截出。
3.1 上層應用讀取數據
上層應用調用 read 接口,這個是系統的 POSIX 接口,read 接口原型以下:
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count);
3.2 VFS
上層應用在用戶態調用 read 接口後會觸發系統調用,這個系統調用在 Kernel 的以下文件中進行註冊:
syscall/fs_syscall.c
對應的系統調用函數爲
237 行的 read 調用的是 VFS 這層的 read,VFS 這層的 read 函數實現位於 Nuttx 項目的以下路徑:
fs/vfs/fs_read.c
read函數從 fd (文件描述符)中獲取對應的 file 對象指針,而後在調用 file_read 接口。file_read 也和 read 函數位於同一個文件下。它從 file 對象中獲取了超級塊的 inode 對象,而後使用這個 inode 調用 bch 驅動的 read 函數。
3.3 bch 驅動
bch 驅動是一個字符設備驅動,它被用來當作上層與塊設備驅動的中間層。註冊塊設備驅動時會調用 block_proxy 來作代理轉換,它的實現位於:
fs/driver/fs_blockproxy.c
當打開(open)一個塊設備時,內核會判斷 inode 是不是塊設備類型,若是是則調用 block_proxy 來作轉換處理。 當上層調用 u.i_ops->read 時,它對應的是 bch_read,它的實現位於:
drivers/bch/bchdev_driver.c
bch_read 會接着調用 bchlib_read,這個函數的實現位於:
drivers/bch/bchlib_read.c
它會根據偏移(offset)計算出在哪一個扇區進行讀數據,若是要讀取的數據只是某個扇區的一部分,則它會先利用 bchlib_readsector 將這個扇區所有讀出來,而後再把對應的那部分數據拷貝到內存並返回。 bchlib_readsector 的實現位於以下位置:
drivers/bch/bchlib_cache.c
它會先將位於內存的髒數據下刷,等髒數據都下刷完成後纔會利用 los_disk_read 把數據從磁盤上讀上來。 los_disk_read 的實現位於 kernel 的以下位置:
fs/vfs/disk/disk.c
這 los_disk_read 這層會有一層緩存,叫 bcache。它會把每次 IO 的扇區緩存到內存中,緩存的組織方式爲紅黑樹。它是有大小限制的,不是無限增加,具體大小與內存大小有關。 los_disk_read 在讀數據以前會先從 bcache 緩存中查找有沒有對應的緩存扇區,若是有則直接將這個扇區返回,若是沒有則調用真正塊設備的 read 函數。這個 read 函數在內核中沒有對應的實現,因此它是跟隨每一個塊設備的驅動的不一樣而不一樣。
整個讀數據流程源碼分析就到這裏。
鴻蒙系統的 IO 棧分支比較多,此次的源碼解讀選用了塊設備的分支進行分析,但願能夠幫助你們更好的理解鴻蒙系統。最後我還想作一下鴻蒙系統與 Linux 關於 IO 棧的對比。
4.鴻蒙 IO 棧與 Linux IO 棧的對比
若是有研究過 linux IO 棧的同窗應該能體會到鴻蒙的 IO 棧是比較簡單。先來看一下 Linux 的 IO 棧總體架構圖:
因此,咱們對比一下鴻蒙系統和 Linux IO 棧的主要區別吧:
- 鴻蒙沒有 pagecache。因此鴻蒙的系統調用加不加 O_SYNC 應該是同樣的,都是直接下到磁盤。
- 鴻蒙沒有通用塊層和 IO 調度層。在 Linux 中通用塊層是用來將連續的塊請求組成一個 bio 結構體,便於對接下層的調度管理。調度層的目的則是用來減小 IO 尋址時間,在這層也有多種調度算法能夠選擇,如 cfq/deadline/noop 等。我以爲鴻蒙不是沒有這兩層,而是尚未作,目前只是 IoT 的適用場景。等明年適用於手機的時候再看看,我以爲應該也會作相關的處理,只不過不必定與 Linux 的處理同樣。
- 鴻蒙的驅動層次不夠完整,須要用字符設備的驅動來代理塊設備的驅動,不知道這是基於什麼考慮。
- 鴻蒙 bcache 的做用與 linux 的 pagecache 做用基本一致,只不過它們在 IO 棧上所在的位置不同。
本文參與了「解讀鴻蒙源碼」技術徵文,歡迎正在閱讀的你也加入。