1.IOVA
IOVA、IO仮想アドレス。DPDK の上位層 EAL (環境抽象化層) によって管理される機能の一部には、他のドライバーによるアプリケーションのためにハードウェア デバイスのレジスタをメモリにマッピングすることが含まれます。つまり、ユーザーモードのプロセスは、IO アドレスを直接使用して IO 操作を実行できます。前述したように、これらのアドレスは、物理アドレス (PA) と IO 仮想アドレス (IOVA) に分類できます。上位層はこの 2 つを区別しません。つまり、アプリケーション層の場合、上位層は 2 つの影響を受けません。すべてのユーザー モード プロセスが参照するのは IOVA アドレスです。
PA の IOVA モードの利点は、カーネル空間アプリケーションおよびすべてのハードウェアで使用できることですが、欠点は、メモリ操作にアクセス許可が必要な場合に面倒になることです。同様に、メモリの断片が多数ある場合、メモリを割り当てることができない可能性があり、DPDK 全体の初期化が失敗する原因になります。これらの問題を解決するために、一般的には 1G などのより大きなページが使用され、システムの起動時に大きなページを使用するように誘導されます。しかし、洞察力のある人であれば、「頭痛が頭の病気を治すなら、足の痛みは足の病気を治す」という諺にあるように、これが一時的な解決策にすぎないことが一目でわかります。
VA の IOVA モードでは、アドレスの変換と分析に IOMMU が必要です。一般に、設計を行ったことがある人は、抽象化が主に効率の低下を意味することを理解しています (ゼロコストの抽象化を除く)。その利点は、メモリ処理が実際の物理メモリによって完全に制限されず、特別なアクセス許可も必要ないことです。特にクラウド環境 (仮想環境では IOMMU の方が適しています) では、DPDK の適用範囲がさらに広がります。もちろん、これには多くの欠点があります。ハードウェアが必ずしも IOMMU をサポートしているわけではない、プラットフォームが IOMMU をサポートしていない、ソフトウェアがサポートしていない、または IOMMU が制限されているなどです。
通常の状況では、DPDK はデフォルトで PA の IOVA モードを使用することを選択します。1 つは安全で、もう 1 つは幅広い適用性があります。ただし、条件が許せば VA の IOVA モードを使用することをお勧めします。DPDK17.11 以降のバージョンでは、コマンド
–iova-mode を使用して
、適切なモードを自動的に選択できます。
2. IOVAの適用
これは IO 操作であるため、理論的には、この機能は従来のドライバーの機能と似ています。DPDK では、ハードウェアの割り込みマッピングとレジスタ マッピングの両方にカーネルの支援が必要です。コンピューターを使ったことがある人ならわかるように、PCI にバインドする必要があります。この PCI の特徴は、特定のデバイスのセットに束縛されないことです。ハードウェア ドライバーを作成したことのある人は、一部のデバイスのタイプ ID が通常、ドライバー内にハードコーディングされていることを知っています。したがって、理論的には、このタイプのどのデバイスでも使用できます。しかし、開発者は多くの場合、水深がどれくらいであるかを知っています。
DPDK では、ユーザー空間 (UIO) では、独自の制限 (igb_uio を使用) のため、PA の IOVA モードのみを使用でき、これにより UIO 内のアプリケーションも制限され、VFIO カーネル ドライバーの上位バージョンで推奨されます ( Linux3.6) では、IOMMU を使用して特別に開発されているため、VFIO での処理に前の 2 つのモードを選択できます (ただし、PA モードの権限の問題は依然として存在します)。カーネルの新しいバージョン (>=4.5) まで待つと、enable_unsafe_noiommu_mode オプションを設定した後、IOMMU なしで VFIO を使用できるようになります。これはさらに有利である。
もちろん、DPDK では、PMD (ソフトウェア ポーリング モード ドライバー) および一部の関連ソフトウェアはドライバーの動作に PCI を必要とせず、標準のカーネル インフラストラクチャを通じてハードウェアを動作させるため、上記の IOVA は無視できます。それは問題ではありません。
3. データ構造とソースコード
まず、関連するアドレス定義を確認します。
/** Physical address */
typedef uint64_t phys_addr_t;
#define RTE_BAD_PHYS_ADDR ((phys_addr_t)-1)
/**
* IO virtual address type.
* When the physical addressing mode (IOVA as PA) is in use,
* the translation from an IO virtual address (IOVA) to a physical address
* is a direct mapping, i.e. the same value.
* Otherwise, in virtual mode (IOVA as VA), an IOMMU may do the translation.
*/
typedef uint64_t rte_iova_t;
#define RTE_BAD_IOVA ((rte_iova_t)-1)
後で分析されるデータ構造を含む以前の分析では、これら 2 つのデータ型の適用を確認できます。関連する変換コードを見てみましょう。
//\dpdk-stable-19.11.14\lib\librte_eal\linux\eal\eal_memory.c
rte_iova_t
rte_mem_virt2iova(const void *virtaddr)
{
if (rte_eal_iova_mode() == RTE_IOVA_VA)
return (uintptr_t)virtaddr;
return rte_mem_virt2phy(virtaddr);
}
もう一度 VFIO を見てください。
struct user_mem_map {
uint64_t addr;
uint64_t iova;
uint64_t len;
};
//\lib\librte_eal\linux\eal\eal_vfio.h
struct vfio_iommu_type {
int type_id;
const char *name;
bool partial_unmap;
vfio_dma_user_func_t dma_user_map_func;
vfio_dma_func_t dma_map_func;
};
モード判定をもう一度見てください。
/* IOMMU types we support */
static const struct vfio_iommu_type iommu_types[] = {
/* x86 IOMMU, otherwise known as type 1 */
{
.type_id = RTE_VFIO_TYPE1,
.name = "Type 1",
.partial_unmap = false,
.dma_map_func = &vfio_type1_dma_map,
.dma_user_map_func = &vfio_type1_dma_mem_map
},
/* ppc64 IOMMU, otherwise known as spapr */
{
.type_id = RTE_VFIO_SPAPR,
.name = "sPAPR",
.partial_unmap = true,
.dma_map_func = &vfio_spapr_dma_map,
.dma_user_map_func = &vfio_spapr_dma_mem_map
},
/* IOMMU-less mode */
{
.type_id = RTE_VFIO_NOIOMMU,
.name = "No-IOMMU",
.partial_unmap = true,
.dma_map_func = &vfio_noiommu_dma_map,
.dma_user_map_func = &vfio_noiommu_dma_mem_map
},
};
//4\lib\librte_eal\common\eal_common_bus.c
/*
* Get iommu class of devices on the bus.
*/
enum rte_iova_mode
rte_bus_get_iommu_class(void)
{
enum rte_iova_mode mode = RTE_IOVA_DC;
bool buses_want_va = false;
bool buses_want_pa = false;
struct rte_bus * bus;
TAILQ_FOREACH(bus, &rte_bus_list, next) {
enum rte_iova_mode bus_iova_mode;
if (bus->get_iommu_class == NULL)
continue;
bus_iova_mode = bus->get_iommu_class();
RTE_LOG(DEBUG, EAL, "Bus %s wants IOVA as '%s'\n",
bus->name,
bus_iova_mode == RTE_IOVA_DC ? "DC" :
(bus_iova_mode == RTE_IOVA_PA ? "PA" : "VA"));
if (bus_iova_mode == RTE_IOVA_PA)
buses_want_pa = true;
else if (bus_iova_mode == RTE_IOVA_VA)
buses_want_va = true;
}
if (buses_want_va && !buses_want_pa) {
mode = RTE_IOVA_VA;
} else if (buses_want_pa && !buses_want_va) {
mode = RTE_IOVA_PA;
} else {
mode = RTE_IOVA_DC;
if (buses_want_va) {
RTE_LOG(WARNING, EAL, "Some buses want 'VA' but forcing 'DC' because other buses want 'PA'.\n");
RTE_LOG(WARNING, EAL, "Depending on the final decision by the EAL, not all buses may be able to initialize.\n");
}
}
return mode;
}
カーネルでの IOMMU のサポートを確認してください。
//\drivers\bus\pci\linux\pci.c
#if defined(RTE_ARCH_X86)
bool
pci_device_iommu_support_va(const struct rte_pci_device *dev)
{
#define VTD_CAP_MGAW_SHIFT 16
#define VTD_CAP_MGAW_MASK (0x3fULL << VTD_CAP_MGAW_SHIFT)
const struct rte_pci_addr *addr = &dev->addr;
char filename[PATH_MAX];
FILE *fp;
uint64_t mgaw, vtd_cap_reg = 0;
snprintf(filename, sizeof(filename),
"%s/" PCI_PRI_FMT "/iommu/intel-iommu/cap",
rte_pci_get_sysfs_path(), addr->domain, addr->bus, addr->devid,
addr->function);
fp = fopen(filename, "r");
if (fp == NULL) {
/* We don't have an Intel IOMMU, assume VA supported */
if (errno == ENOENT)
return true;
RTE_LOG(ERR, EAL, "%s(): can't open %s: %s\n",
__func__, filename, strerror(errno));
return false;
}
/* We have an Intel IOMMU */
if (fscanf(fp, "%" PRIx64, &vtd_cap_reg) != 1) {
RTE_LOG(ERR, EAL, "%s(): can't read %s\n", __func__, filename);
fclose(fp);
return false;
}
fclose(fp);
mgaw = ((vtd_cap_reg & VTD_CAP_MGAW_MASK) >> VTD_CAP_MGAW_SHIFT) + 1;
/*
* Assuming there is no limitation by now. We can not know at this point
* because the memory has not been initialized yet. Setting the dma mask
* will force a check once memory initialization is done. We can not do
* a fallback to IOVA PA now, but if the dma check fails, the error
* message should advice for using '--iova-mode pa' if IOVA VA is the
* current mode.
*/
rte_mem_set_dma_mask(mgaw);
return true;
}
#elif defined(RTE_ARCH_PPC_64)
bool
pci_device_iommu_support_va(__rte_unused const struct rte_pci_device *dev)
{
return false;
}
#else
bool
pci_device_iommu_support_va(__rte_unused const struct rte_pci_device *dev)
{
return true;
}
#endif
enum rte_iova_mode
pci_device_iova_mode(const struct rte_pci_driver *pdrv,
const struct rte_pci_device *pdev)
{
enum rte_iova_mode iova_mode = RTE_IOVA_DC;
switch (pdev->kdrv) {
case RTE_KDRV_VFIO: {
#ifdef VFIO_PRESENT
static int is_vfio_noiommu_enabled = -1;
if (is_vfio_noiommu_enabled == -1) {
if (rte_vfio_noiommu_is_enabled() == 1)
is_vfio_noiommu_enabled = 1;
else
is_vfio_noiommu_enabled = 0;
}
if (is_vfio_noiommu_enabled != 0)
iova_mode = RTE_IOVA_PA;
else if ((pdrv->drv_flags & RTE_PCI_DRV_NEED_IOVA_AS_VA) != 0)
iova_mode = RTE_IOVA_VA;
#endif
break;
}
case RTE_KDRV_IGB_UIO:
case RTE_KDRV_UIO_GENERIC:
iova_mode = RTE_IOVA_PA;
break;
default:
if ((pdrv->drv_flags & RTE_PCI_DRV_NEED_IOVA_AS_VA) != 0)
iova_mode = RTE_IOVA_VA;
break;
}
return iova_mode;
}
実際、IOVA や IOMMU などはアドレス制御方式であり、IOMMU は IOVA により適切に機能します。いずれにせよ、上位アプリケーションから見ると物理とか仮想とかは関係なく、操作のアドレスだけを気にしていて、最終的にアドレスをどうするかということは気にする必要はありません。他の人に。銀行にお金を預けるのと同じように、お金を送金するだけで、銀行がそのお金をどう扱うかは気にしません。
4. 分析
国内のプログラマは時間があるときに下まで成長してほしい、これが今後の方向性です。上位レイヤーのアプリケーションはここ 10 年間でブームとなってきましたが、基本的には岐路に達していることがわかりました。左に行くか、右に行くか、それともそのまま進みますか?慈悲深い人は慈悲を参照します。しかし、どうやっても下にある支えからは離れてしまいますし、基礎がなければどんなに立派な建物でも風雨には耐えることができません。