iT邦幫忙

0

[6.1810][code] xv6 的 Device (三) : virtio_disk (一)

  • 分享至 

  • xImage
  •  

系列文章 : [6.1810] 跟著 MIT 6.1810 學習基礎作業系統觀念

大綱

  • kernel/virtio.h/virtio_blk_req
  • kernel/virtio.h/virtq_desc
  • kernel/virtio.h/virtq_avail
  • kernel/virtio.h/virtq_used
  • kernel/virtio.h/virtq_used_elem
  • kernel/virtio.h/some macros
  • kernel/virtio.h/summary

kernel/virtio.h/virtio_blk_req

{ 5.2.6 Device Operation }

// the format of the first descriptor in a disk request.
// to be followed by two more descriptors containing
// the block, and a one-byte status.
struct virtio_blk_req {
  uint32 type; // VIRTIO_BLK_T_IN or ..._OUT
  uint32 reserved;
  uint64 sector;
};

想送給 VirtIO block device ( disk ) 的 request。
完整的 request 還需要包含 data, 以及 1 byte 的 status,在 xv6-riscv 實作中,因為 virtio_blk_req, data 跟 status 在 physical address 上不連續,所以拆分成三個 descriptor。

  • type
    • VIRTIO_BLK_T_OUT : 寫資料到 disk 裡面
    • VIRTIO_BLK_T_IN : 從 disk 讀取資料
  • sector : 想寫入到 disk 的哪個位址,以 512 bytes 為單位。
    • 例如 xv6-riscv 的 blockno 是以 BSIZE 為單位,而 BSIZE 是 1024 bytes ( kernel/fs.h )。
    • 當我們想寫入 blockno == 1 的時候,我們實際上想寫入的位址是 1024,但在這裡需要轉成第 2 ( 1024 / 512 ) 個 sector。


kernel/virtio.h/virtq_desc

{ 2.4.5 The Virtqueue Descriptor Table }

// a single descriptor, from the spec.
struct virtq_desc {
  uint64 addr;
  uint32 len;
  uint16 flags;
  uint16 next;
};

描述要傳遞給 VirtIO device 的資料。

  • addr : 要送給 VirtIO device 的資料在 guest os 裡面的 physical address
  • len : 要送給 VirtIO device 的資料的長度
  • flags
    • VRING_DESC_F_NEXT : 代表 descriptor 還沒結束,需要同時處理下一個 descriptor ( descriptor chain )
    • VRING_DESC_F_WRITE :
      • 設為 1,代表 VirtIO device 要對 virtio_blk_req 的 data 進行寫入 ( 以 Guest OS 的角度而言,是從 disk 讀取資料 )。
      • 假如這個 bit 是 0,則 Virtio device 要對 virtio_blk_req 的 data 進行讀取 ( 以 Guest OS 的角度而言,是對 disk 寫入資料 )
  • next : 假如想要一次傳遞多筆資料,需要附上下一筆 desciptor 在 disk.desc 陣列中的 index。這個把 descriptor 串在一起的方式,又稱為 descriptor chain

以 Guest OS 的角度而言,透過 desc 傳遞的資料在 physical address 可能是不連續的,但對於接收資料的 VirtIO device 而言,只要是在同一個 desctipor chain 的資料,會是連續的。



kernel/virtio.h/virtq_avail

{ 2.4.6 The Virtqueue Available Ring }

// the (entire) avail ring, from the spec.
struct virtq_avail {
  uint16 flags; // always zero
  uint16 idx;   // driver will write ring[idx] next
  uint16 ring[NUM]; // descriptor numbers of chain heads
  uint16 unused;
};

driver 把想要交給 VirtIO device 的 descriptor ( virtq_desc ) 的資訊放在這裡。

  • driver 能寫入,VirtIO device 僅能讀取。
  • idx
    • 指的並不是 disk.desc 陣列的 index,而是自己的 ring ( disk.avail->ring ) 的 index。
    • idx 是由 driver 寫入的,遵守嚴格遞增,不需要 modulo queue size。當我們將 idx + 1 時候,是告訴 VirtIO device,這邊有一個新的,可取用的 virtq_avail ring entry。
  • ring
    • ring buffer
    • ring buffer 裡面的元素 ( element ) 是 disk.desc 陣列的 index。假如有使用 descriptor chain 的話,只需要放 descriptor chain 的 head 的 index。


