--- title: Zoned Namespace SSD (ZNS SSD) 的架構解析 --- ## Zoned Namespace SSD (ZNS SSD) 的架構解析 ### Outline * **Namespace 的概念與導入** * **Zoned Storage 的概念** * **ZNS SSD 的架構** --- ### Namespace 的概念與導入 :::info :mega: 詳細的部分可參考 NVMe spec. ::: 基本的 NVMe 的架構是由 controller 、Flash 、PCIe interface 所組成,現在把重點放在前面兩個要件,假如把 Flash 分割成數個獨立的邏輯空間,且每個空間的 LBA 範圍為 0 ~ N-1 ,而這被切出來的空間就被稱為 Namespace (NS) > ![](https://hackmd.io/_uploads/H1mm4wIL2.png) > 示意圖 而為了管理 NS,所以每個 NS 都會有兩個屬性 : * NS ID * 空間名稱 **而對於連接著 Namespace 的 host 來說,每個 Namespace 都可以被視為一個獨立的 SSD**。 --- ### Zoned Storage 的概念 #### 定義 > Zoned Storage 是屬於儲存設備的其中一類,主要的特點是它的 address space 被劃分為多個 zone ,這些 zone 的寫入限制與普通儲存設備有所不同。 > > Zone : 被定義為一段連續且有範圍的 LBA #### 與眾不同的寫入機制 > ![](https://hackmd.io/_uploads/Hko3aqvL3.png =500x300) > Zoned Storage Devices Principle 示意圖 (取自 [zonedstorage.io](https://zonedstorage.io/docs/introduction/zoned-storage)) Zone storage 所定義的寫入機制主要有以下幾個特點 : * **sequential write constraint** * Zone 內的寫入必須為連續寫入,不可為隨機寫入 * 每個 zone 有獨立的 writer pointer (WP),用以尋找、紀錄該 zone 下一次寫入的位址 * Zone 內的資料不能被隨意覆寫,若要覆寫,需先透過 zone reset (或該裝置所定義之類似功能的指令) ,方可進行覆寫 #### Zone Capacity & Size > ![](https://hackmd.io/_uploads/SJj6SXd82.png) > 示意圖,取自 [link](https://www.usenix.org/system/files/atc21-bjorling.pdf) > 引入這一新屬性是為了允許區域大小保持邏輯塊數量的 2 次方(便於邏輯塊到區域編號的轉換),同時允許優化區域存儲容量到底層媒體特性的映射。例如,在基於閃存的設備的情況下,區域容量可以與閃存擦除塊的大小對齊,而無需設備實現大小為 2 的擦除塊。 ##### Zone capacity Zone capacity 所定義的是寫入的 LBAs + 可以被寫入但尚未寫入的 LBAs,白話來說就是被寫的(可利用) LBAs 的數量 表示如下 : * $Written\space LBAs + Unwritten\space LBAs = Zone \space capacity$ ##### Zone size Zone size 根據定義是由 : * $Zone\space capacity + unmapping\space LBAs = Zone\space size$ >有了 zone capacity,就能很好得控制 LBA 映射表的大小,不像 interface ssd 一樣,一開始就對整個存儲空間建立一個 LBA 映射表,如果磁盤足夠大,那對內存的消耗就比較大了;有了 capacity, 那當 capacity 快要被消耗光的時候再建立 LBA,就能少一部分的內存消耗了 #### Active & Open Resources 在 Zoned storage Device 運作時,方便管理資源利用,Zoned storage 定義了以下兩個名詞 : * Active Resources : * 表示當前 Zone Storage Device **可以被處理**的事務的 zone * $\text{number of Active zone} = \text{number of ZSIO}+ \text{number of ZSEO}+ \text{number of ZSC}$ * 由各 spec. 定義的 **maximum number of Active zone** 所限制其數量 * Open Resources: * 表示當前 Zone Storage Device **正在被處理**的事務的必須資源 * $\text{number of Active zone} = \text{number of ZSIO}+ \text{number of ZSEO}$ * 由各 spec. 定義的 **maximum number of Open zone** 所限制其數量 #### Zone States Zone storage 定義一個 FSM , 用來表示 zone 的現態的與運作機制,如下圖所示 : >![](https://hackmd.io/_uploads/H1LRAHuLh.png =600x300) >FSM 示意圖,取自 [NVM-Express-Zoned-Namespace-Command-Set-Spec.](https://nvmexpress.org/wp-content/uploads/NVM-Express-Zoned-Namespace-Command-Set-Specification-1.1c-2022.10.03-Ratified.pdf) 大致上各個 state 的意義如下 : * **Empty (ZSE)** : * 表示該 zone 尚未被使用(未使用前的狀態) * **Open** : * 當需要對 zone 進行寫入時,zone 必需轉態成 open,而又根據使用的 command 不同,又細分成兩種 : * Implicitly Opened (ZSIO) * 為一般寫入,非透過 Zone Management 的 Open Zone 來操作 -- (非應用程式主導) * Explicitly Opened (ZSEO) * 透過 Zone Management 的 Open Zone 來操作 -- (應用程式主導) * **Closed (ZSC)**: * 對 Zone 的操作告一段落,可以釋放所佔用的 Open Resources,而發生的情形有兩種 * **if zone $\in$ ZSIO** * 可在資源瓶頸時被直接 Free 掉,無須特別處理便可轉態為 ZSC * **if zone $\in$ ZSEO** * 在 ZSEO 的 zone 只能接收到 Zone Management 的 CLOSE ZONE 的時候才能轉態為 ZSC -- (由應用程式主導) * **Full (ZSF)** : * 轉態為 ZSF 主要有兩種情況 : * Zone 內的 LBA 全被寫滿 * 接收到 Zone Management 的 FINISH ZONE -- (由應用程式主導) * **Read-Only (ZSRO)** : * Zone 只允許 Read 操作 * **Offline (ZSO)** : * Zone 發生不可預期的錯誤 * e.g. 壞塊 #### Zone Management 如果把 zone 想像成一個 ssd ,那在使用它之前總要知道它的狀態,所以 NVMe spec 定義了一個用來管理 Zone 的指令類別, Zone Management ,大致上對 zone 的操作分為以下幾個 : * **OPEN ZONE** * 允許應用程式明確地打開一個 zone,並向 device 表明,寫**入該 zone 所需的資源應保持可用,直到該 zone 被完全寫入或使用 CLOSE ZONE 指令關閉該 zone 為止**。 ```c static uint16_t zns_open_zone(NvmeNamespace *ns, NvmeZone *zone, NvmeZoneState state, NvmeRequest *req) { uint16_t status; switch (state) { case NVME_ZONE_STATE_EMPTY: status = zns_aor_check(ns, 1, 0); if (status != NVME_SUCCESS) { return status; } zns_aor_inc_active(ns); // 新增配置 Active Resources /* fall through */ case NVME_ZONE_STATE_CLOSED: status = zns_aor_check(ns, 0, 1); if (status != NVME_SUCCESS) { if (state == NVME_ZONE_STATE_EMPTY) { zns_aor_dec_active(ns); // 釋放 Active Resources } return status; } zns_aor_inc_open(ns); // 新增配置 Open Resources /* fall through */ case NVME_ZONE_STATE_IMPLICITLY_OPEN: // 將該 zone 轉態為 IMPLICITLY_OPEN zns_assign_zone_state(ns, zone, NVME_ZONE_STATE_EXPLICITLY_OPEN); /* fall through */ case NVME_ZONE_STATE_EXPLICITLY_OPEN: return NVME_SUCCESS; default: return NVME_ZONE_INVAL_TRANSITION; } } ``` * **CLOSE ZONE** * 允許應用程式明確關閉使用 OPEN ZONE 指令打開的 zone。CLOSE ZONE 向 device 表明,**用於向該 zone 所需的資源已不再需要,可以釋放** ```c static uint16_t zns_close_zone(NvmeNamespace *ns, NvmeZone *zone, NvmeZoneState state, NvmeRequest *req) { switch (state) { case NVME_ZONE_STATE_EXPLICITLY_OPEN: /* fall through */ case NVME_ZONE_STATE_IMPLICITLY_OPEN: zns_aor_dec_open(ns); // * 釋放 Open Resources // 將該 zone 轉態為 CLOSED zns_assign_zone_state(ns, zone, NVME_ZONE_STATE_CLOSED); /* fall through */ case NVME_ZONE_STATE_CLOSED: return NVME_SUCCESS; default: return NVME_ZONE_INVAL_TRANSITION; } } ``` * **RESET ZONE** * 由是應用程式發起,**用來將 write pointer 的位置重置到 zone 的 SLBA (zone 的第一個 LBA)的指令**。執行此指令後,所有寫入**該 zone 的資料都會格式化,不能被訪問** ```c static uint16_t zns_reset_zone(NvmeNamespace *ns, NvmeZone *zone, NvmeZoneState state, NvmeRequest *req) { switch (state) { case NVME_ZONE_STATE_EMPTY: return NVME_SUCCESS; case NVME_ZONE_STATE_EXPLICITLY_OPEN: case NVME_ZONE_STATE_IMPLICITLY_OPEN: case NVME_ZONE_STATE_CLOSED: case NVME_ZONE_STATE_FULL: break; default: return NVME_ZONE_INVAL_TRANSITION; } // 進行 zone reset zns_aio_zone_reset_cb(req, zone); return NVME_SUCCESS; } ``` * **FINISH ZONE** * 允許應用程式將**一個 zone write pointer 移到該 zone 的 end**,以**防止對該 zone 進行** ```c static uint16_t zns_finish_zone(NvmeNamespace *ns, NvmeZone *zone, NvmeZoneState state, NvmeRequest *req) { switch (state) { case NVME_ZONE_STATE_EXPLICITLY_OPEN: /* fall through */ case NVME_ZONE_STATE_IMPLICITLY_OPEN: zns_aor_dec_open(ns); // * 釋放 Open Resources /* fall through */ case NVME_ZONE_STATE_CLOSED: zns_aor_dec_active(ns); // * 釋放 Active Resources /* fall through */ case NVME_ZONE_STATE_EMPTY: // 將 WP 移到 zone 的 end(boundary) zone->w_ptr = zns_zone_wr_boundary(zone); zone->d.wp = zone->w_ptr; // 將該 zone 轉態為 FULL zns_assign_zone_state(ns, zone, NVME_ZONE_STATE_FULL); /* fall through */ case NVME_ZONE_STATE_FULL: return NVME_SUCCESS; default: return NVME_ZONE_INVAL_TRANSITION; } } ``` --- ### ZNS SSD 的架構 #### 架構 某種義上來說, ZNS SSD 算是 OC-SSD 的迭代架構,OC-SSD 是利用 Group、PU、chunk 三個要件來構築出 OC-SSD 的儲存架構 ![](https://hackmd.io/_uploads/Sy1lz9O8n.png) 而作為新架構的 ZNS SSD 利用 Zone 、NVMe 定義的 Namespace、Virtual Function NVMe controller 來構築架構 >![](https://hackmd.io/_uploads/ByzQePu83.png) 相同的是都可以將資料的擺放權移交由 Host 端來負責 ![](https://hackmd.io/_uploads/B1nCZc_Ln.png) #### 衍生的新命令 -- Zone Append > Zone 內資料需連續寫入,這簡化了主機軟體對資料佈局的管理和對舊資料的更新。但當多個 thread 想對同一個 Zone 同時寫入時,就會出現性能瓶頸,每一個寫 thread 的當前 LBA 都需要跟 Zone 的當前 WP 保持一致。 > 說到資料同步的問題,免不了產生 race condition,一個 thread 成功寫入資料,必然會使 Zone 更新 WP,此時正好另一個 thread 的寫命令到達SSD, WP 已經發生了變化,那麼命令會被 SSD 拒絕。要避免這種情況,程式就需要用到多 thread 之間的同步,來保證兩個寫操作是順序執行。 thread 同步操作不可避免地會引入lock ,thread 變多的時候 lock 的競爭也會加劇,性能同樣會受影響。 >![](https://hackmd.io/_uploads/rJ3EjcOI2.png) NVMe ZNS cmd spec. 定義 Zone Append 來取代一般的寫入指令。而命令的執行狀況大致如下 : Zone Append 的 LBA 始終指向當前 Zone 的 SLBA,SSD 在處理時,資料會寫入到當前 WP 指向的位址,同時會將實際寫入的 LBA 位址返回給 host 這種機制下,multi-thread 不需要做同步就可以同時寫入資料。當然這也增加了 Host 處理的複雜度,之前是資料寫入已知的 LBA,現在需要寫入操作完成後才知道實際寫到了哪個 LBA 了 ```c /** * @brief 對於 zone append 寫入的 write function * * @param n - controller * @param req - request * @param append append_flag * @param wrz write_ * @return uint16_t */ static uint16_t zns_do_write(FemuCtrl *n, NvmeRequest *req, bool append, bool wrz) { NvmeRwCmd *rw = (NvmeRwCmd *)&req->cmd; NvmeNamespace *ns = req->ns; uint64_t slba = le64_to_cpu(rw->slba); uint32_t nlb = (uint32_t)le16_to_cpu(rw->nlb) + 1; uint64_t data_size = zns_l2b(ns, nlb); uint64_t data_offset; NvmeZone *zone; // * 利用 complete queue entry 來存放結果 NvmeZonedResult *res = (NvmeZonedResult *)&req->cqe; uint16_t status; // TODO: 檢查該 payload 是否有超過寫入上限的長度 if (!wrz) { status = nvme_check_mdts(n, data_size); if (status) { goto err; } } // ! 示意圖 //! |-----------Zone-----------| //! |lba_0|lba_1| ... |lba_n-1| //! lba_0 = lba_1 = ... = lba_n-1 // TODO: 寫入前檢查寫入範圍是否超過 zone lba range status = zns_check_bounds(ns, slba, nlb); if (status) { goto err; } // TODO 取得要寫入的 zone lba -> zslba zone = zns_get_zone_by_slba(ns, slba); // TODO :檢查是否可以寫入 // ! 檢查點: // ! 1.是否目前的操作會使 open zone 超過 ZNS ssd 負荷的上限 // ! 2. 目前這個 zone 的 state 是否可被寫入 -> // ! EMPTY、IMPLICITLY_OPEN、EXPLICITLY_OPEN、CLOSED -> 可以寫入 status = zns_check_zone_write(n, ns, zone, slba, nlb, append); if (status) { goto err; } // TODO: 確認是否有 zone 可供 open status = zns_auto_open_zone(ns, zone); if (status) { goto err; } if (append) { slba = zone->w_ptr; } // TODO : 寫入後對 zone 內部的 wp 位移,並將所有的 ZS 轉態為 ZSIO res->slba = zns_advance_zone_wp(ns, zone, nlb); // TODO : 將 lba 轉換為 byte 為單位的偏移量,以便 host 尋址 data_offset = zns_l2b(ns, slba); // TODO : 令 write_zero 以外的指令的寫入內容 , 有被 mapping 到 nvme device 裡的 ptr if (!wrz) { status = zns_map_dptr(n, data_size, req); if (status) { goto err; } // TODO: 實際寫入 device backend_rw(n->mbe, &req->qsg, &data_offset, req->is_write); } //TODO: 檢查zone 寫入後的 status zns_finalize_zoned_write(ns, req, false); return NVME_SUCCESS; err: printf("****************Append Failed***************\n"); return status | NVME_DNR; } ```