1. 程式人生 > 實用技巧 >宇智波程式筆記38-鴻蒙系統 IO 棧分析 | 解讀鴻蒙原始碼

宇智波程式筆記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 流程如下:

  1. 上層應用會在使用者態下呼叫 read / write 介面,這會觸發系統呼叫(syscall)進入核心態;
  2. 系統呼叫往下呼叫 VFS 的介面,如 read 則對應 read,write 對應 write;
  3. VFS 這層會根據 fd 對應的 file 結構拿出超級塊的 inode,利用這個 inode 繼續往下呼叫具體 driver 的 read / write 介面;
  4. 在塊裝置的場景下,它是利用字元裝置的驅動作為它的代理,也就是 driver 下面的 bch。鴻蒙系統的裝置驅動中並沒有塊裝置的驅動,所以它做了一層 block_proxy,無論是字元裝置還是塊裝置的 IO 都會經過 bch 驅動。資料所位於的扇區以及偏移量(offset)計算位於這層;
  5. IO 往下走會有一層快取,叫 bcache。bcache 採用紅黑樹管理這些快取的資料;
  6. 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

    1. <?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 棧的主要區別吧:

  1. 鴻蒙沒有 pagecache。所以鴻蒙的系統呼叫加不加 O_SYNC 應該是一樣的,都是直接下到磁碟。
  2. 鴻蒙沒有通用塊層和 IO 排程層。在 Linux 中通用塊層是用來將連續的塊請求組成一個 bio 結構體,便於對接下層的排程管理。排程層的目的則是用來減少 IO 定址時間,在這層也有多種排程演算法可以選擇,如 cfq/deadline/noop 等。我覺得鴻蒙不是沒有這兩層,而是還沒有做,目前只是 IoT 的適用場景。等明年適用於手機的時候再看看,我覺得應該也會做相關的處理,只不過不一定與 Linux 的處理一樣。
  3. 鴻蒙的驅動層次不夠完整,需要用字元裝置的驅動來代理塊裝置的驅動,不知道這是基於什麼考慮。
  4. 鴻蒙 bcache 的作用與 linux 的 pagecache 作用基本一致,只不過它們在 IO 棧上所在的位置不一樣。