首页
关于
Search
1
PacketForge 博客部署文档
16 阅读
2
深入 SONiC 与 SAI:从命令行到芯片的完整数据之旅
14 阅读
3
DPU 与 CPO:AI 时代的网络基础设施革命
11 阅读
4
Broadcom芯片
10 阅读
5
DPDK高性能网络
10 阅读
默认分类
交换芯片
高性能网络
AI算力网络
WebSocket高性能项目
DPDK 成长路线
①性能工程基础
②DPDK 源码深读
③用户态协议栈
④软件交换与 SONiC
⑤PCIe、虚拟化与 DPU
⑥面试与复盘
登录
Search
标签搜索
DPDK→DPU 学习路线
P0
P1
P2
P3
P5
PCIe
P4
P6
libmtcp
NUMA
mTCP
OVS
SONiC
SAI
DPU
perf
gdb
EAL
mempool
PacketForge
累计撰写
53
篇文章
累计收到
3
条评论
首页
栏目
页面
关于
最新文章
热门文章
评论最多
点赞最多
2026-08-18
P4-02 · MSI-X 中断机制:Table 在哪、怎么触发,一个内存写就够了
对应路线图:Phase 4 (Day 120-140) | 内核 v6.6 | 代码出处 source/linux-stable/drivers/pci/msi/ | 上一篇:P4-01 Config Space 与 BAR | 下一篇:P4-03 最小 PCIe 驱动一句话定位MSI-X 把"中断线"变成了"内存写事务":设备想打断 CPU 时,往一个特殊地址写一个 16 位数据,根复合体(RC)把它翻译成中断投递。每个向量在 MSI-X Table 里有一张 16 字节的表项,存着自己的"地址+数据"。所以问题拆成两个:Table 在哪(Capability 链 → BAR 内偏移),谁写表项(内核用 ioremap 后的 MMIO 直接写)。真实源码精读① 表在哪:从 Capability 链到 BAR 内偏移(msi.c:561-580)static void __iomem *msix_map_region(struct pci_dev *dev, unsigned int nr_entries) { resource_size_t phys_addr; u32 table_offset; u8 bir; pci_read_config_dword(dev, dev->msix_cap + PCI_MSIX_TABLE, &table_offset); bir = (u8)(table_offset & PCI_MSIX_TABLE_BIR); // ...省略(pci_resource_flags 检查,:572-574) table_offset &= PCI_MSIX_TABLE_OFFSET; phys_addr = pci_resource_start(dev, bir) + table_offset; return ioremap(phys_addr, nr_entries * PCI_MSIX_ENTRY_SIZE); }找 Capability:内核沿 Capability 链表(起点 PCI_CAPABILITY_LIST,pci_regs.h:121)找到 ID 为 PCI_CAP_ID_MSIX = 0x11(pci_regs.h:226)的能力结构,缓存偏移到 dev->msix_cap(pci.h:359);读 Table dword:PCI_MSIX_TABLE(能力内偏移 4,pci_regs.h:327)低 3 位是 BIR(PCI_MSIX_TABLE_BIR,pci_regs.h:328),高 29 位是 BAR 内偏移(PCI_MSIX_TABLE_OFFSET,pci_regs.h:329);BAR 基址 + 偏移 → ioremap:pci_resource_start(dev, bir) 拿到 P4-01 里解析好的 BAR 物理地址,加偏移直接映射。MSI-X 表项数与 PCI_MSIX_FLAGS(pci_regs.h:323)里的 QSIZE 相关:msi.h:6 定义 msix_table_size(flags) = (flags & PCI_MSIX_FLAGS_QSIZE) + 1——QSIZE 存的是"个数减 1"。② 表项布局与触发:16 字节一张"名片"(pci_regs.h:336-342)/* MSI-X Table entry format (in memory mapped by a BAR) */ #define PCI_MSIX_ENTRY_SIZE 16 #define PCI_MSIX_ENTRY_LOWER_ADDR 0x0 /* Message Address */ #define PCI_MSIX_ENTRY_UPPER_ADDR 0x4 /* Message Upper Address */ #define PCI_MSIX_ENTRY_DATA 0x8 /* Message Data */ #define PCI_MSIX_ENTRY_VECTOR_CTRL 0xc /* Vector Control */ #define PCI_MSIX_ENTRY_CTRL_MASKBIT 0x00000001中断触发不需要任何"电平拉低":设备做一次 Memory Write TLP,写地址 = 表项里的 address、数据 = data,RC/IOMMU 拦截后路由到本地 APIC/ITS,最终给 CPU 一个向量号。INTx 是物理线共享,MSI-X 是每个向量独享一张名片——这就是多队列网卡每队列一个中断还互不打扰的原因。③ 内核怎么写表项:先 mask 再改(msi.c:205-227,pci_write_msg_msix 节选) /* * The specification mandates that the entry is masked * when the message is modified: * * "If software changes the Address or Data value of an * entry while the entry is unmasked, the result is * undefined." */ if (unmasked) pci_msix_write_vector_ctrl(desc, ctrl | PCI_MSIX_ENTRY_CTRL_MASKBIT); writel(msg->address_lo, base + PCI_MSIX_ENTRY_LOWER_ADDR); writel(msg->address_hi, base + PCI_MSIX_ENTRY_UPPER_ADDR); writel(msg->data, base + PCI_MSIX_ENTRY_DATA);写完 address/data 再解除 mask,并用一次读强制写可见(:231-232)。消息本身来自 msi_desc.msg(msi.h:193),由中断子系统(x86 上多为 irq_matrix / MSI domain)分配。④ 使能全流程:mask 一切,再逐步放开(msi.c:712-766,msix_capability_init 节选) /* * Some devices require MSI-X to be enabled before the MSI-X * registers can be accessed. Mask all the vectors to prevent * interrupts coming in before they're fully set up. */ pci_msix_clear_and_set_ctrl(dev, 0, PCI_MSIX_FLAGS_MASKALL | PCI_MSIX_FLAGS_ENABLE); // ...省略(msix_enabled=1、读 control、msix_table_size) dev->msix_base = msix_map_region(dev, tsize); // ... /* Disable INTX */ pci_intx_for_msi(dev, 0); // ...省略(Marvell 设备 quirk 注释) msix_mask_all(dev->msix_base, tsize); pci_msix_clear_and_set_ctrl(dev, PCI_MSIX_FLAGS_MASKALL, 0);顺序是防御性的:先置 MASKALL+ENABLE(防止设置期间漏进中断)→ map Table → 分配描述符 → 关 INTx(避免与 MSI-X 并存导致双份中断)→ mask 全部表项 → 解除 MASKALL。全部搞定后驱动才逐个 unmask 自己的向量。⑤ 驱动侧入口:MSI-X 优先的 fallback 链(api.c:254-298) if (flags & PCI_IRQ_MSIX) { nvecs = __pci_enable_msix_range(dev, NULL, min_vecs, max_vecs, affd, flags); if (nvecs > 0) return nvecs; } if (flags & PCI_IRQ_MSI) { nvecs = __pci_enable_msi_range(dev, min_vecs, max_vecs, affd); // ... } /* use legacy IRQ if allowed */ if (flags & PCI_IRQ_LEGACY) { if (min_vecs == 1 && dev->irq) { // ... pci_intx(dev, 1); return 1; } }驱动传 PCI_IRQ_MSIX | PCI_IRQ_MSI | PCI_IRQ_LEGACY(pci.h:1076-1078)时,有多少本事用多少:MSI-X 不够换 MSI,再不行退回共享的 INTx。成功之后用 pci_irq_vector(dev, nr)(api.c:313)把"设备侧向量号"翻译成 Linux IRQ 号传给 request_irq。🐧 内核机制对照:INTx vs MSI vs MSI-X维度INTx(PCI_INTERRUPT_PIN)MSIMSI-X载体物理线 INTA#-INTD#,多设备共享内存写 TLP内存写 TLP向量数1(共享)1/2/4/8/16/32最多 2048(QSIZE+1)每向量独立地址/数据无共享一份每表项一份内核路径传统 irq domainMSI domain(msi.c:270-279)Table ioremap(msi.c:579)亲和性由 APIC 路由每向量可绑核每向量可绑核对 DPDK 的意义:多队列网卡每队列一个 MSI-X 向量,是"每队列独立中断源"的硬件基础;PMD 不想要中断时把 mask 位一置即可。面试 3 问MSI-X Table 在物理内存的哪个位置? → Capability 链找到 PCI_CAP_ID_MSIX(pci_regs.h:226),读 PCI_MSIX_TABLE dword(pci_regs.h:327)拆出 BIR(pci_regs.h:328)与偏移(pci_regs.h:329),BAR 基址加偏移 ioremap(msi.c:569-579)。为什么改表项前必须 mask? → PCI 规范原文:unmasked 状态下改 Address/Data 结果 undefined,内核在 msi.c:213-222 引用并遵守。QSIZE 为什么加 1? → QSIZE 编码的是"表项数 - 1",msix_table_size 统一加 1(msi/msi.h:6),读表项数走 pci_msix_vec_count(api.c:71-80)。🔧 动手实验lspci -vvv -s 01:00.0 | grep -A5 MSI-X # 记下 "Capabilities: [70] MSI-X" 的能力偏移 sudo lspci -xxxx -s 01:00.0 | grep -A4 '^70:' # 手工解析 Table/BIR dword sudo setpci -s 01:00.0 0x72.L # Message Control: QSIZE / Enable / MaskAll 位对照任务:把 0x72.L 读出的 QSIZE 与 lspci -v 的 "Count=xx" 互验;流量打满时观察 /proc/interrupts 各队列分布是否均匀(RSS 生效则各队列计数接近)。数据落点 perf-data/P4/02-msix.txt。下一篇:P4-03 · 从 0 写一个最小 PCIe 内核驱动
2026年08月18日
0 阅读
0 评论
0 点赞
2026-08-18
P4-01 · PCIe Configuration Space 与 BAR 解码:写全 1 读回来,才知道设备多大
对应路线图:Phase 4 (Day 120-140) | 内核 v6.6 | 代码出处:source/linux-stable/drivers/pci/,引用均标 文件:行号上一篇:P3-06 host_if | 下一篇:P4-02 MSI-X一句话定位Config Space 是设备与软件之间的"注册表":软件通过它知道设备是谁(Vendor/Device ID)、有什么能力(Capability 链)、占多大地址(BAR)。而 BAR 解码的玄机在于——BAR 寄存器同时承担"地址寄存器"和"尺寸寄存器"两个身份,内核用"写全 1 读回"让它现出原形。真实源码精读① 64 字节头部:偏移定义在 include/uapi/linux/pci_regs.h:38-125#define PCI_VENDOR_ID 0x00 /* 16 bits */ #define PCI_DEVICE_ID 0x02 /* 16 bits */ #define PCI_COMMAND 0x04 /* 16 bits */ // ...省略(STATUS 0x06、REVISION_ID 0x08、CLASS_DEVICE 0x0a ...) #define PCI_BASE_ADDRESS_0 0x10 /* 32 bits */ // ...省略(BASE_ADDRESS_1..5 依次 0x14/0x18/0x1c/0x20/0x24,pci_regs.h:96-100) #define PCI_CAPABILITY_LIST 0x34 /* Offset of first capability list entry */ #define PCI_INTERRUPT_LINE 0x3c /* 8 bits */ #define PCI_INTERRUPT_PIN 0x3d /* 8 bits */BAR 寄存器的位布局(pci_regs.h:101-110):bit0 区分空间(1=I/O, 0=Memory)、bit1-2 编码类型(PCI_BASE_ADDRESS_MEM_TYPE_64 = 0x04 表示 64 位 BAR 占两个寄存器)、bit3 是否 prefetchable,其余位才是地址。② 枚举第一步:读 Vendor/Device ID 判断设备是否存在(probe.c:2494-2517)static struct pci_dev *pci_scan_device(struct pci_bus *bus, int devfn) { struct pci_dev *dev; u32 l; if (!pci_bus_read_dev_vendor_id(bus, devfn, &l, 60*1000)) return NULL; dev = pci_alloc_dev(bus); if (!dev) return NULL; dev->devfn = devfn; dev->vendor = l & 0xffff; dev->device = (l >> 16) & 0xffff; if (pci_setup_device(dev)) { // ... return NULL; } // ... }一次 dword 读偏移 0x00 就完成"设备在不在"判断:全 0xffffffff 表示该 devfn 悬空。存在则 pci_setup_device(probe.c:1867)继续读 header type、class 等并解析 BAR。③ BAR 尺寸探测:写全 1 读回(probe.c:176-247,__pci_read_base 节选) mask = type ? PCI_ROM_ADDRESS_MASK : ~0; // ... pci_read_config_dword(dev, pos, &l); pci_write_config_dword(dev, pos, l | mask); pci_read_config_dword(dev, pos, &sz); pci_write_config_dword(dev, pos, l); /* * All bits set in sz means the device isn't working properly. * If the BAR isn't implemented, all bits must be 0. If it's a * memory BAR or a ROM, bit 0 must be clear; if it's an io BAR, bit * 1 must be clear. */ if (PCI_POSSIBLE_ERROR(sz)) sz = 0; // ... if (res->flags & IORESOURCE_MEM_64) { pci_read_config_dword(dev, pos + 4, &l); pci_write_config_dword(dev, pos + 4, ~0); pci_read_config_dword(dev, pos + 4, &sz); pci_write_config_dword(dev, pos + 4, l); // ... }四步协议:读原值 → 写全 1 → 读回 → 还原。读回值中低 N 位被硬件"钉死"为 0,N 就是尺寸的对数——设备应答 0xfff00000 说明它是 1MB 的 BAR。注意写全 1 前内核先关掉 decode(probe.c:189-193),防止探测期间把配置写当成正常访问。64 位 BAR 占两个寄存器,第二个寄存器在 pos + 4 重复一遍(probe.c:239-243)。④ 树形枚举:pci_scan_bus → pci_scan_slot → pci_device_addpci_scan_bus(probe.c:3308)挂上 ioport/iomem/busn 三类资源后创建根总线,调 pci_scan_child_bus 深度扫描。pci_scan_slot(probe.c:2745-2770)按 function 递增探测:fn 0 不存在且不在 hypervisor 透传环境就 break——Fn 0 是一个 slot 的"存在性锚点"。每个设备最终进 pci_device_add(probe.c:2609),挂到 bus->devices 链表(:2642)后由 driver core 触发匹配和 probe。🐧 内核机制对照:同一份 BAR 信息,两种消费方式环节内核(drivers/pci/)DPDK(drivers/bus/pci/)枚举入口pci_scan_bus probe.c:3308用户态读 /sys/bus/pci/devices/存在性判断读 offset 0x00 dword,probe.c:2499sysfs vendor/device 文件BAR 尺寸写全 1 读回,probe.c:198-201直接读 sysfs resource 文件BAR 映射ioremap(内核态地址)mmap,pci_common_uio.c:178-179空 BAR 跳过尺寸 0 则 fail,probe.c:253-254phys_addr == 0 continue,pci_common_uio.c:120-121本质区别:内核替所有驱动把 BAR 解析好、放进 pci_dev.resource[](pci.h:421);DPDK 在用户态只能拿到已分配好的物理地址,用 mmap 直接映射设备寄存器——这也是 DPDK 不写内核驱动就能操作网卡的前提。面试 3 问内核怎么测 BAR 尺寸? → 写全 1 读回(probe.c:198-201),读回值取反加一即尺寸;写之前先关 decode 防误访问(probe.c:189-193);64 位 BAR 在 pos+4 再做一遍(probe.c:239-243)。BAR 低 4 位为什么不是地址? → bit0 空间类型(pci_regs.h:101)、bit1-2 类型编码含 64 位标志(pci_regs.h:104-107)、bit3 prefetch(pci_regs.h:108),掩码 PCI_BASE_ADDRESS_MEM_MASK = ~0x0f(pci_regs.h:109)。扫描时如何避免误判"幽灵设备"? → 悬空 devfn 读回 0xffffffff,pci_scan_device 直接返回 NULL(probe.c:2499-2500);读回全 1 也用于识别坏设备(probe.c:203-210 注释)。🔧 动手实验用真实 NIC(如 ixgbe 网卡)做一次"手写 __pci_read_base":lspci -nn -v -s 01:00.0 # 记录 BAR0 地址与尺寸(如 64-bit, 4MB) sudo lspci -xxxx -s 01:00.0 | head # 前 64 字节逐字节对表解码 sudo setpci -s 01:00.0 0x10.L # 读 BAR0 原值 sudo setpci -s 01:00.0 0x10.L=0xffffffff sudo setpci -s 01:00.0 0x10.L # 读回:低 N 位为 0,验证尺寸 sudo setpci -s 01:00.0 0x10.L=<原值> # 必须还原!不还原设备可能失效同时 insmod code/dpu_dev/01_pci/pci_scan.ko 看内核侧 pci_resource_start(pdev, 0) 给出的 BAR0 值是否与 lspci 一致。数据落点 perf-data/P4/01-config-space.txt(lspci 原文 + 手工解码过程 + setpci 三次读数)。下一篇:P4-02 · MSI-X 中断机制:Table 在哪、怎么触发
2026年08月18日
0 阅读
0 评论
0 点赞
2026-08-18
P0-03 · PCIe 拓扑初探:lspci 里藏了什么
对应路线图:Phase 0 (Day 1-21) | 内核 v6.6 | 上一篇:P0-02 NUMA | 下一篇:P0-04 无锁编程一句话定位lspci -t -v 打出来的那棵树,是内核 pci_scan_bus 一行行代码扫出来的。看懂这三层枚举递归,就明白 BAR 是什么、网卡怎么 DMA 到 hugepage、以及 DPDK 为什么要把网卡从内核手里"抢"过来。真实源码精读:内核枚举三层递归第一层:入口(drivers/pci/probe.c):struct pci_bus *pci_scan_bus(int bus, struct pci_ops *ops, void *sysdata) // probe.c:3308 { LIST_HEAD(resources); struct pci_bus *b; pci_add_resource(&resources, &ioport_resource); pci_add_resource(&resources, &iomem_resource); pci_add_resource(&resources, &busn_resource); // probe.c:3314-3316 b = pci_create_root_bus(NULL, bus, ops, sysdata, &resources); // probe.c:3317 if (b) { pci_scan_child_bus(b); // probe.c:3319 } // ... }三种资源(IO 端口、内存映射、总线号)挂到 root bus 上——lspci 里每个设备的 Memory 窗口就是从 iomem_resource 里切出来的。第二层:扫子总线(probe.c:2961): /* Go find them, Rover! */ for (devfn = 0; devfn < 256; devfn += 8) pci_scan_slot(bus, devfn); // probe.c:2972-2973PCI 总线最多 32 个 slot × 8 function,devfn 就是 (slot<<3)|function 的打包。第三层:扫 slot 内的 function(probe.c:2745): do { dev = pci_scan_single_device(bus, devfn + fn); // probe.c:2754 if (dev) { if (!pci_dev_is_added(dev)) nr++; if (fn > 0) dev->multifunction = 1; // probe.c:2758-2759 } else if (fn == 0) { /* * Function 0 is required unless we are running on * a hypervisor that passes through individual PCI * functions. */ if (!hypervisor_isolated_pci_functions()) break; // probe.c:2760-2767 } fn = next_fn(bus, dev, fn); } while (fn >= 0); // probe.c:2769-2770pci_scan_single_device(probe.c:2657)读 config space 的 vendor/device ID,识别出设备并建 pci_dev;发现是 bridge 就递归进下一层总线(子总线号继续涨)——这就是 lspci -t 树的由来。实验怎么跑(README 03-pcie:104-120)lspci -t -v # 树形拓扑 lspci -vvv -s 03:00.0 | grep -E "Width|Speed" # 链路协商带宽(LnkCap/LnkSta) cat /proc/iomem | grep -A 2 "vmxnet3\|e1000" # BAR 在物理地址空间的落点 dmesg | grep -i DMAR # IOMMU 重映射 cat /proc/interrupts | grep -E "CPU|vmxnet3|e1000|mlx" # MSI-X 向量分布看点:① 你的网卡插在哪个 bridge 下面、链路是 Gen3 x8 还是 Gen4 x16——带宽上限 = 线速上限;② BAR0 是 MMIO 寄存器窗口(CPU 写 Doorbell = 给网卡下命令),BAR 区域是 DMA 目标内存(README 03-pcie:44-54 的图);③ 中断向量列告诉你包进来时哪几个 CPU 被打断——DPDK 直接把这个机制换成轮询(README 03-pcie:60-73)。🐧 内核对照DPDK内核关键差异rte_bus_scan 枚举 PCI 设备pci_scan_bus → pci_scan_child_bus(probe.c:3308/2961)同一拓扑,DPDK 只关心"能被我 PMD 驱动的"PMD 写 BAR MMIO 寄存器内核驱动 io_remap BAR都是内存映射,DPDK 省掉内核层轮询 rte_eth_rx_burstMSI-X 中断 → NAPI 软中断README 03-pcie:60-73:中断适合 <1Mpps,轮询适合 100G收包 1 次 DMA + 0 次拷贝1 次 DMA + 3 次拷贝 + 上下文切换README 03-pcie:13-25 的账本面试 3 问内核怎么枚举出整棵 PCI 树? → pci_scan_bus 挂三种资源建 root bus(probe.c:3314-3317),pci_scan_child_bus_extend 以 devfn 步长 8 扫 256 个位置(probe.c:2972-2973),pci_scan_slot 内层循环逐个 function、fn==0 不存在就 break(probe.c:2753-2770),遇到 bridge 递归。BAR 是什么、两种典型用途? → BAR 把设备寄存器/DMA 空间映射进 CPU 物理地址空间。BAR0 类 = MMIO 寄存器(Doorbell);BAR2 类 = DMA 目标(hugepage),见 README 03-pcie:44-54。为什么 DPDK 用轮询而内核用中断? → 中断有上下文切换与不可预测延迟,100G=148.8Mpps 时中断风暴拖垮 CPU;轮询让 lcore 100% 占满换来了 <1μs 的可预测延迟(README 03-pcie:60-73)。代价是耗电——这正是 DPU 上"混合模式"的由来。数据落点perf-data/P0/03-pcie/:lspci-tree.txt、lspci-vvv-nic.txt(LnkCap/LnkSta)、iomem-bar.txt、interrupts.txt。
2026年08月18日
1 阅读
0 评论
0 点赞
第 1/1 页 · 本页 3 条 · 共 3 条