kernel/virtio.h/virtq_used

{ 2.4.8 The Virtqueue Used Ring }

struct virtq_used {
  uint16 flags; // always zero
  uint16 idx;   // device increments when it adds a ring[] entry
  struct virtq_used_elem ring[NUM];
};

當 VirtIO device 處理完 descriptor 之後,會把處理好的 descriptor 的資訊存放在這裡。

  • 與 virtq_avail 相反,VirtIO device 能寫入,driver 僅能讀取。
  • idx : 指的是自己的 ring 的 idx ( disk.used->ring ),每當 VirtIO device 處理完一個 descriptor request,會把處理完的 descriptor 的資訊整理成 virtq_used_elem,放進 ring[idx],並將 idx + 1。
  • ring : 放置 VirtIO device 處理好的 descriptor request。


kernel/virtio.h/virtq_used_elem

{ 2.4.8 The Virtqueue Used Ring }

// one entry in the "used" ring, with which the
// device tells the driver about completed requests.
struct virtq_used_elem {
  uint32 id;   // index of start of completed descriptor chain
  uint32 len;
};
  • id : descriptor 的 index ( disk.desc 陣列的 index )。
  • len : 真的有送到 VirtIO device 的資料長度。在 xv6-riscv 的實作中,似乎沒有去檢查這個欄位。


kernel/virtio.h/some macros

#define VIRTIO_MMIO_MAGIC_VALUE         0x000 // 0x74726976
#define VIRTIO_MMIO_VERSION             0x004 // version; should be 2
#define VIRTIO_MMIO_DEVICE_ID           0x008 // device type; 1 is net, 2 is disk
#define VIRTIO_MMIO_VENDOR_ID           0x00c // 0x554d4551
#define VIRTIO_MMIO_DEVICE_FEATURES     0x010
#define VIRTIO_MMIO_DRIVER_FEATURES     0x020
#define VIRTIO_MMIO_QUEUE_SEL           0x030 // select queue, write-only
#define VIRTIO_MMIO_QUEUE_NUM_MAX       0x034 // max size of current queue, read-only
#define VIRTIO_MMIO_QUEUE_NUM           0x038 // size of current queue, write-only
#define VIRTIO_MMIO_QUEUE_READY         0x044 // ready bit
#define VIRTIO_MMIO_QUEUE_NOTIFY        0x050 // write-only
#define VIRTIO_MMIO_INTERRUPT_STATUS    0x060 // read-only
#define VIRTIO_MMIO_INTERRUPT_ACK       0x064 // write-only
#define VIRTIO_MMIO_STATUS              0x070 // read/write
#define VIRTIO_MMIO_QUEUE_DESC_LOW      0x080 // physical address for descriptor table, write-only
#define VIRTIO_MMIO_QUEUE_DESC_HIGH     0x084
#define VIRTIO_MMIO_DRIVER_DESC_LOW     0x090 // physical address for available ring, write-only
#define VIRTIO_MMIO_DRIVER_DESC_HIGH    0x094
#define VIRTIO_MMIO_DEVICE_DESC_LOW     0x0a0 // physical address for used ring, write-only
#define VIRTIO_MMIO_DEVICE_DESC_HIGH    0x0a4

各個 VirtIO memory-mapped register 的 offset。
詳情可以參考


#define VIRTIO_CONFIG_S_ACKNOWLEDGE     1
#define VIRTIO_CONFIG_S_DRIVER          2
#define VIRTIO_CONFIG_S_DRIVER_OK       4
#define VIRTIO_CONFIG_S_FEATURES_OK     8

