[原] KVM 虛擬化原理探究(6)— 塊設備IO虛擬化


KVM 虛擬化原理探究(6)— 塊設備IO虛擬化

標簽(空格分隔): KVM


塊設備IO虛擬化簡介

上一篇文章講到了網絡IO虛擬化,作為另外一個重要的虛擬化資源,塊設備IO的虛擬化也是同樣非常重要的。同網絡IO虛擬化類似,塊設備IO也有全虛擬化和virtio的虛擬化方式(virtio-blk)。現代塊設備的工作模式都是基於DMA的方式,所以全虛擬化的方式和網絡設備的方式接近,同樣的virtio-blk的虛擬化方式和virtio-net的設計方式也是一樣,只是在virtio backend端有差別。

傳統塊設備架構

塊設備IO協議棧

image_1aqo0ufta16pqlsm7nvuvc1ur71g.png-133.1kB

如上圖所示,我們把塊設備IO的流程也看做一個TCP/IP協議棧的話,從最上層說起。

Page cache層,這里如果是非直接IO,寫操作如果在內存夠用的情況下,都是寫到這一級后就返回。在IO流程里面,屬於writeback模式。 需要持久化的時候有兩種選擇,一種是顯示的調用flush操作,這樣此文件(以文件為單位)的cache就會同步刷到磁盤,另一種是等待系統自動flush。

VFS,也就是我們通常所說的虛擬文件系統層,這一層給我們上層提供了統一的系統調用,我們常用的create,open,read,write,close轉化為系統調用后,都與VFS層交互。VFS不僅為上層系統調用提供了統一的接口,還組織了文件系統結構,定義了文件的數據結構,比如根據inode查找dentry並找到對應文件信息,並找到描述一個文件的數據結構struct file。文件其實是一種對磁盤中存儲的一堆零散的數據的一種描述,在Linux上,一個文件由一個inode 表示。inode在系統管理員看來是每一個文件的唯一標識,在系統里面,inode是一個結構,存儲了關於這個文件的大部分信息。這個數據結構有幾個回調操作就是提供給不同的文件系統做適配的。下層的文件系統需要實現file_operation的幾個接口,做具體的數據的讀寫操作等。

struct file_operations {
    //文件讀操作
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    //文件寫操作
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    int (*readdir) (struct file *, void *, filldir_t);
    //文件打開操作
    int (*open) (struct inode *, struct file *);
};

再往下就是針對不同的文件系統層,比如我們的ext3,ext4等等。 我們在VFS層所說的幾個文件系統需要實現的接口,都在這一層做真正的實現。這一層的文件系統並不直接操作文件,而是通過與下層的通用塊設備層做交互,為什么要抽象一層通用塊設備呢?我們的文件系統適用於不同的設備類型,比如可能是一個SSD盤又或者是一個USB設備,不同的設備的驅動不一樣,文件系統沒有必要為每一種不同的設備做適配,只需要兼容通用快設備層的接口就可以。

位於文件系統層下面的是通用快設備層,這一層在程序設計里面是屬於接口層,用於屏蔽底層不同的快設備做的抽象,為上層的文件系統提供統一的接口。

通用快設備下層就是IO調度層。用如下命令可以看到系統的IO調度算法.

➜  ~ cat /sys/block/sda/queue/scheduler
noop deadline [cfq]
  1. noop,可以看成是FIFO(先進先出隊列),對IO做一些簡單的合並,比如對同一個文件的操作做合並,這種算法適合比如SSD磁盤不需要尋道的塊設備。

  2. cfq,完全公平隊列。此算法的設計是從進程級別來保證的,就是說公平的對象是每個進程。系統為此算法分配了N個隊列用來保存來自不同進程的請求,當進程有IO請求的時候,會散列到不同的隊列,散列算法是一致的,同一個進程的請求總是被散列到同一個隊列。然后系統根據時間片輪訓這N個隊列的IO請求來完成實際磁盤讀寫。

  3. deadline,在Linux的電梯調度算法的基礎上,增加了兩個隊列,用來處理即將超時或者超時的IO請求,這兩個隊列的優先級比其他隊列的優先級較高,所以避免了IO飢餓情況的產生。

塊設備驅動層就是針對不同的塊設備的真實驅動層了,塊設備驅動層完成塊設備的內存映射並處理塊設備的中斷,完成塊設備的讀寫。

塊設備就是真實的存儲設備,包括SAS,SATA,SSD等等。塊設備也可能有cache,一般稱為Disk cache,對於驅動層來說,cache的存在是很重要的,比如writeback模式下,驅動層只需要寫入到Disk cache層就可以返回,塊設備層保證數據的持久化以及一致性。
通常帶有Disk cache的塊設備都有電池管理,當掉電的時候保證cache的內容能夠保持一段時間,下次啟動的時候將cache的內容寫入到磁盤中。

塊設備IO流程

應用層的讀寫操作,都是通過系統調用read,write完成,由Linux VFS提供的系統調用接口完成,屏蔽了下層塊設備的復雜操作。write操作有直接IO和非直接IO之分(緩沖IO),非直接IO的寫操作直接寫入到page cache后就返回,后續的數據依賴系統的flush操作,如果在flush操作未完成的時候發生了系統掉電,那可能會丟失一部分數據。直接IO(Direct IO),繞過了page cache,數據必須達到磁盤后才返回IO操作完成。

