LIBNVDIMM:非易失性裝置¶
libnvdimm - 核心 / libndctl - 使用者空間輔助庫
版本 13
術語表¶
- PMEM
一個系統物理地址範圍,其寫入是持久的。由 PMEM 組成的塊裝置能夠支援 DAX。一個 PMEM 地址範圍可以跨越多個 DIMM 的交錯。
- DPA
DIMM 物理地址,是相對於 DIMM 的偏移量。當系統中只有一個 DIMM 時,系統物理地址與 DPA 之間存在 1:1 的關聯。一旦添加了更多的 DIMM,就必須解碼記憶體控制器交錯以確定與給定系統物理地址相關聯的 DPA。
- DAX
檔案系統擴充套件,用於繞過頁快取和塊層,將 PMEM 塊裝置中的持久記憶體直接 mmap 到程序地址空間。
- DSM
裝置特定方法:ACPI 方法,用於控制特定裝置——在此情況下是韌體。
- DCR
NVDIMM 控制區域結構,定義於 ACPI 6 第 5.2.25.5 節。它定義了給定 DIMM 的供應商 ID、裝置 ID 和介面格式。
- BTT
塊轉換表:持久記憶體是位元組定址的。現有軟體可能期望寫入的掉電原子性至少為一個扇區(512 位元組)。BTT 是一個具有原子更新語義的間接表,用於支援 PMEM 塊裝置驅動程式並提供任意原子扇區大小。
- 標籤
儲存在 DIMM 裝置上的元資料,用於分割槽和識別(持久命名)分配給不同 PMEM 名稱空間的容量。它還指示是否將 BTT 等地址抽象應用於名稱空間。請注意,傳統的分割槽表(GPT/MBR)是疊加在 PMEM 名稱空間或 BTT 等地址抽象(如果存在)之上的,但未來將棄用分割槽支援。
概述¶
LIBNVDIMM 子系統支援平臺韌體或裝置驅動程式描述的 PMEM。在基於 ACPI 的系統中,平臺韌體透過 ACPI 6 中的 ACPI NFIT “NVDIMM 韌體介面表” 傳輸持久記憶體資源。雖然 LIBNVDIMM 子系統實現是通用的並支援預 NFIT 平臺,但其設計指導思想是支援 ACPI 6 對 NVDIMM 資源定義的超集能力。最初的實現支援 NFIT 中描述的塊視窗孔徑功能,但該支援此後已被放棄,從未在產品中釋出。
支援文件¶
Git 倉庫¶
LIBNVDIMM PMEM¶
在 NFIT 出現之前,非易失性記憶體以各種臨時方式向系統描述。通常只提供最低限度的資訊,即單個系統物理地址範圍,其中寫入預計在系統斷電後仍能持久。現在,NFIT 規範不僅標準化了 PMEM 的描述,還標準化了用於控制和配置的平臺訊息傳遞入口點。
PMEM (nd_pmem.ko):驅動一個系統物理地址範圍。此範圍在系統記憶體中是連續的,並且可以在多個 DIMM 上交錯(由硬體記憶體控制器條帶化)。當交錯時,平臺可以選擇性地提供哪些 DIMM 參與交錯的詳細資訊。
值得注意的是,當檢測到標籤功能(找到 EFI 名稱空間標籤索引塊)時,預設情況下不會建立塊裝置,因為使用者空間需要至少一次將 DPA 分配給 PMEM 範圍。相比之下,ND_NAMESPACE_IO 範圍一旦註冊,就可以立即附加到 nd_pmem。後一種模式稱為無標籤或“傳統”模式。
PMEM 區域、原子扇區和 DAX¶
對於應用程式或檔案系統仍然需要原子扇區更新保證的情況,它可以在 PMEM 裝置或分割槽上註冊一個 BTT。請參閱 LIBNVDIMM/NDCTL:塊轉換表 “btt”
NVDIMM 平臺示例¶
對於本文的其餘部分,任何 sysfs 佈局示例都將參考以下圖表
(a) (b) DIMM
+-------------------+--------+--------+--------+
+------+ | pm0.0 | free | pm1.0 | free | 0
| imc0 +--+- - - region0- - - +--------+ +--------+
+--+---+ | pm0.0 | free | pm1.0 | free | 1
| +-------------------+--------v v--------+
+--+---+ | |
| cpu0 | region1
+--+---+ | |
| +----------------------------^ ^--------+
+--+---+ | free | pm1.0 | free | 2
| imc1 +--+----------------------------| +--------+
+------+ | free | pm1.0 | free | 3
+----------------------------+--------+--------+
在此平臺中,我們有一個插槽中的四個 DIMM 和兩個記憶體控制器。每個 PMEM 交錯集都由一個具有動態分配 ID 的區域裝置標識。
DIMM0 和 DIMM1 的第一部分以 REGION0 的形式交錯。在 REGION0-SPA 範圍內建立了一個單個 PMEM 名稱空間,該範圍跨越了 DIMM0 和 DIMM1 的大部分,使用者指定名稱為 “pm0.0”。該交錯系統物理地址範圍的一部分被保留,以供定義另一個 PMEM 名稱空間。
在 DIMM0 和 DIMM1 的最後一部分,我們有一個交錯的系統物理地址範圍 REGION1,它跨越這兩個 DIMM 以及 DIMM2 和 DIMM3。REGION1 的一部分被分配給一個名為 “pm1.0” 的 PMEM 名稱空間。
當載入來自 tools/testing/nvdimm 的 nfit_test.ko 模組時,核心在裝置 /sys/devices/platform/nfit_test.0 下提供此匯流排。此模組是 LIBNVDIMM 和 acpi_nfit.ko 驅動程式的單元測試。
LIBNVDIMM 核心裝置模型和 LIBNDCTL 使用者空間 API¶
以下是 LIBNVDIMM sysfs 佈局的描述以及透過 LIBNDCTL API 檢視的相應物件層次結構圖。示例 sysfs 路徑和圖表是相對於 NVDIMM 平臺示例的,該示例也是 LIBNDCTL 單元測試中使用的 LIBNVDIMM 匯流排。
LIBNDCTL:上下文¶
LIBNDCTL 庫中的每個 API 呼叫都需要一個上下文,該上下文儲存日誌引數和其他庫例項狀態。該庫基於 libabc 模板
LIBNDCTL:例項化新庫上下文示例¶
struct ndctl_ctx *ctx;
if (ndctl_new(&ctx) == 0)
return ctx;
else
return NULL;
LIBNVDIMM/LIBNDCTL:匯流排¶
匯流排與 NFIT 之間存在 1:1 的關係。目前對基於 ACPI 的系統的期望是,只有一個平臺全域性 NFIT。儘管如此,註冊多個 NFIT 是微不足道的,規範並未排除這種情況。基礎設施支援多個匯流排,我們利用此功能在單元測試中測試多種 NFIT 配置。
LIBNVDIMM:/sys/class 中的控制類裝置¶
此字元裝置接受 DSM 訊息,並將其傳遞給由其 NFIT 控制代碼標識的 DIMM
/sys/class/nd/ndctl0
|-- dev
|-- device -> ../../../ndbus0
|-- subsystem -> ../../../../../../../class/nd
LIBNVDIMM:匯流排¶
struct nvdimm_bus *nvdimm_bus_register(struct device *parent,
struct nvdimm_bus_descriptor *nfit_desc);
/sys/devices/platform/nfit_test.0/ndbus0
|-- commands
|-- nd
|-- nfit
|-- nmem0
|-- nmem1
|-- nmem2
|-- nmem3
|-- power
|-- provider
|-- region0
|-- region1
|-- region2
|-- region3
|-- region4
|-- region5
|-- uevent
`-- wait_probe
LIBNDCTL:匯流排列舉示例¶
查詢描述 NVDIMM 平臺示例中匯流排的匯流排控制代碼
static struct ndctl_bus *get_bus_by_provider(struct ndctl_ctx *ctx,
const char *provider)
{
struct ndctl_bus *bus;
ndctl_bus_foreach(ctx, bus)
if (strcmp(provider, ndctl_bus_get_provider(bus)) == 0)
return bus;
return NULL;
}
bus = get_bus_by_provider(ctx, "nfit_test.0");
LIBNVDIMM/LIBNDCTL:DIMM (NMEM)¶
DIMM 裝置提供了一個字元裝置用於向硬體傳送命令,並且它是 LABEL 的容器。如果 DIMM 由 NFIT 定義,則可以提供一個可選的“nfit”屬性子目錄以新增 NFIT 特定的內容。
請注意,核心中“DIMM”的裝置名稱是“nmemX”。NFIT 透過“記憶體裝置到系統物理地址範圍對映結構”描述這些裝置,並且沒有要求它們必須是物理 DIMM,因此我們使用了一個更通用的名稱。
LIBNVDIMM:DIMM (NMEM)¶
struct nvdimm *nvdimm_create(struct nvdimm_bus *nvdimm_bus, void *provider_data,
const struct attribute_group **groups, unsigned long flags,
unsigned long *dsm_mask);
/sys/devices/platform/nfit_test.0/ndbus0
|-- nmem0
| |-- available_slots
| |-- commands
| |-- dev
| |-- devtype
| |-- driver -> ../../../../../bus/nd/drivers/nvdimm
| |-- modalias
| |-- nfit
| | |-- device
| | |-- format
| | |-- handle
| | |-- phys_id
| | |-- rev_id
| | |-- serial
| | `-- vendor
| |-- state
| |-- subsystem -> ../../../../../bus/nd
| `-- uevent
|-- nmem1
[..]
LIBNDCTL:DIMM 列舉示例¶
請注意,在此示例中,我們假設是 NFIT 定義的 DIMM,它們由“nfit_handle”標識,這是一個 32 位值,其中:
位 3:0 記憶體通道內的 DIMM 編號
位 7:4 記憶體通道編號
位 11:8 記憶體控制器 ID
位 15:12 插槽 ID(如果在場節點控制器範圍內)
位 27:16 節點控制器 ID
位 31:28 保留
static struct ndctl_dimm *get_dimm_by_handle(struct ndctl_bus *bus,
unsigned int handle)
{
struct ndctl_dimm *dimm;
ndctl_dimm_foreach(bus, dimm)
if (ndctl_dimm_get_handle(dimm) == handle)
return dimm;
return NULL;
}
#define DIMM_HANDLE(n, s, i, c, d) \
(((n & 0xfff) << 16) | ((s & 0xf) << 12) | ((i & 0xf) << 8) \
| ((c & 0xf) << 4) | (d & 0xf))
dimm = get_dimm_by_handle(bus, DIMM_HANDLE(0, 0, 0, 0, 0));
LIBNVDIMM/LIBNDCTL:區域¶
每個 PMEM 交錯集/範圍都註冊一個通用 REGION 裝置。根據示例,“nfit_test.0”總線上有兩個 PMEM 區域。區域的主要作用是充當“對映”的容器。對映是一個 <DIMM, DPA-起始偏移量, 長度> 的元組。
LIBNVDIMM 為 REGION 裝置提供了內建驅動程式。此驅動程式負責解析所有 LABEL(如果存在),然後發出 NAMESPACE 裝置供 nd_pmem 驅動程式使用。
除了“mapping”、“interleave_ways”和“size”的通用屬性外,REGION 裝置還匯出了一些便利屬性。“nstype”指示此區域發出的名稱空間裝置的整數型別,“devtype”複製 udev 在“add”事件時儲存的 DEVTYPE 變數,“modalias”複製 udev 在“add”事件時儲存的 MODALIAS 變數,最後,在區域由 SPA 定義的情況下,提供可選的“spa_index”。
LIBNVDIMM:區域
struct nd_region *nvdimm_pmem_region_create(struct nvdimm_bus *nvdimm_bus,
struct nd_region_desc *ndr_desc);
/sys/devices/platform/nfit_test.0/ndbus0
|-- region0
| |-- available_size
| |-- btt0
| |-- btt_seed
| |-- devtype
| |-- driver -> ../../../../../bus/nd/drivers/nd_region
| |-- init_namespaces
| |-- mapping0
| |-- mapping1
| |-- mappings
| |-- modalias
| |-- namespace0.0
| |-- namespace_seed
| |-- numa_node
| |-- nfit
| | `-- spa_index
| |-- nstype
| |-- set_cookie
| |-- size
| |-- subsystem -> ../../../../../bus/nd
| `-- uevent
|-- region1
[..]
LIBNDCTL:區域列舉示例¶
基於 NFIT 獨有資料(如“spa_index”(交錯集 ID))的示例區域檢索例程。
static struct ndctl_region *get_pmem_region_by_spa_index(struct ndctl_bus *bus,
unsigned int spa_index)
{
struct ndctl_region *region;
ndctl_region_foreach(bus, region) {
if (ndctl_region_get_type(region) != ND_DEVICE_REGION_PMEM)
continue;
if (ndctl_region_get_spa_index(region) == spa_index)
return region;
}
return NULL;
}
LIBNVDIMM/LIBNDCTL:名稱空間¶
一個 REGION,在解決了 DPA 別名和 LABEL 指定的邊界之後,會顯露一個或多個“名稱空間”裝置。目前,“名稱空間”裝置的出現會觸發 nd_pmem 驅動程式載入並註冊一個磁碟/塊裝置。
LIBNVDIMM:名稱空間¶
以下是兩種主要 NAMESPACE 型別的一個示例佈局,其中 namespace0.0 代表由 DIMM 資訊支援的 PMEM(請注意它有一個‘uuid’屬性),而 namespace1.0 代表一個匿名 PMEM 名稱空間(請注意它沒有‘uuid’屬性,因為不支援 LABEL)
/sys/devices/platform/nfit_test.0/ndbus0/region0/namespace0.0
|-- alt_name
|-- devtype
|-- dpa_extents
|-- force_raw
|-- modalias
|-- numa_node
|-- resource
|-- size
|-- subsystem -> ../../../../../../bus/nd
|-- type
|-- uevent
`-- uuid
/sys/devices/platform/nfit_test.1/ndbus1/region1/namespace1.0
|-- block
| `-- pmem0
|-- devtype
|-- driver -> ../../../../../../bus/nd/drivers/pmem
|-- force_raw
|-- modalias
|-- numa_node
|-- resource
|-- size
|-- subsystem -> ../../../../../../bus/nd
|-- type
`-- uevent
LIBNDCTL:名稱空間列舉示例¶
名稱空間相對於其父區域進行索引,示例如下。這些索引在每次引導時大多是靜態的,但子系統對此不作保證。對於靜態名稱空間識別符號,請使用其“uuid”屬性。
static struct ndctl_namespace
*get_namespace_by_id(struct ndctl_region *region, unsigned int id)
{
struct ndctl_namespace *ndns;
ndctl_namespace_foreach(region, ndns)
if (ndctl_namespace_get_id(ndns) == id)
return ndns;
return NULL;
}
LIBNDCTL:名稱空間建立示例¶
如果給定區域有足夠的可用容量來建立新名稱空間,核心會自動建立空閒名稱空間。名稱空間例項化涉及查詢空閒名稱空間並對其進行配置。大多數情況下,名稱空間屬性的設定可以以任何順序進行,唯一的限制是“uuid”必須在“size”之前設定。這使得核心能夠使用靜態識別符號在內部跟蹤 DPA 分配。
static int configure_namespace(struct ndctl_region *region,
struct ndctl_namespace *ndns,
struct namespace_parameters *parameters)
{
char devname[50];
snprintf(devname, sizeof(devname), "namespace%d.%d",
ndctl_region_get_id(region), parameters->id);
ndctl_namespace_set_alt_name(ndns, devname);
/* 'uuid' must be set prior to setting size! */
ndctl_namespace_set_uuid(ndns, parameters->uuid);
ndctl_namespace_set_size(ndns, parameters->size);
/* unlike pmem namespaces, blk namespaces have a sector size */
if (parameters->lbasize)
ndctl_namespace_set_sector_size(ndns, parameters->lbasize);
ndctl_namespace_enable(ndns);
}
為何使用術語“名稱空間”?¶
例如,為什麼不是“卷”(volume)?“卷”有使 ND (libnvdimm 子系統) 與 device-mapper 等卷管理器混淆的風險。
該術語最初用於描述可在 NVME 控制器內建立的子裝置(請參閱 nvme 規範:https://www.nvmexpress.org/specifications/),而 NFIT 名稱空間旨在與 NVME 名稱空間的功能和可配置性並行。
LIBNVDIMM/LIBNDCTL:塊轉換表 “btt”¶
BTT(設計文件:https://pmem.io/2014/09/23/btt.html)是名稱空間的一個個性化驅動程式,它將整個名稱空間呈現為一種“地址抽象”。
LIBNVDIMM:BTT 佈局¶
每個區域最初都將至少有一個 BTT 裝置,即種子裝置。要啟用它,請設定“namespace”、“uuid”和“sector_size”屬性,然後根據區域型別將裝置繫結到 nd_pmem 或 nd_blk 驅動程式。
/sys/devices/platform/nfit_test.1/ndbus0/region0/btt0/
|-- namespace
|-- delete
|-- devtype
|-- modalias
|-- numa_node
|-- sector_size
|-- subsystem -> ../../../../../bus/nd
|-- uevent
`-- uuid
LIBNDCTL:BTT 建立示例¶
與名稱空間類似,每個區域都會自動建立一個空閒的 BTT 裝置。每次配置和啟用此“種子”BTT 裝置時,都會建立一個新的種子。建立 BTT 配置涉及兩個步驟:找到一個空閒的 BTT 並將其分配給一個名稱空間。
static struct ndctl_btt *get_idle_btt(struct ndctl_region *region)
{
struct ndctl_btt *btt;
ndctl_btt_foreach(region, btt)
if (!ndctl_btt_is_enabled(btt)
&& !ndctl_btt_is_configured(btt))
return btt;
return NULL;
}
static int configure_btt(struct ndctl_region *region,
struct btt_parameters *parameters)
{
btt = get_idle_btt(region);
ndctl_btt_set_uuid(btt, parameters->uuid);
ndctl_btt_set_sector_size(btt, parameters->sector_size);
ndctl_btt_set_namespace(btt, parameters->ndns);
/* turn off raw mode device */
ndctl_namespace_disable(parameters->ndns);
/* turn on btt access */
ndctl_btt_enable(btt);
}
一旦例項化,一個新的非活動 BTT 種子裝置將出現在該區域下方。
一旦“名稱空間”從 BTT 中移除,該 BTT 裝置的例項將被刪除或重置為預設值。此刪除僅在裝置模型級別進行。為了銷燬 BTT,“資訊塊”需要被銷燬。請注意,要銷燬 BTT,介質需要以原始模式寫入。預設情況下,核心將自動檢測 BTT 的存在並停用原始模式。可以透過 ndctl_namespace_set_raw_mode() API 為名稱空間啟用原始模式來抑制此自動檢測行為。
LIBNDCTL 概述圖¶
對於上述示例,以下是 LIBNDCTL API 所見物件的檢視
+---+
|CTX|
+-+-+
|
+-------+ |
| DIMM0 <-+ | +---------+ +--------------+ +---------------+
+-------+ | | +-> REGION0 +---> NAMESPACE0.0 +--> PMEM8 "pm0.0" |
| DIMM1 <-+ +-v--+ | +---------+ +--------------+ +---------------+
+-------+ +-+BUS0+-| +---------+ +--------------+ +----------------------+
| DIMM2 <-+ +----+ +-> REGION1 +---> NAMESPACE1.0 +--> PMEM6 "pm1.0" | BTT1 |
+-------+ | | +---------+ +--------------+ +---------------+------+
| DIMM3 <-+
+-------+