可以參考 VirtIO spec 的 { 2.1 Device Status Field }

  • VIRTIO_CONFIG_S_ACKNOWLEDGE
    • 表示 guest OS 已經找到該 device,並將其識別為有效的 VirtIO device。
  • VIRTIO_CONFIG_S_DRIVER
    • 表示 guest OS 知道如何驅動該 VirtIO device
  • VIRTIO_CONFIG_S_DRIVER_OK
    • 表示 guest OS 的 driver 已經設定完畢,並且準備好 drive VirtIO device。
  • VIRTIO_CONFIG_S_FEATURES_OK
    • 表示 guest OS 的 driver 已經確認 (acknowledged) 了所有它能理解的功能,且 feature negotiation 已經完成。

// device feature bits
#define VIRTIO_BLK_F_RO              5  /* Disk is read-only */
#define VIRTIO_BLK_F_SCSI            7  /* Supports scsi command passthru */
#define VIRTIO_BLK_F_CONFIG_WCE     11  /* Writeback mode available in config */
#define VIRTIO_BLK_F_MQ             12  /* support more than one vq */
#define VIRTIO_F_ANY_LAYOUT         27
#define VIRTIO_RING_F_INDIRECT_DESC 28
#define VIRTIO_RING_F_EVENT_IDX     29

可以參考 VirtIO spec 的 { 5.2.3 Feature bits }

  • VIRTIO_BLK_F_RO
    • 當我們設定這個 bit 的時候,表示這個 VirtIO block device 是 read only 的。
    • xv6-riscv 沒有開啟這個 bit
  • VIRTIO_BLK_F_SCSI
    • 當我們設定這個 bit 的時候,代表這個 VirtIO block device 能夠支援並處理 SCSI 封包指令 (SCSI packet commands)。
    • 雖然不知道 SCSI 是什麼,不過 xv6-riscv 沒有使用到這個功能。
  • VIRTIO_BLK_F_CONFIG_WCE
    • 主要作用是允許 VirtIO block device 在 回寫 (Writeback) 與 透寫 (write-through) 快取模式之間進行切換。
    • 假如沒有設定 VIRTIO_BLK_F_CONFIG_WCE
      • 也沒有設定 VIRTIO_BLK_F_FLUSH : 預期會是 write-through
      • 有設定 VIRTIO_BLK_F_FLUSH : 預期會是 write-back
    • xv6-riscv 沒有開啟這個 bit
  • VIRTIO_BLK_F_MQ
    • 有設定這個 bit 的話,表示想要使用多個 virtioqueue。
    • xv6-riscv 沒有開啟這個 bit
  • VIRTIO_F_ANY_LAYOUT
    • 看不懂 … 總之 xv6-riscv 沒有開啟這個 bit
  • VIRTIO_RING_F_INDIRECT_DESC
    • 設定這個 bit 可以使用 INDIRECT_DESC 的功能,xv6-riscv 關掉了這個功能。
  • VIRTIO_RING_F_EVENT_IDX
    • 看不懂 … 總之 xv6-riscv 沒有開啟這個 bit

// this many virtio descriptors.
// must be a power of two.
#define NUM 8

xv6-riscv 預期會使用的 virtioqueue entry 數量。


#define VRING_DESC_F_NEXT  1 // chained with another descriptor
#define VRING_DESC_F_WRITE 2 // device writes (vs read)

可以參考 VirtIO spec 的 { 2.4.5 The Virtqueue Descriptor Table }

  • VRING_DESC_F_NEXT
    • 代表 descriptor 還沒結束,需要同時處理下一個 descriptor ( descriptor chain )
  • VRING_DESC_F_WRITE
    • 設為 1,代表 VirtIO device 要對 virtio_blk_req 的 data 進行寫入 ( 以 Guest OS 的角度而言,是從 disk 讀取資料 )。
    • 假如這個 bit 是 0,則 Virtio device 要對 virtio_blk_req 的 data 進行讀取 ( 以 Guest OS 的角度而言,是對 disk 寫入資料 )

#define VIRTIO_BLK_T_IN  0 // read the disk
#define VIRTIO_BLK_T_OUT 1 // write the disk

可以參考 VirtIO spec 的 { 5.2.6 Device Operation }

  • VIRTIO_BLK_T_IN
    • 從 disk 讀取資料
  • VIRTIO_BLK_T_OUT
    • 寫資料到 disk 裡面


kernel/virtio.h/summary