對於I/O的讀寫流程,邏輯比較復雜,這里以寫流程簡單描述如下:
image_1aqo0lnfn6m8ei6lef1qg019p713.png-46.9kB

  1. 用戶調用系統調用write寫一個文件,會調到sys_write函數;
  2. 經過VFS虛擬文件系統層,調用vfs_write,如果是緩存寫方式,則寫入page cache,然后就返回,后續就是刷臟頁的流程;如果是Direct I/O的方式,就會走到塊設備直接IO(do_blockdev_direct_IO)的流程;
  3. 構造bio請求,調用submit_bio往具體的塊設備下發請求,submit_bio函數通過generic_make_request轉發bio,generic_make_request是一個循環,其通過每個塊設備下注冊的q->make_request_fn函數與塊設備進行交互;
  4. 請求下發到底層的塊設備上,調用塊設備請求處理函數__make_request進行處理,在這個函數中就會調用blk_queue_bio,這個函數就是合並bio到request中,也就是I/O調度器的具體實現:如果幾個bio要讀寫的區域是連續的,就合並到一個request;否則就創建一個新的request,把自己掛到這個request下。合並bio請求也是有限度的,如果合並后的請求超過閾值(在/sys/block/xxx/queue/max_sectors_kb里設置),就不能再合並成一個request了,而會新分配一個request;
  5. 接下來的I/O操作就與具體的物理設備有關了,塊設備驅動的讀寫也是通過DMA方式進行。
    image_1aqnul1bnu1b1o3l14m81es11r72m.png-19.8kB

如上圖所示,在初始化IO設備的時候,會為IO設備分配一部分物理內存,這個物理內存可以由CPU的MMU和連接IO總線的IOMMU管理,作為共享內存存在。以一個讀取操作為例子,當CPU需要讀取塊設備的某個內容的時候,CPU會通過中斷告知設備內存地址以及大小和需要讀取的塊設備地址,然后CPU返回,塊設備完成實際的讀取數據后,將數據寫入到共享的內存,並以中斷方式通知CPU IO流程完成,並設置內存地址,接着CPU直接從內存中讀取數據。
寫請求類似,都是通過共享內存的方式,這樣可以解放CPU,不需要CPU同步等待IO的完成並且不需要CPU做過多的運算操作。
因為塊設備IO的虛擬化需要經過兩次IO協議棧,一次Guest,一次HV。所以需要把塊設備IO協議棧說的很具體一點。

至此,Linux塊設備的IO層就基本介紹完整了,以上內容也只是做一個簡單的介紹,這部分的內容可以很深入的去了解,在此限於篇幅限制,就不做過多介紹了。

塊設備IO虛擬化

塊設備的全虛擬化方式和網絡IO的DMA設備虛擬化方式類似,這里就不過多介紹了,主要介紹一下virtio-blk。

image_1aqo1nvbo1o615hn2kuptlbso1t.png-52.7kB

如上圖所示,塊設備IO的虛擬化流程和網絡IO的流程基本一致,差別在於virtio-backend一段,virtio-net是寫入到tap設備,virtio-blk是寫入到鏡像文件中。
塊設備IO的流程需要經過兩次IO協議棧,一次位於Guest,一次位於HV。當我們指定virtio的cache模式的時候,實際上指定的是virtio-backend(下面簡稱v-backend)寫入HV塊設備的方式。
在虛擬化層次來看,Guest對於這幾種Cache模式是沒有感知的,也就是無論Cache模式是怎樣,Guest都不會有所謂的繞過Guest的Page cache等操作,Virtio-front模擬的是驅動層的操作,不會涉及到更上層的IO協議棧。

image_1aqo7picj8ks1qng1lt51errfvim.png-62.7kB

如上圖所示,藍色表示 writethrough,黃色表示 none,紅色表示 writeback。其中虛線表示寫到哪一個層次后write調用返回。

  • cache=writethrough (藍色線)
    表示v-backend打開鏡像文件並寫入時候采用非直接IO+flush操作,也就是說每次寫入到Page cache並flush一次,直到數據被真實寫入到磁盤后write調用返回,這樣必然會導致數據寫入變慢,但是好處就是安全性較高。

  • cache=none (黃色線)
    cache為none模式表示了v-backend寫入文件時候使用到了DIRECT_IO,將會繞過HV的Page cache,直接寫入磁盤,如果磁盤有Disk cache的話,寫入Disk cache就返回,此模式的好處在於保證性能的前提下,也能保證數據的安全性,在使用了Disk cache電池的情況下。但是對於讀操作,因為沒有寫入HV Page cache,所以會有一定性能影響。

  • cache=writeback (紅色線)
    此模式表示v-backend使用了非直接IO,寫入到HV的Page后就返回,有可能會導致數據丟失。

總結

塊設備IO的虛擬化方式也是統一的virtio-x模式,但是virtio-blk需要經過兩次IO協議棧,帶來了不必要的開銷。前面的鋪墊都是為了介紹三種重要的cache模式。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM