在介紹NVMe SSD的讀IO處理流程之前,需要往下一層到達PCIe層。無論是NVMe的命令本身,還是要傳輸的數據,最終都會被封裝成為TLP包進行傳輸。AIC、U.2以及M.2等形態的NVMe SSD也都是借助PCIe插槽與host進行交互的。
PCI Express Layered Model
上圖就詳細說明了PCIe 協議中每層的內容。后文中我們會通過一個trace記錄,結合NVMe記錄和TLP包信息對NVMe的讀I/O進行解讀。
NVMe協議通過下圖描述了一個完整的命令處理流程:
本文使用的是《NVM ExpressTM Revision 1.2a》(以下簡稱“NVMe協議”),嚴謹起見,在此注明版本,需要說明的是不同的NVMe協議版本在讀操作中沒有本質的不同,也不會影響到本文的描述。
本文介紹的是NVMe over PCIe的讀I/O,不涉及NVMe over Fabrics或者其他網絡傳輸。
為了更好的理解上圖中命令處理過程的細節,我們總結如下:
1. 一個I/O命令執行過程中,host和SSD是兩個主角。讀I/O也就是host發起讀請求,SSD響應請求,并且將數據寫到host指定的內存地址上的過程。
2. Submission Queue 和Completion Queue是host內存中的兩個隊列,host通過Submission Queue告知SSD的Controller要處理的命令,Controller通過Completion Queue告知Host已經被處理完畢的命令以及這些命令執行的狀態。需要補充的是, NVMe的命令分為Admin Command和NVM Command,Admin Command執行一些SSD管控操作,比如Namespace管理和固件升級等,系統中只有一對Admin Submission Queue和Admin Completion Queue;NVM Command用于執行讀寫等I/O命令,可以有多對I/O Submission Queue和I/O Completion Queue。
常見的形式如上圖,服務器CPU每個核都會綁定一個I/O Submission Queue 和一個I/O Completion Queue,另一方面,隊列上限也不能超過Controller的處理能力。
3. Submission Queue Tail Doorbell和Completion Queue Head Doorbell,這是兩個Controller Registers中的字段,會映射在host內存中。host通過更新Submission Queue Tail Doorbell映射的內存上的記錄,就可以達到告知SSD有新的命令需要處理,同樣,在命令執行尾聲,host也是通過更新Completion Queue head Doorbell映射的內存上的信息而實現告知SSD命令已經執行完畢。
4. 至于SSD要把數據往內存哪段地址上寫,怎么寫,就要靠PRP或者SGL,下文也會對此解讀。
前文介紹了一些概念和部分操作細節,接下來是更為形象和深入的解讀。在此我們還要借助協議分析儀,分析儀的職責是將host和SSD傳輸的0和1翻譯成人話,借助支持NVMe協議的分析儀,我們獲得了一條讀I/O的Trace記錄,如下圖(每行信息記錄都可以進一步展開看到相應的TLP包和DLLP包的信息):
分析儀可以對host和SSD之間傳輸的數據進行解析,并按照PCIe和NVMe的規范進行展示。有了這張圖,我們就可以更為直觀的看到每一步host和SSD都做了什么。接下來就開始了。首先是這條命令執行過程信息的概覽。
可以看出,這次我們使用的設備是一個3.2TB的PBlaze5 916 NVMe SSD(以下簡稱為“PBlaze5”)。host從SSD上讀出了1024dwords(4KiB)數據,這段數據在SSD上起始地址(SLBA)是0x8,一共8個LBA(NLB=0x7,這一參數為0‘s based value)。數據將從SSD傳輸到0xFEB84000(PRP1)這個地址上,最終這個操作成功了(SC為Successful Completion)。
名詞&概念
SLBA(Starting LBA):數據在SSD上的起始地址,這里的值是0x8。
NLB(Number of Logical Blocks):讀取數據的blocks數,這里的值是0x7,需要指出的是這是一個0’s based value,所以最終傳的是8個block。
NSID(Namespace Identifier):指明了此次讀操作的namespace id,這里值為0x1。
命令信息被存放在ID為0x004C的Submission Queue(SQID)中,SSD處理完畢的信息會存放在ID為0x004C的Completion Queue(CQID)中。此外,這條記錄還標注出了SSD在服務器上的ID(Device ID)等信息。
第一步:host準備一條命令,并將之加入到內存中的Submission Queue中。這步屬于host自身的行為,還沒有開始和SSD交換數據。
第二步:host告知SSD有新的待處理。這步操作包含很多細節,前文說過,SQyTDBL(Submission Queue y Tail Doorbell)是Controller Registers信息,映射在host的BAR 0空間中,其具體的地址是0xC6421260,這步中,host更新了這個地址中的值,將Submission Queue中有新紀錄的信息告知了SSD。
上圖展示了host更新SQyTDBL的細節。第一行是NVMe協議層面的內容,下面則是具體的TLP包信息和ACK信息。到這SSD終于知道了有新的需求。
第三步。SSD取Command。從上文可知,命令具體內容被存放在Submission Queue中,SSD也知道了有新的命令需要處理,這里SSD首先發起一個Memory Read的請求。
host把命令內容信息通過另一個TLP包返回給SSD。一個Submission Queue記錄是64byte,所以,可以看到一共傳輸了16個dwords,共64Bytes。而這16dwords中的內容翻譯出來就是第一行NVMe 的內容,可以看到命令的地址、隊列和命令的ID、操作類型(OPC)、數據的地址、以及SSD在系統中的Device ID都在其中了。有了這些內容,SSD就可以往指定的內存地址上寫數據了。
第四步:SSD處理Command。對于host來講,這是一個讀取數據的操作。對于SSD來講,就需要將相應的數據(SLBA為0x8,NLB為0x7)使用DMA的方式寫到指定的內存地址(PRP1 Address為0xFEB84000)上。
系統的MaxPayloadSize為256bytes,超過就需要分開傳輸。所以這段1024dwords(4KiB)數據傳輸操作被拆分成了16個64dwords(256bytes)的TLP包。另一個重要的概念是PRP(Physical Region Page),本例中PRP1是一個host內存中的地址——0xFEB84000。SSD從內存的0xFEB84000開始寫,最后一個TLP包的起始地址是0xFEB84F00。
NVMe規定了PRP和SGL兩種數據傳輸方式,后者在NVMe over Fabrics中使用比較普遍,這里不再贅述。采用PRP方式的I/O命令都會有PRP1和PRP2兩個地址,它們可以指向一個內存地址,也可以是一個物理地址列表,被稱為PRP list,如果傳輸數據量超過兩個內存頁,就需要PRP list指明數據存放的地址。本文中例子數據量為4KiB,恰好為Linux的1個內存頁大小,所以使用PRP1傳一個物理地址就可以。更為復雜的PRP記錄規范,可以參看NVMe協議的《Physical Region Page Entry and List》等內容。
第五步,到現在,SSD已經把數據傳到了host指定的內存地址上。SSD會把命令執行的狀態總結成為一條記錄添加在Completion Queue中,具體的操作就是SSD向host內存的Completion Queue中添加一個4dwords(16bytes)的記錄。
上圖中NVMe返回的信息可見,SSD成功的完成了寫,并在Status Code(SC)中記下Successful Completion的信息。如果整個命令過程有錯誤出現,那么SC也會記錄下相應的錯誤信息。
第六步,SSD發起中斷,通知host處理Completion Queue中的記錄。數據傳輸完畢,執行狀態也記錄在了Completion Queue中,SSD通過MSI-X中斷告知host做進一步的處理。
第七步。host會處理相應的Completion Queue中的記錄。但是這部分細節發生在host內部,分析儀的trace記錄不會捕捉到具體內容。
第八步。Host更新內存中Completion Queue y Head Doorbell信息,相應的Controller Registers中Doorbell信息也會進行更新。至此,一個讀I/O已經處理完畢。
NVMe協議中還規定了很多其他的命令,根據其命令類型不同,返回值和對內存的操作也各不相同,但原理和本文中介紹的讀I/O相似,相信看完本文,您可以觸類旁通,理解NVMe 協議中host和SSD的交互機制
評論
查看更多