SR-IOV (Single Root I/O Virtualization)
SR-IOV 하드웨어 가상화 기술: PF/VF 개념, VF 리소스 할당, 커널 API, PF/VF 드라이버 구현, IOMMU+VFIO VM 패스스루, sysfs 관리, 성능 최적화
SR-IOV(Single Root I/O Virtualization)는 PCI-SIG 표준(ECN SR-IOV 1.1)에 정의된 하드웨어 가상화 기술입니다. 하나의 물리 PCIe 디바이스(PF, Physical Function)를 여러 경량 가상 디바이스(VF, Virtual Function)로 분할하고, 각 VF에 독립적인 Config Space·BAR·MSI-X·TX/RX 큐를 부여하여 VM에 직접 패스스루(passthrough)합니다. IOMMU가 VF별 DMA 격리를 보장하므로, 에뮬레이션 오버헤드 없이 네이티브 대비 5% 이내 성능 손실로 I/O 가속이 가능합니다. 고성능 NIC(Intel X710/E810, Mellanox ConnectX), NVMe SSD, HBA, RDMA 가속기 등에 널리 적용됩니다.
PF / VF 핵심 개념
| 개념 | 설명 | 특이사항 |
|---|---|---|
| PF (Physical Function) | 전체 디바이스 기능을 가진 물리 함수. SR-IOV Capability 보유. 호스트 드라이버가 직접 관리 | VF 생성·삭제·MAC·VLAN·QoS 정책을 PF 드라이버가 제어 (mailbox) |
| VF (Virtual Function) | 경량화된 가상 함수. 독립적 PCIe Config Space(일부)·BAR·MSI-X·TX/RX 큐 보유 | Config Space 일부(전원 관리 등)는 PF가 가상화. VF 자체는 리셋·전원 제어 불가 |
| VF BAR | PF의 SR-IOV Capability에 정의된 VF 전용 MMIO BAR 영역. NumVFs × VF_BAR_size로 연속 매핑 | VF[n] BAR 기준주소 = VF BAR 기준 + n × VF_BAR_size |
| First VF Offset / VF Stride | VF의 Routing ID(BDF) 간격과 첫 VF BDF 오프셋. SR-IOV Capability 레지스터에 하드코딩 | VF[n] BDF = PF BDF + FirstVFOffset + n × VFStride |
| ARI (Alt. Routing-ID) | Function 번호를 8비트로 확장 — 한 디바이스에 최대 256개 Function 허용 | PCIe 2.1 이상, ARI 지원 루트 포트 필요. 대규모 VF(예: 64개+)에 필수 |
| IOMMU 그룹 격리 | 각 VF는 별도 IOMMU 그룹에 배치 → DMA 격리 보장. 단독 VFIO 패스스루 가능 | ACS 미지원 스위치 아래에서는 PF와 VF가 동일 그룹이 될 수 있음 — lspci로 확인 |
| Mailbox | PF ↔ VF 간 메시지 채널. VF가 MAC/VLAN 변경·링크 상태 등을 PF에 요청 | 보안 경계: VM이 임의 MAC 스푸핑·VLAN 침입 불가 (PF가 화이트리스트 관리) |
SR-IOV PCIe Extended Capability 구조
SR-IOV Extended Capability(Cap ID 0x0010)는 PCIe Extended Configuration Space(오프셋 0x100 이상)에 위치하며 PF에서만 노출됩니다. VF 수·BAR 크기·Stride 등을 하드웨어 레지스터로 기술합니다.
VF 리소스 할당 구조
SR-IOV 활성화 시 NIC의 물리 자원(TX/RX 큐, MSI-X 벡터, MMIO BAR 공간)이 VF 수에 맞게 분할됩니다. VF별로 독립적인 MMIO BAR 공간이 연속으로 매핑되어 게스트 드라이버가 직접 레지스터를 접근합니다.
커널 API
#include <linux/pci.h>
/* ── VF 활성화 / 비활성화 ── */
/* PF 드라이버에서 num_vfs개 VF 활성화. 성공 시 num_vfs 반환 */
int pci_enable_sriov(struct pci_dev *dev, int nr_virtfn);
/* 모든 VF 비활성화. VM에 할당된 VF 있으면 호출 금지 */
void pci_disable_sriov(struct pci_dev *dev);
/* ── VF 수 조회 ── */
int pci_num_vf(struct pci_dev *dev); /* 현재 활성 VF 수 */
int pci_vfs_assigned(struct pci_dev *dev); /* VFIO에 할당된 VF 수 */
int pci_sriov_get_totalvfs(struct pci_dev *dev); /* 하드웨어 최대 VF 수 */
/* ── PF ↔ VF 관계 탐색 ── */
struct pci_dev *pci_physfn(struct pci_dev *dev); /* VF → PF */
bool pci_is_virtfn(struct pci_dev *dev); /* VF 여부 확인 */
/* ── ARI 지원 확인 ── */
bool pci_ari_enabled(struct pci_bus *bus); /* 버스 ARI 활성 여부 */
/* ── VF 드라이버에서 PF 데이터 접근 패턴 ── */
struct pci_dev *physfn = pci_physfn(vf_pdev);
struct my_pf_priv *pf = pci_get_drvdata(physfn);
/* pf를 통해 mailbox, 전역 자원에 접근 */
PF 드라이버 구현
#include <linux/pci.h>
#include <linux/netdevice.h>
struct my_pf_priv {
struct pci_dev *pdev;
int num_vfs;
struct my_vf_info *vf_info; /* VF당 MAC·VLAN 설정 테이블 */
void __iomem *hw; /* PF BAR0 */
};
static int my_sriov_configure(struct pci_dev *pdev, int num_vfs)
{
struct my_pf_priv *pf = pci_get_drvdata(pdev);
int ret;
if (num_vfs == 0) {
/* VFIO에 할당된 VF가 있으면 비활성화 거부 */
if (pci_vfs_assigned(pdev))
return -EPERM;
my_hw_disable_vfs(pf);
kfree(pf->vf_info);
pf->vf_info = NULL;
pci_disable_sriov(pdev);
pf->num_vfs = 0;
return 0;
}
if (num_vfs > pci_sriov_get_totalvfs(pdev))
return -EINVAL;
/* VF당 자원 구조체 할당 (MAC 주소, VLAN, 신뢰 모드 등) */
pf->vf_info = kcalloc(num_vfs, sizeof(*pf->vf_info), GFP_KERNEL);
if (!pf->vf_info)
return -ENOMEM;
/* 하드웨어 VF 큐·인터럽트 할당 */
ret = my_hw_enable_vfs(pf, num_vfs);
if (ret) {
kfree(pf->vf_info);
return ret;
}
/* PCIe SR-IOV Capability NumVFs 레지스터 설정 & VF Config Space 노출 */
ret = pci_enable_sriov(pdev, num_vfs);
if (ret) {
my_hw_disable_vfs(pf);
kfree(pf->vf_info);
return ret;
}
pf->num_vfs = num_vfs;
dev_info(&pdev->dev, "%d VFs 활성화
", num_vfs);
return num_vfs; /* 성공 시 활성화된 VF 수 반환 */
}
/* PF 드라이버의 NDO 콜백 — VF MAC/VLAN/QoS 정책 제어 */
static int my_ndo_set_vf_mac(struct net_device *dev, int vf_id,
u8 *mac)
{
struct my_pf_priv *pf = netdev_priv(dev);
if (vf_id >= pf->num_vfs)
return -EINVAL;
ether_addr_copy(pf->vf_info[vf_id].mac, mac);
my_hw_set_vf_mac(pf, vf_id, mac); /* MMIO 레지스터에 MAC 기록 */
return 0;
}
static const struct net_device_ops my_netdev_ops = {
.ndo_set_vf_mac = my_ndo_set_vf_mac,
.ndo_set_vf_vlan = my_ndo_set_vf_vlan,
.ndo_set_vf_rate = my_ndo_set_vf_rate, /* QoS 대역폭 */
.ndo_get_vf_config = my_ndo_get_vf_config,
.ndo_set_vf_trust = my_ndo_set_vf_trust, /* 신뢰 모드 */
.ndo_set_vf_link_state = my_ndo_set_vf_link_state,
};
static struct pci_driver my_pf_driver = {
.name = "my_nic_pf",
.id_table = my_pf_ids,
.probe = my_pf_probe,
.remove = my_pf_remove,
.sriov_configure = my_sriov_configure, /* sysfs sriov_numvfs 쓰기 시 호출 */
};
코드 설명
- sriov_configure 콜백사용자가
echo N > /sys/bus/pci/devices/.../sriov_numvfs를 실행하면 커널이 이 콜백을 호출합니다.num_vfs == 0이면 VF 비활성화, 양수면 해당 수만큼 VF 생성을 요청합니다.drivers/pci/iov.c의sriov_numvfs_store()에서 호출됩니다. - pci_vfs_assigned()VFIO를 통해 VM에 할당(passthrough)된 VF가 있는지 확인합니다. 할당된 VF가 있는 상태에서 SR-IOV를 비활성화하면 VM이 크래시할 수 있으므로, 반드시 0인지 확인 후 비활성화해야 합니다.
- pci_enable_sriov()SR-IOV Extended Capability의
NumVFs레지스터를 설정하고VF Enable비트를 활성화합니다. 이후 커널이 각 VF의 Configuration Space를 탐색하여pci_dev구조체를 생성하고, 버스에 등록합니다.drivers/pci/iov.c에 구현되어 있습니다. - NDO 콜백 (ndo_set_vf_mac 등)PF 드라이버의
net_device_ops에 VF 관리 콜백을 등록하면,ip link set dev PF vf N mac XX:XX:XX:XX:XX:XX같은 명령으로 VF의 MAC 주소, VLAN, QoS, 신뢰 모드를 PF를 통해 제어할 수 있습니다. - pci_sriov_get_totalvfs()SR-IOV Capability의
TotalVFs레지스터를 읽어 하드웨어가 지원하는 최대 VF 수를 반환합니다. 요청된num_vfs가 이 값을 초과하면 거부해야 합니다.
VF 드라이버 구현
/* VF 드라이버 — VM 내부 게스트 OS 또는 호스트에서 VF 직접 제어 */
struct my_vf_priv {
struct pci_dev *pdev;
void __iomem *hw; /* VF BAR0 */
struct my_tx_ring tx_ring;
struct my_rx_ring rx_ring;
};
static int my_vf_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
struct my_vf_priv *vf;
int ret;
ret = pcim_enable_device(pdev);
ret = pcim_iomap_regions(pdev, BIT(0), "my_nic_vf");
vf = devm_kzalloc(&pdev->dev, sizeof(*vf), GFP_KERNEL);
vf->pdev = pdev;
vf->hw = pcim_iomap_table(pdev)[0]; /* VF BAR0 ioremap */
pci_set_master(pdev); /* DMA Bus Master 활성화 */
/* VF는 IOMMU가 격리 보장 — DMA 마스크 직접 설정 */
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
/* TX/RX 디스크립터 링 DMA 할당 후 하드웨어 레지스터에 물리 주소 기록 */
my_vf_setup_rings(vf);
/* MSI-X 벡터 할당 (VF Config Space에 명시된 최대 수 이내) */
ret = pci_alloc_irq_vectors(pdev, 1, MY_VF_MSIX_MAX, PCI_IRQ_MSIX);
request_irq(pci_irq_vector(pdev, 0), my_vf_tx_irq,
0, "my_vf_tx", vf);
request_irq(pci_irq_vector(pdev, 1), my_vf_rx_irq,
0, "my_vf_rx", vf);
/* PF mailbox로 MAC 주소 요청 (VF는 MAC 직접 변경 불가) */
my_vf_mailbox_send(vf, MSG_REQUEST_MAC, NULL);
pci_set_drvdata(pdev, vf);
return 0;
}
static const struct pci_device_id my_vf_ids[] = {
{ PCI_VDEVICE(INTEL, 0x154c), 0 }, /* Intel X710 VF */
{ PCI_VDEVICE(INTEL, 0x1571), 0 }, /* Intel XL710 VF */
{}
};
MODULE_DEVICE_TABLE(pci, my_vf_ids);
static struct pci_driver my_vf_driver = {
.name = "my_nic_vf",
.id_table = my_vf_ids,
.probe = my_vf_probe,
.remove = my_vf_remove,
/* VF 드라이버는 sriov_configure 없음 */
};
SR-IOV + IOMMU + VFIO → VM 패스스루 전체 스택
sysfs 인터페이스 상세
# PF 기준 경로: /sys/bus/pci/devices/0000:03:00.0/
## VF 수 조회 및 제어
cat /sys/bus/pci/devices/0000:03:00.0/sriov_totalvfs # 최대 VF 수 (RO)
cat /sys/bus/pci/devices/0000:03:00.0/sriov_numvfs # 현재 활성 VF 수
echo 4 > /sys/bus/pci/devices/0000:03:00.0/sriov_numvfs # VF 4개 활성화
echo 0 > /sys/bus/pci/devices/0000:03:00.0/sriov_numvfs # VF 전부 비활성화
## VF 활성화 후 생성된 VF 디바이스 목록
ls -l /sys/bus/pci/devices/0000:03:00.0/virtfn*
# virtfn0 → ../0000:03:00.2
# virtfn1 → ../0000:03:00.3
## VF → PF 역방향 참조
readlink /sys/bus/pci/devices/0000:03:00.2/physfn
# ../0000:03:00.0 (PF BDF)
## VF를 vfio-pci에 바인딩 (VM 패스스루 준비)
VFBDF="0000:03:00.2"
echo $VFBDF > /sys/bus/pci/devices/$VFBDF/driver/unbind 2>/dev/null || true
echo "vfio-pci" > /sys/bus/pci/devices/$VFBDF/driver_override
echo $VFBDF > /sys/bus/pci/drivers/vfio-pci/bind
## IOMMU 그룹 번호 확인
readlink /sys/bus/pci/devices/$VFBDF/iommu_group | grep -o '[0-9]*$'
## ip link로 PF에서 VF 설정 (네트워크 드라이버)
ip link set enp3s0f0 vf 0 mac 52:54:00:aa:bb:cc # VF 0 MAC 설정
ip link set enp3s0f0 vf 0 vlan 100 # VF 0 VLAN 태그
ip link set enp3s0f0 vf 0 rate 1000 # 최대 1Gbps 대역폭
ip link set enp3s0f0 vf 0 trust on # 신뢰 모드 (MAC 스푸핑 허용)
ip link set enp3s0f0 vf 0 spoofchk off # 스푸핑 검사 비활성
ip link show enp3s0f0 # VF 설정 전체 확인
SR-IOV + DPDK 고성능 패킷 처리
SR-IOV VF를 DPDK의 PMD(Poll Mode Driver)와 결합하면 커널 네트워크 스택(Network Stack)을 완전히 우회하여 라인 레이트(100Gbps+) 패킷 처리가 가능합니다. DPDK iavf PMD는 VF BAR에 직접 접근하여 TX/RX 디스크립터 링을 폴링(Polling) 방식으로 처리합니다.
# DPDK에서 SR-IOV VF 사용 예시
# 1. 대용량 페이지 설정
echo 1024 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
mount -t hugetlbfs nodev /mnt/huge
# 2. VF를 vfio-pci에 바인딩 (위 절차와 동일)
# 3. DPDK testpmd로 VF 성능 테스트
dpdk-testpmd -l 0-3 -n 4 \
-- --forward-mode=io --nb-cores=2 \
--rxq=2 --txq=2 --stats-period=1
# DPDK 지원 VF PMD 드라이버
# Intel iavf PMD: drivers/net/iavf/ (X710/E810 VF)
# Mellanox mlx5 PMD: drivers/net/mlx5/ (ConnectX VF)
# Virtio-user PMD: DPDK vhost 통합 시 SR-IOV 대안
디버깅
# SR-IOV Capability 및 VF 상태 확인
lspci -vvv -s 0000:03:00.0 | grep -A 12 "SR-IOV"
# IOVCtl: Enable+ → VF 활성화됨
# Number of VFs: 4, Total VFs: 64
# 생성된 VF 디바이스 목록
lspci | grep "Virtual Function"
# 03:00.2 Ethernet [0200]: Intel [Virtual Function]
# IOMMU 그룹 격리 확인 (VF별 별도 그룹이어야 안전)
for vf in /sys/bus/pci/devices/0000:03:00.*/iommu_group; do
echo "$(basename $(dirname $vf)): group $(readlink $vf | grep -o '[0-9]*$')"
done
# dmesg: SR-IOV 활성화 로그
dmesg | grep -i "sriov\|virtfn\|VF enable"
# pci 0000:03:00.0: 4 VFs enabled
# pci 0000:03:00.2: [8086:154c] type 00 class 0x020000
# VF 드라이버 바인딩 상태 확인
lspci -nnk -s 0000:03:00.2
# Kernel driver in use: vfio-pci ← VM 패스스루 준비 완료
# Kernel driver in use: iavf ← 호스트에서 직접 사용 중
# VF가 대규모일 때: ARI 활성화 여부 확인
lspci -vvv -s 0000:03:00.0 | grep "ARI Forwarding"
# ARI Forwarding: + ← 256개 Function 지원 (64+ VF 가능)
# VF 통계 (PF 드라이버에서 조회)
ethtool -S enp3s0f0 | grep -i "vf"
- 라이브 마이그레이션 불가 — VF는 하드웨어 상태를 가져 VFIO passthrough VM의 라이브 마이그레이션이 불가능합니다. vDPA(virtio Data Path Acceleration) 또는 virtio를 사용하면 마이그레이션 가능합니다.
- VF 비활성화 전 VM 종료 필요 —
pci_vfs_assigned()가 0이 아닐 때pci_disable_sriov()는 -EPERM을 반환합니다. - ACS 미지원 스위치 경고 — ACS 없는 PCIe 스위치 아래 VF는 P2P DMA로 인해 IOMMU 격리가 불완전할 수 있습니다.
- VF Config Space 제한 — VF는 FLR(Function Level Reset) 이외의 전원 관리·ASPM 기능이 없으며, Config Space의 일부 필드만 접근 가능합니다.
- SR-IOV vs mdev 선택 — 하드웨어가 SR-IOV를 지원하면 성능이 뛰어나지만, 라이브 마이그레이션이 필요하거나 VF 수보다 더 많은 VM을 서비스할 때는 mdev 또는 virtio가 적합합니다.
pci_enable_msix_range() 소스 분석
pci_enable_msix_range()는 드라이버가 원하는 MSI-X 벡터 수의 범위(minvec~maxvec)를 지정하면, 하드웨어가 지원하는 최대값으로 협상하여 할당합니다. 내부적으로 pci_msix_vec_count()로 하드웨어 상한을 확인한 뒤 __pci_enable_msix_range()를 호출합니다.
/* drivers/pci/msi/api.c */
int pci_enable_msix_range(struct pci_dev *dev,
struct msix_entry *entries,
int minvec, int maxvec)
{
int rc;
if (maxvec < minvec)
return -ERANGE;
if (WARN_ON_ONCE(dev->msix_enabled)) {
int nvec = pci_msix_vec_count(dev);
if (nvec < 0)
return nvec;
if (nvec < minvec)
return -ENOSPC;
}
rc = __pci_enable_msix_range(dev, entries, minvec, maxvec, NULL, 0);
if (rc < 0)
return rc;
if (rc < minvec) {
pci_free_irq_vectors(dev);
return -ENOSPC;
}
return rc; /* 실제 할당된 벡터 수 반환 */
}
EXPORT_SYMBOL(pci_enable_msix_range);
코드 설명
- maxvec < minvec범위가 역전된 경우 즉시
-ERANGE로 거부합니다. 드라이버가 잘못된 인수를 넘기는 것을 조기 차단합니다. - WARN_ON_ONCE(dev->msix_enabled)이미 MSI-X가 활성화된 상태에서 재진입 시 경고를 출력합니다. 이 경우
pci_msix_vec_count()로 하드웨어 상한을 다시 읽어minvec과 비교합니다. - __pci_enable_msix_range()실제 MSI-X 테이블·PBA를 MMIO에 매핑하고,
irq_domain을 통해 각 벡터에 Linux IRQ 번호를 할당하며, Config Space의 MSI-X Enable 비트를 설정합니다. - rc < minvec하드웨어가
minvec보다 적은 벡터만 제공할 수 있으면 이미 부분 할당된 벡터를pci_free_irq_vectors()로 해제하고-ENOSPC를 반환합니다. - return rc성공 시 실제 할당된 벡터 수(
minvec~maxvec사이)를 반환합니다. 드라이버는 이 값으로 큐 수를 결정합니다.
pci_enable_sriov() 소스 분석
pci_enable_sriov()는 PF의 SR-IOV Capability를 프로그래밍하여 지정한 수의 VF를 생성합니다. VF용 PCIe Config Space 주소를 계산하고, 각 VF를 pci_dev로 등록하여 드라이버 바인딩이 가능하도록 합니다.
/* drivers/pci/iov.c */
int pci_enable_sriov(struct pci_dev *dev, int nr_virtfn)
{
might_sleep();
if (!dev->is_physfn)
return -ENOSYS; /* VF에서는 호출 불가 */
return sriov_enable(dev, nr_virtfn);
}
EXPORT_SYMBOL_GPL(pci_enable_sriov);
static int sriov_enable(struct pci_dev *dev, int nr_virtfn)
{
int rc, i;
int nres;
u16 initial;
struct pci_dev *pdev;
struct pci_sriov *iov = dev->sriov;
u16 offset, stride, tmp;
if (!nr_virtfn)
return 0;
if (iov->num_VFs)
return -EINVAL; /* 이미 VF가 활성화 중 */
pci_read_config_word(dev, iov->pos + PCI_SRIOV_INITIAL_VF, &initial);
if (nr_virtfn > iov->total_VFs ||
(!(iov->cap & PCI_SRIOV_CAP_VFM) && nr_virtfn > initial)) {
pci_err(dev, "can't enable %d VFs (max %d)\n",
nr_virtfn, iov->total_VFs);
return -EINVAL;
}
/* VF BAR 리소스 계산 및 할당 */
rc = sriov_enable_vf_resources(dev, nr_virtfn);
if (rc)
return rc;
/* SR-IOV Control: VF Enable + VF MSE 비트 설정 */
pci_write_config_word(dev, iov->pos + PCI_SRIOV_NUM_VF, nr_virtfn);
pci_iov_set_numvfs(dev, nr_virtfn); /* SRIOV Control 레지스터 */
iov->num_VFs = nr_virtfn;
/* 각 VF에 대해 pci_dev 생성 및 등록 */
for (i = 0; i < nr_virtfn; i++) {
rc = pci_iov_add_virtfn(dev, i);
if (rc)
goto failed;
}
return 0;
failed:
sriov_disable(dev);
return rc;
}
코드 설명
- might_sleep()VF 생성 과정에서 슬리프가 발생할 수 있음을 명시합니다.
pci_enable_sriov()는 인터럽트 컨텍스트에서 호출할 수 없습니다. - !dev->is_physfnVF(
is_virtfn == 1)에서 SR-IOV를 활성화하려는 시도를 차단합니다. SR-IOV 활성화는 PF에서만 가능합니다. - iov->num_VFs이미 VF가 활성화된 상태에서 재활성화를 시도하면
-EINVAL을 반환합니다. 먼저pci_disable_sriov()를 호출해야 합니다. - PCI_SRIOV_INITIAL_VFSR-IOV Capability에서 초기 VF 수를 읽습니다.
VF Migration기능이 없는 경우nr_virtfn이 이 값을 초과할 수 없습니다. - sriov_enable_vf_resources()VF BAR 크기를 읽고(
PCI_SRIOV_BAR레지스터), VF 수만큼 곱한 크기의 주소 공간을 부모 버스 윈도우에서 할당합니다. - pci_iov_set_numvfs()SR-IOV Control 레지스터의 VF Enable 비트와 VF MSE(Memory Space Enable) 비트를 설정하여 하드웨어가 VF를 PCIe 토폴로지에 노출하도록 지시합니다.
- pci_iov_add_virtfn()각 VF에 대해 BDF(Bus/Device/Function)를 계산하고,
pci_dev를 할당하여is_virtfn = 1로 설정합니다. 이후pci_device_add()→device_add()로 sysfs 공개 및 드라이버 매칭을 수행합니다. - goto failed / sriov_disable()VF 등록 도중 실패 시 이미 등록된 VF를 모두 제거하고 Config Space를 원래 상태로 복원합니다. 원자적 트랜잭션처럼 동작합니다.
SR-IOV + Kubernetes 통합
클라우드 네이티브 환경에서 SR-IOV VF를 컨테이너(Pod)에 직접 할당하면, 커널 네트워크 스택 오버헤드 없이 네이티브에 가까운 네트워크 성능을 달성할 수 있습니다. Kubernetes에서는 SR-IOV Device Plugin과 SR-IOV CNI Plugin이 VF 자원 관리와 네트워크 인터페이스 연결을 담당합니다. Multus CNI를 통해 Pod에 여러 네트워크 인터페이스를 부착할 수 있습니다.
NetworkAttachmentDefinition은 Multus가 참조하는 추가 네트워크 정의입니다. SR-IOV CNI Plugin이 VF를 Pod의 네트워크 네임스페이스로 이동시킵니다.
# SR-IOV Network Attachment Definition (Multus)
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
name: sriov-net-a
annotations:
k8s.v1.cni.cncf.io/resourceName: intel.com/sriov_netdevice
spec:
config: '{
"type": "sriov",
"cniVersion": "0.3.1",
"vlan": 100,
"spoofchk": "on",
"trust": "off",
"ipam": {
"type": "host-local",
"subnet": "10.56.0.0/16",
"rangeStart": "10.56.1.10",
"rangeEnd": "10.56.1.250"
}
}'
# Pod에서 SR-IOV VF 자원 요청
apiVersion: v1
kind: Pod
metadata:
name: sriov-pod
annotations:
k8s.v1.cni.cncf.io/networks: sriov-net-a
spec:
containers:
- name: app
image: my-app:latest
resources:
requests:
intel.com/sriov_netdevice: "1"
limits:
intel.com/sriov_netdevice: "1"
코드 설명
- NetworkAttachmentDefinitionMultus CNI가 인식하는 커스텀 리소스입니다.
resourceName어노테이션이 SR-IOV Device Plugin이 등록한 자원 이름(intel.com/sriov_netdevice)과 매칭되어야 합니다. - sriov CNI typeSR-IOV CNI Plugin이 할당된 VF를 Pod의 네트워크 네임스페이스로 이동시킵니다. VLAN, spoofchk, trust 등의 VF 정책도 여기서 설정합니다.
- intel.com/sriov_netdeviceSR-IOV Device Plugin이 kubelet에 등록하는 Extended Resource 이름입니다. Pod이 이 자원을
requests/limits에 선언하면 kubelet이 Device Plugin을 통해 VF를 할당합니다. - k8s.v1.cni.cncf.io/networksPod 어노테이션에 추가 네트워크를 지정합니다. Multus가 이 값을 읽어 기본 CNI(eth0) 외에 SR-IOV 네트워크(net1)를 추가로 연결합니다.
vfio-pci로 바인딩한 상태에서 할당하려면 intel.com/sriov_vfio 자원 유형을 별도로 구성해야 합니다.
SR-IOV Device Plugin의 configMap에서 "drivers": ["vfio-pci"]로 설정하면 DPDK 전용 VF 풀을 분리할 수 있습니다.
Scalable IOV (SIOV)
Intel Scalable IOV(SIOV)는 SR-IOV의 한계를 극복하기 위해 설계된 차세대 I/O 가상화 기술입니다. SR-IOV가 하드웨어에 고정된 수의 VF를 생성하는 반면, SIOV는 ADI(Assignable Device Interface)라는 소프트웨어 정의 가상 디바이스를 PASID(Process Address Space ID) 기반으로 동적 생성합니다. ADI는 BAR 공간을 소비하지 않으므로 수천 개의 가상 디바이스를 유연하게 생성할 수 있습니다.
ADI와 PASID 기반 격리
SIOV에서 각 ADI는 고유한 PASID 값을 가지며, IOMMU가 PASID별 페이지 테이블(Page Table)을 관리하여 DMA 격리를 보장합니다. 디바이스가 TLP(Transaction Layer Packet)에 PASID를 포함하여 전송하면, IOMMU는 해당 PASID의 주소 공간으로 DMA를 제한합니다.
- BAR 공간 절약 — SR-IOV는 VF당 별도 BAR이 필요하지만, SIOV ADI는 공유 BAR 내의 PASID로 구분합니다
- 동적 생성/삭제 — PCIe 재열거 없이 ADI를 런타임에 생성·삭제할 수 있습니다
- 세밀한 자원 제어 — ADI 단위로 큐 수, 대역폭, 우선순위를 독립적으로 설정합니다
- 소프트웨어 정의 — 하드웨어 VF 수 제한 없이 수백~수천 개 ADI를 생성할 수 있습니다
SR-IOV vs Scalable IOV vs vDPA vs mdev 비교
| 항목 | SR-IOV | Scalable IOV | vDPA | mdev |
|---|---|---|---|---|
| 가상 디바이스 단위 | VF (PCIe Function) | ADI (PASID 기반) | virtio datapath | Mediated Device |
| 최대 인스턴스 | ~256 (하드웨어 고정) | 수천 (소프트웨어 정의) | 드라이버 의존 | 드라이버 의존 |
| BAR 공간 소비 | VF당 별도 BAR 필요 | 공유 BAR (PASID 구분) | 없음 | 없음 (에뮬레이션) |
| 생성 방식 | PCIe 재열거 필요 | 런타임 동적 생성 | 런타임 동적 생성 | 런타임 동적 생성 |
| DMA 격리 | IOMMU (BDF 기반) | IOMMU (PASID 기반) | 소프트웨어 (vhost) | 소프트웨어 (mdev) |
| 라이브 마이그레이션 | 불가 (하드웨어 상태) | 제한적 (PASID 전환) | 가능 (virtio 표준) | 벤더 의존 |
| 성능 (지연시간) | 네이티브급 | 네이티브급 | SR-IOV에 근접 | 에뮬레이션 오버헤드 |
| 커널 지원 상태 | 완전 지원 (v2.6.30+) | 초기 단계 (IDXD 등) | v5.7+ (vDPA 프레임워크) | v4.10+ (mdev 프레임워크) |
| 대표 디바이스 | NIC, NVMe, GPU | Intel IDXD, DSA | NIC (virtio-net) | GPU (NVIDIA vGPU, Intel GVT) |
drivers/dma/idxd/에서 PASID 기반의 Work Queue(WQ)를 mdev 인터페이스를 통해 VM에 할당합니다.
범용 SIOV 프레임워크는 아직 커널에 통합되지 않았으며, 디바이스별로 개별 구현되고 있습니다.
VF Configuration Space 구조
VF의 Configuration Space는 PCIe Type 0(Endpoint) 헤더 형식이지만, 대부분의 필드가 읽기 전용(RO)이거나 하드와이어(HwInit)되어 있습니다. VF의 리소스(BAR, Interrupt)는 PF의 SR-IOV Capability를 통해 간접적으로 결정되므로, VF Config Space의 직접 설정 범위가 제한됩니다.
- BAR 직접 쓰기 불가 — VF BAR 값은 PF의 VF BAR 레지스터로부터 계산되며, VF Config Space의 BAR 필드에 직접 쓸 수 없습니다
- SR-IOV Capability 없음 — VF는 SR-IOV Extended Capability를 가지지 않습니다 (중첩 가상화 불가)
- 전원 관리 제한 — VF는 D0/D3hot 상태 전환만 지원하며, ASPM과 같은 링크 전원 관리는 PF가 제어합니다
- FLR만 지원 — VF의 유일한 리셋 방식은 Function Level Reset입니다. VF FLR은 해당 VF만 리셋하고 다른 VF에 영향을 주지 않습니다
SR-IOV 성능 최적화
SR-IOV는 하드웨어 패스스루 방식으로 에뮬레이션 오버헤드를 제거하여 네이티브에 가까운 성능을 제공합니다. 다음은 주요 가상화 NIC 방식별 성능 특성 비교입니다.
가상화 NIC 방식별 성능 비교
| 항목 | SR-IOV VF | vDPA (vhost) | virtio-net (vhost-net) | 에뮬레이션 (e1000) |
|---|---|---|---|---|
| 지연시간 (RTT) | ~15 μs | ~20 μs | ~50 μs | ~200 μs |
| 처리량 (64B 패킷) | ~14.8 Mpps | ~12 Mpps | ~3 Mpps | ~0.3 Mpps |
| CPU 사용률 (10Gbps) | ~5% | ~8% | ~30% | ~90% |
| 데이터 경로 | VF → PCIe → IOMMU → Memory | VF → vDPA bus → vhost → Memory | Guest → vhost-net → tap → Host | Guest → QEMU → tap → Host |
| 라이브 마이그레이션 | 불가 | 가능 | 가능 | 가능 |
| VM Exit 빈도 | 최소 (MSI-X 직접 전달) | 낮음 | 중간 (eventfd) | 매우 높음 |
※ 위 수치는 10GbE NIC(Intel X710) 기준 대표값이며, 하드웨어·드라이버·설정에 따라 달라질 수 있습니다.
성능 튜닝 가이드
# ── VF 큐 수 조정 (멀티코어 활용) ──
# PF 드라이버가 지원하는 경우 VF당 큐 수 변경
ethtool -L enp3s2 combined 4
# ── 인터럽트 코얼레싱 최적화 ──
# 지연시간 우선: rx-usecs 낮게
ethtool -C enp3s2 rx-usecs 10 tx-usecs 10
# 처리량 우선: rx-usecs 높게 (배치 처리)
ethtool -C enp3s2 rx-usecs 100 tx-usecs 100 rx-frames 64
# ── 링 버퍼 크기 증가 (패킷 드롭 방지) ──
ethtool -G enp3s2 rx 4096 tx 4096
# ── IRQ 친화성: VF 인터럽트를 같은 NUMA 노드 CPU에 고정 ──
# NIC NUMA 노드 확인
cat /sys/bus/pci/devices/0000:03:02.0/numa_node
# 해당 NUMA 노드의 CPU 확인
lscpu | grep "NUMA node0"
# IRQ 친화성 설정 (VF의 MSI-X 벡터별)
for irq in $(grep enp3s2 /proc/interrupts | awk '{print $1}' | tr -d :); do
echo 0-3 > /proc/irq/$irq/smp_affinity_list
done
# ── ACS 확인 (VF 간 IOMMU 격리 검증) ──
lspci -vvv -s 0000:00:01.0 | grep "Access Control Services"
# ACSCtl: SrcValid+ TransBlk+ ReqRedir+ CmpltRedir+ UpstreamFwd+ EgrCtrl+ DirTrans+
# IOMMU 그룹 격리 상태 확인
for d in /sys/kernel/iommu_groups/*/devices/*; do
g=$(echo $d | cut -d/ -f5)
echo "Group $g: $(basename $d) $(lspci -nns $(basename $d) | cut -d' ' -f2-)"
done
코드 설명
- ethtool -L combinedVF의 TX/RX 큐 수를 조정합니다. CPU 코어 수에 맞추면 RSS(Receive Side Scaling)가 여러 코어로 패킷을 분산 처리합니다. PF 드라이버에서 VF당 최대 큐 수를 제한할 수 있습니다.
- ethtool -C rx-usecs인터럽트 코얼레싱(Coalescing)은 여러 패킷을 모아 하나의 인터럽트로 처리합니다. 값이 작으면 지연시간이 줄지만 인터럽트가 잦아지고, 값이 크면 처리량이 향상되지만 지연시간이 증가합니다.
- NUMA 친화성NIC과 같은 NUMA 노드의 CPU에서 패킷을 처리하면 메모리 접근 지연시간이 줄어듭니다. 다른 NUMA 노드의 CPU에서 처리하면 QPI/UPI 인터커넥트 지연이 추가됩니다.
- ACS 확인ACS(Access Control Services)는 PCIe 스위치에서 Function 간 P2P 트래픽을 제어합니다. ACS가 없으면 같은 스위치 하위의 VF 간 DMA가 IOMMU를 우회하여 격리가 깨질 수 있습니다.