NVMe接口學習筆記1 基本概念
NVMe接口學習筆記2 寄存器
NVMe接口學習筆記3 隊列和PRP/SGL
上回目學習了Command的隊列和其中entry的數據結構,以及怎樣填寫entry和它所對應buffer的位置描述,這篇文章則繼續完成NVMe spec當中數據結構章節的其他內容。
大致回憶一下隊列,隊列通過發送方更新tail指針指向的空描述符,填寫command的ID和對應位置,最後更新該隊列對應的doorbell寄存器,通知接收方有新的entry發送過來。接收方則通過head指針或者Phase tag取得接收到的entry,從而確定哪些是未處理的entry,哪些是已經處理的。
傳輸數據是通過16bytes的地址描述空間來描述數據的位置的,有兩種常用的數據空間描述方法,PRP和SGL,PRP要求所有的數據空間按照最小一個physical page的大小進行定義,在page內數據連續,在結尾處如果有更多數據需要傳輸,則通過指針形式指向下一個page。SGL則更加靈活,每段描述空間不需要指定長度,但是需要更多的描述段類型來判斷數據存放的位置。
一,Metadata Region
這個Metadata可以被定義為每個logical block(LBA 一般512B或者4KiB)所附帶的一個區域,以512bytes的Logical block為例,可以將8B的metadata放在這512B的數據之後,組成一個520B的數據塊一起傳輸,也可分兩次傳輸,放在獨立的metadata buffer裡,但是metadata不能被分割到Logical block和metadata buffer兩個不同的區域。舉個慄子,Logical block是512B,Metadata是8B,分配給Logical block的空間是516B,分4B給Meta data空間。放在一起還是520B,這樣是不允許的。
當我們把namespace定義成metadata和logical block分開存放的格式的時候,我們就使用了Metadata Region,Command的描述符內將定義Metadata Pointer來指向Metadata真正的位置。這個指針必須是32位對齊的地址。
一個NVMe Controller一般支持多種格式的metadata,由於該數據最終會被保存到NAND上,並且會做ECC校驗和加密動作,所以不同的size的metadata最終會呈現出略微不同的performance,這個現象的主要原因還是硬體操作的速率問題,一般對齊的數據會更快,而增加或者減少8-16B會導致ECC和加解密效率下降。所以對於特定的controller硬體實現,一般都會有一個metadata的效率最優解。
如果Namespace enable了Protection information(PI)這個功能,那麼Metadata中的最開始8B或者最後8B是PI,這個數據是提供給host和Drive用來校驗Logical block數據的校驗信息。
二,Completion Queue Entry
CQ的entry和SQ有些不同,每個entry的大小是16B,4個DW。
DW0是command自己定義的信息,如果一個command需要使用DW0那麼可以自己定義內容。DW1是完全reserved。DW2裡面是SQ ID(該CQ可以對應多個SQ,所以需要指定SQ),SQ Head Pointer,通過這種方式可以讓host知道SQ現在的Head在哪裡,是否滿,是否可以塞下一個。
DW3中的status field用來匯報給host這個命令處理的狀態如何。
P表示我們前面提到的phase tag,這個tag只有一個bit,用來表示CQ中真正有效的entry的數目,這個bit在寫入一個有效entry的時候會被反轉,當head回到queue開始的位置的時候也會反轉,這樣就可以判斷當head自加,到達下一個phase tag和當前head的phase tag不一致時,是有效entry的結尾位置。我來手繪一張圖:
這張圖表示所有現在的phase tag都是0,host看不到tail指針,只能看到head指針和tag的情況下,它如何判斷當前的哪個entry是tail呢?這裡如果所有的tag都是一樣的,說明CQ是空的。
如果在這個深度為6的queue裡寫入3個,其中前面的3個的phase tag位就會被置為1,這時看到的head指針的phase tag是1,然後進入一個循環,head++直到找到一個phase tag不為1的entry,這個entry就是tail指針指向的位置。
那麼如果tail寫到了隊列結束的位置繞圈了怎麼辦?這個時候要將寫入的tag值反轉,也就是說如果之前valid entry寫入1,從queue end跳到queue begin的位置的時候,需要改成寫入0。
如上圖就是一個寫滿了的cq的phase tag排布,可以看到,除了queue從結尾到開始的跳轉的一次轉變外,tail永遠都是指向phase tag變化的第一個entry的位置。Nvme 的drive也很好寫:
731 static irqreturn_t nvme_process_cq(struct nvme_queue *nvmeq)
732 {
733 u16 head, phase;
734
735 head = nvmeq->cq_head;
736 phase = nvmeq->cq_phase; //獲取當前phase tag值
737
738 for (;;) {
739 void *ctx;
740 nvme_completion_fn fn;
741 struct nvme_completion cqe = nvmeq->cqes[head];
742 if ((le16_to_cpu(cqe.status) & 1) != phase) //如果當前P和head取出來的P不一樣,則直接break,說明沒有新的valid entry
743 break;
744 nvmeq->sq_head = le16_to_cpu(cqe.sq_head);
// 如果到達隊列結尾位置,則將當前phase tag值反轉
745 if (++head == nvmeq->q_depth) {
746 head = 0;
747 phase = !phase;
748 }
749
750 ctx = free_cmdid(nvmeq, cqe.command_id, &fn);
751 fn(nvmeq->dev, ctx, &cqe);
752 }
753
754 /* If the controller ignores the cq head doorbell and continuously
755 * writes to the queue, it is theoretically possible to wrap around
756 * the queue twice and mistakenly return IRQ_NONE. Linux only
757 * requires that 0.1% of your interrupts are handled, so this isn't
758 * a big problem.
759 */
760 if (head == nvmeq->cq_head && phase == nvmeq->cq_phase)
761 return IRQ_NONE;
762
763 writel(head, nvmeq->q_db + (1 << nvmeq->dev->db_stride));
764 nvmeq->cq_head = head;
765 nvmeq->cq_phase = phase;
766
767 return IRQ_HANDLED;
768 }
最後還有一個command ID(2Bytes)用來表示具體是SQ發送出來的哪個Command,這個位置的大小直接關係到SQ的深度上限,2^16最多64K的SQ深度。
三,Status Feild Definition
這個域是用來定義command所出的各種錯誤的域,如果這個域是0,說明command沒有出錯,不需要處理,但是你我都知道,一個軟體或者固件有80%的code都是用來做各種各樣的錯誤處理的,他們才是系統的主角。
這個域如果不包括Phase Tag這個bit的話一共有15位,從17-31,詳細信息如下:
最低的8位是Status Code,用來描述錯誤或者信息的一個域,之後是3個bit的Status Code Type域,這裡感覺上是SC的擴充位,28-29兩位用來表示host需要等多久之後可以再試一次當前出錯的命令,30位表示該條command出錯還有很多信息可以獲取,使用Get Log Page(一個admin指令)可以獲取這個錯誤的更多信息。最後31位用來表示這個command是不是host可以重新發送該命令並且有可能成功,如果是1表示這個command不用再次嘗試,本來就是期望錯誤,或者不可能成功。如果是0表示該command如果再次嘗試,有可能成功。
Status Code Type
這個code type 用來分類,有些是通用錯誤,比如command被abort了,或者namespace防寫了之類的,這個是Generic Status,另外和command強相關的,在Command specific status這一類中,比如admin創建Q的時候size設置超過最大值了。
Media 和數據完整性錯誤,就是NAND出了問題,而且firmware的糾正功能沒有糾正回來。
Path Related Status這一類是在drive內部的路徑上出現的錯誤,比如DDR或者總線上的傳輸出現了bitflip。
最後是為了測試和debug,設計drive的廠商可以自定義一些command以及對應的錯誤。
三,Controller Memory Buffer (CMB)
這是一個drive上的可選支持功能,並非所有的標準NVMe SSD都需要支持的。在CAP.CMBS位,可以查到當前的nvme controller是不是支持這個功能,另外同樣通過CMB寄存器的CMBLOC和CMBSZ可以得到CMB在host端看到的位置和大小。
CMB是drive和host的一段share memory,從drive和host都可以訪問到這個buffer,並且看到的offset是線性映射的,buffer大小也一樣。
CMB可以有很多中靈活的用法,比如可以用來放SQ的entry,可以用來放PRP和SGL的buffer,也可以用來放unaligned寫入的數據。總之這個buffer可以有很大的想像空間,可以支持很多種優化的做法。
這個buffer必須是4KiB對齊的,最好是8KiB對齊,寫入buffer的數據要在command寫入doorbell寄存器之前就寫完成。
四,Persistent Memory Region (PMR)
和CMB一樣是一個可選的功能,這個功能保證這個區域的數據在遇到異常掉電事件的時候可以被固化或者已經被固化在非易失介質上,從而保證數據的安全性可靠性。這個buffer更像是一個可以固化的DRAM區域,是一種非常理想化的存儲邏輯。但是現在的實現場景上看,固化和速度是一對天敵,固化在現有的介質上,速度一定會比DRAM訪問慢,所以如果實現這個功能,同步狀態檢查和效率上的損失是一定的。
五,NVMe Sets
NVMe Sets是一種後端概念,他們是一組物理上的介質組,
比如一個960GB的drive,host可以按照lun的數目進行劃分,如果有32個lun,可以把前16個和後16個分在不同的NVMe Set上,也可以把不同的block和page分在不同的NVMe set上,通過不同的劃分和組合,可以讓兩個set在進行不同的操作時做到相互影響最小化,從而達到latency可控的目的。
這種做法本質上和Open Channel,ZNS的做法和目的並無不同,只是對於drive內的media介質的開放程度不同,從host角度上來說,可以獲取和配置的信息不同。可以說這種進一步開放media信息給host的做法已經被驗證過是大勢所趨,但是上下分層的界限到底在哪裡最合適,將會是存儲業界通過實踐尋找的一個結果。
六,Namespace List
一個用來顯示現有namespace ID的數據結構。每4個Bytes表示一個NS ID。
七,Controller list
用來顯示有多少Controller以及每個controller的ID。每兩個byte是一個controller ID,最低2B是共有多少個controller,最多有2047個controller。
八,Fused Operations
這是一種通過設置Fused位來將兩個簡單command合併處理的技術,這兩個command將會作為一個原子操作進行處理,並且按照嚴格的順序(NVMe 隊列中的command先後順序並不能決定command真正執行的先後順序),如果第一個command出錯,第二個command將會被abort,如果第二個出錯,第一個command將會回sequence specific狀態。
兩個command操作的LBA range必須是一致的,如果不一致,將會被abort並且返回Invalid Field狀態。
兩個command必須插入在一個SQ裡,必須是連續的,必須update兩個entry到doorbell寄存器裡。(一次加2)
如果host要abort fused command,需要發兩個獨立的abort指令。同時兩個command各自都會收到自己的CQE。
九,Command Arbitration
因為有很多Queue,所以drive針對如何取queue內的entry都有一套自己的算法,所有的controller都應當默認支持round robin的方法,如果學有餘力,可以實現有權重的round robin的算法,或者廠家自定義的算法。
Round Robin方法,
這種方法非常簡單,按照順序從每個Queue中取一個或者幾個command進行處理。
有權重的Round Robin
這種方法有三個權重優先級,高中低三擋,並且還有Urgent和Admin兩種strict policy。
最後是用戶自定義優先級這種優先級可以按照定製用戶的規則來設計,想必航空航天等高安全和高可靠實時設備會更加需要嚴格的優先級設計。
總結,本文主要介紹了Metadata,CQE的數據結構,Cmd的錯誤狀態,CMB和PMR兩種未來可能會大放異彩的NVMe SSD高階功能,NVMe sets這種將media進行初步劃分的功能,以及NVMe SQ之間是如何調度優先級的。
CMB, PMR,NVMe Sets和Q的優先級調度都能在存儲系統中對SSD的功能和性能進行優化,是還有很大發展潛力的NVMe feature。
參考文章:https://blog.csdn.net/Memblaze_2011/article/details/52767226
https://www.sohu.com/a/294397317_505795