---
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)
> 
> 示意圖
而為了管理 NS,所以每個 NS 都會有兩個屬性 :
* NS ID
* 空間名稱
**而對於連接著 Namespace 的 host 來說,每個 Namespace 都可以被視為一個獨立的 SSD**。
---
### Zoned Storage 的概念
#### 定義
> Zoned Storage 是屬於儲存設備的其中一類,主要的特點是它的 address space 被劃分為多個 zone ,這些 zone 的寫入限制與普通儲存設備有所不同。
> > Zone : 被定義為一段連續且有範圍的 LBA
#### 與眾不同的寫入機制
> 
> 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
> 
> 示意圖,取自 [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 的現態的與運作機制,如下圖所示 :
>
>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 的儲存架構

而作為新架構的 ZNS SSD 利用 Zone 、NVMe 定義的 Namespace、Virtual Function NVMe controller 來構築架構
>
相同的是都可以將資料的擺放權移交由 Host 端來負責

#### 衍生的新命令 -- Zone Append
> Zone 內資料需連續寫入,這簡化了主機軟體對資料佈局的管理和對舊資料的更新。但當多個 thread 想對同一個 Zone 同時寫入時,就會出現性能瓶頸,每一個寫 thread 的當前 LBA 都需要跟 Zone 的當前 WP 保持一致。
> 說到資料同步的問題,免不了產生 race condition,一個 thread 成功寫入資料,必然會使 Zone 更新 WP,此時正好另一個 thread 的寫命令到達SSD, WP 已經發生了變化,那麼命令會被 SSD 拒絕。要避免這種情況,程式就需要用到多 thread 之間的同步,來保證兩個寫操作是順序執行。 thread 同步操作不可避免地會引入lock ,thread 變多的時候 lock 的競爭也會加劇,性能同樣會受影響。
>
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;
}
```