總結一下 virtio_blk_req virtq_desc, virtq_avail, virtq_used, virtq_used_elem



virtq_desc, virtq_avail, virtq_used, virtq_used_elem 這四個 structure 代表 driver 該怎麼傳遞資訊給 VirtIO device ( 無關乎 Device Types, e.g. network card, block device … )

  • virtq_desc 本身會是一個陣列 ( 有多個 virtq_desc ),而 virtq_avail, virtq_used 則是每個 virtioqueue 會有一個,且裡面會有一個 ring buffer。

  • virtq_used_elem 是 virtq_used 的 ring buffer 的 element。

  • virtq_availvirtq_used 的 idx 欄位,並不是指 virtq_desc 陣列的 index,而是自己 ring buffer 的 index。

  • virtq_avail 的 ring buffer 的 element 是 virtq_desc 的 index

  • virtq_used 的 ring buffer 的 element 是 virtq_used_elem

  • virtq_used_elem 的 id 指的是 virtq_desc 陣列的 index

這邊簡述一下 driver 要傳資訊給 VirtIO device 的流程。

  1. virtq_desc ( descriptor ) 會是多個 descriptor 組成一個陣列。
    driver 把想要傳給 Virtio Device 的資料整理進一個或多個 virtq_desc 裡面。因為我們目前想使用的是 Virtio Block Device,所以要傳給 VirtIO Device 的資料是 virtio_blk_req ( 包含 type, sector, data, status)。
    在使用 descriptor 之前,會需要先向系統 allocate 在 virtq_desc 陣列裡空閒的 descriptor。假如目前沒有空閒的 descriptor,我們可能需要陷入睡眠來等待。
    以 xv6-riscv 的實作而言,type, sector 會包在第一個 descriptor, data 會在第二個 descriptor,status 會在第三個 descriptor,並用 VIRTQ_DESC_F_NEXT flag 把它們串接起來 ( descriptor chain )。

  2. 將 descriptor chain 的第一個 descriptor 在 virtq_desc 陣列裡面的 index,令其為 N。
    把 N 放進 virtq_avail 的 ring 陣列裡面,並更新 idx。這表示正式將這些 virtq_desc 提供給 VirtIO device。

  3. 把 virtioqueue 的 index 寫入 QueueNotify register,以此來通知 VirtIO Device。

  • 注意,這裡的 index 是 virtioqueue 的 index
  • 不是 virtq_desc 陣列的 index
  • 不是 virtq_avail.ring 的 index
  1. VirtIO device 收到來自 driver 的通知後,會去讀取 virtq_avail 得知有新的請求。 VirtIO device 會去 virtq_avail.ring 拿取 virtq_desc 的 index ( 假如有使用 descriptor chain 的話,就會去拿鏈上的第一個 virtq_desc ),並根據 descriptor 進行資料的讀寫。

  2. 當 VirtIO device 完成請求後,會把 virt_desc 陣列的 index,以及 VirtIO device 實際處理的資料的長度放在 virtq_used_elem,並把 virtq_used_elem 放進 virtq_used.ring,VirtIO device 會更新 virtq_used.idx。最後, VirtIO device 會發出中斷給 CPU 。

  3. Driver 在收到來自 VirtIO device 的中斷之後,會去看 virtq_used.ring 裡面的 virtq_used_mem.id,這個 id 表示已經被 VirtIO device 處理完的 descriptor,在 virtq_desc 陣列裡的 index。取得這個 index 之後,就可以去釋放相對應的 virtq_desc,使其成為空閒的 descriptor,供後續的請求重複使用。



virtio_blk_req 則是 VirtIO block device 特有的 structure,當我們想從 block device 讀取或是寫入資料的時候,要給 VirtIO block device virtio_blk_req 這個資訊。

傳遞 virtio_blk_req 給 VirtIO block device 的方式,跟傳遞給任何 VirtIO device 的方式一樣, driver 都需要透過 virtq_desc, virtq_avail 給 VirtIO device 資訊,driver 並透過 virtq_used, virtq_used_elem 讀取來自 VirtIO device 的回覆。



Reference


圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言