宇智波程式筆記38-鴻蒙系統 IO 棧分析 | 解讀鴻蒙原始碼
1.FS 原始碼結構
下載核心原始碼後發現 fs 目錄下似乎缺少很多東西。
當時覺得好奇怪,啥都沒有,那它的 shell 相關命令是怎麼使用 fs 模組進行讀寫的呢?於是發現鴻蒙的 FS 模組主要是從 Nuttx (注:Nuttx 是 Apache 正在孵化的實時作業系統核心)那裡借用了 FS 的相關實現。這是從核心的 fs.h 引用的路徑發現的,它引用的路徑內容如下:
../../../../../third_party/NuttX/include/nuttx/fs/fs.h
所以我們需要找到這個模組,在 gitee 的倉庫中搜索 Nuttx 發現的確有這個倉庫,所以我們需要聯合兩個倉庫的程式碼一起解讀 IO 棧的原始碼。Nuttx 的倉庫地址為:https://gitee.com/openharmony/third_party_NuttX。
我們來看一下 Nuttx 的目錄結構:
可以發現 FS 的具體實現都在這個 Nuttx 倉庫內。接下來我們來看看鴻蒙系統的 IO 棧吧,因為 IO 棧的路徑比較多,所以我們選取塊裝置(block device)的路徑來分析。
2. IO 整體架構
鴻蒙系統關於塊裝置的 IO 棧路徑整體架構如下圖所示:
整體 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
- <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration>
<appender name="stdout" class="org.apache.log4j.ConsoleAppender">
<!--替換成AspectLog4jPatternLayout-->
<layout class="com.yomahub.tlog.core.enhance.log4j.AspectLog4jPatternLayout">
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss,SSS} [%p] %m >> %c:%L%n"/>
</layout>
</appender>
<appender name="fileout" class="org.apache.log4j.DailyRollingFileAppender">
<param name="File" value="./logs/test.log"/>
<!--替換成AspectLog4jPatternLayout-->
<layout class="com.yomahub.tlog.core.enhance.log4j.AspectLog4jPatternLayout">
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss,SSS} [%p] %m >> %c:%L%n"/>
</layout>
</appender>
<root>
<priority value="info"www.chuanchenpt.cn/ />
<appender-ref ref="stdout"www.longtenghai2.com/>
<appender-ref ref="fileout"www.baichuangyule.cn />
</root>
</log4j:configuration>
Logback的配置檔案增強
換掉encoder的實現類或者換掉layout的實現類就可以了
以下給出xml示例:
<?xml version="1.0"www.zhuyngyule.cn encoding=www.bhylzc.cn"UTF-8"?>
<configuration debug=www.shicaiyulezc.cn"false"www.huachenguoj.com >
<property name="APP_NAME" value="logtest"/>
<property name=www.xinhuihpw.com "LOG_HOME" value="./logs" www.wanyaylezc.cn//>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!--替換成AspectLogbackEncoder-->
<encoder class="www".shengrenyp.cn "com.yomahub.tlog.core.enhance.logback.AspectLogbackEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<appender www.baishenjzc.cn name="FILE" www.fanhuagjqw.cn class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${LOG_HOME}/${APP_NAME}.log<www.baihua178.cn /File>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<FileNamePattern>www.yachengyl.cn ${LOG_HOME}/${APP_NAME}.log.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<MaxHistory>30</MaxHistory>
<maxFileSize>1000MB</maxFileSize>
</rollingPolicy>
<!--替換成AspectLogbackEncoder-www.qiaoheibpt.com->
<encoder class="com.yomahub.tlog.core.enhance.logback.AspectLogbackEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS}www.baishenjzc.cn [%thread] %-5level %logger{50} - %msg%n</pattern>
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 棧上所在的位置不一樣。