首页
关于
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-03 · 从 0 写一个最小 PCIe 内核驱动:24 行 probe 一切,然后逐行升级
对应路线图:Phase 4 (Day 120-140) | 内核 v6.6 | 实验代码:code/dpu_dev/01_pci/,引用均标 文件:行号上一篇:P4-02 MSI-X | 下一篇:P5-01 DMA/IOMMU一句话定位PCIe 驱动的最小骨架不是操作寄存器,而是参与内核的"设备-驱动匹配协议":填一张 pci_device_id 表 + 一个 probe 回调,driver core 就会在设备出现时替你调它。code/dpu_dev/01_pci/pci_scan.c 全文件只有 24 行,却能 probe 整条总线上的所有设备——这是每个网卡驱动(ixgbe、mlx5)的同款起点。真实源码精读① 全文件带读:code/dpu_dev/01_pci/pci_scan.c:1-24#include <linux/module.h> #include <linux/pci.h> static int probe(struct pci_dev *pdev, const struct pci_device_id *id) { printk("pci_scan: %04x:%04x (rev %02x) BAR0=%llx IRQ=%d\n", pdev->vendor, pdev->device, pdev->revision, pci_resource_start(pdev, 0), pdev->irq); return 0; } static struct pci_device_id ids[] = { { PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, { 0, } }; MODULE_DEVICE_TABLE(pci, ids); static struct pci_driver drv = { .name = "pci_scan", .id_table = ids, .probe = probe, }; module_pci_driver(drv); MODULE_LICENSE("GPL");逐行拆:probe 回调(:4-10):签名 (struct pci_dev *, const struct pci_device_id *) 是 PCI 子系统的硬约定。这里读的 vendor/device/revision 正是 P4-01 里 pci_scan_device 从 Config Space 填进 pci_dev 的(probe.c:2507-2508);pci_resource_start(pdev, 0) 是 BAR0 物理地址(宏定义 pci.h:2107);pdev->irq 是内核分配的 IRQ 号(pci.h:420)。返回 0 = "这个设备我接管了"。ids 表(:12-16):全 PCI_ANY_ID 表示匹配所有设备——所以本模块会 probe 系统里每一个 PCI 设备,只能在学习机上玩。真正的驱动用 PCI_DEVICE(0x8086, 0x10fb) 这类精确匹配(宏在 pci.h:962)。表项结构在 mod_devicetable.h:44-50:vendor/device/subvendor/subdevice/class/class_mask 六维过滤 + driver_data 私有数据。module_pci_driver(drv)(:23):展开成 module_driver(drv, pci_register_driver, pci_unregister_driver)(pci.h:1598-1599),自动生成 init/exit,替换手写 module_init/module_exit 的样板代码。② 匹配与 probe 触发:struct pci_driver 与 driver core(pci.h:928-943)struct pci_driver { struct list_head node; const char *name; const struct pci_device_id *id_table; /* Must be non-NULL for probe to be called */ int (*probe)(struct pci_dev *dev, const struct pci_device_id *id); /* New device inserted */ void (*remove)(struct pci_dev *dev); /* Device removed (NULL if not a hot-plug capable driver) */ // ...省略 suspend/resume/shutdown/sriov/err_handler ... struct device_driver driver; // ... };id_table 注释说得很直白:没有它 probe 就不会被调用。注册入口 __pci_register_driver(pci-driver.c:1451-1467)只做一件事:填 drv->driver.bus = &pci_bus_type 然后 driver_register()——后面就是通用设备模型的 match → probe 机器了。设备侧由 pci_device_probe(pci-driver.c:444-467)接棒:static int pci_device_probe(struct device *dev) { int error; struct pci_dev *pci_dev = to_pci_dev(dev); struct pci_driver *drv = to_pci_driver(dev->driver); // ... pci_assign_irq(pci_dev); error = pcibios_alloc_irq(pci_dev); // ... pci_dev_get(pci_dev); error = __pci_device_probe(drv, pci_dev); // ... }注意顺序:先 pci_assign_irq 分配 IRQ,再进驱动的 probe——所以 probe 里 pdev->irq 一定已经有值。③ remove 与卸载:内核兜底(pci-driver.c:469-510 节选)static void pci_device_remove(struct device *dev) { struct pci_dev *pci_dev = to_pci_dev(dev); struct pci_driver *drv = pci_dev->driver; if (drv->remove) { // ... runtime PM barrier ... drv->remove(pci_dev); // ... } pcibios_free_irq(pci_dev); pci_dev->driver = NULL; // ... }drv->remove 是驱动自己的清理窗口(放走 IRQ、unmap、还资源);窗口之外的事内核兜底:pcibios_free_irq 收尾 IRQ(:486)、解绑 driver = NULL(:487)。rmmod 会经 pci_unregister_driver(pci-driver.c:1480)对每个被该驱动接管的设备各调一次 remove。④ 升级路线:把"只读"变成"拥有"pci_scan.c 只读不占,等于借设备看了一眼。要真正"拥有"一个 BAR(比如网卡的 MMIO 寄存器区),probe 里按序做四件事(API 均已核实存在):步骤API位置作用1pci_enable_device(pdev)pci.c:2191打开 IO/MEM decode,唤醒设备2pci_request_regions(pdev, name)pci.c:4273声明 resource 所有权(/proc/iomem 可见)3pci_ioremap_bar(pdev, bar)pci.c:252映射 BAR 到内核虚拟地址4request_irq(pci_irq_vector(pdev, 0), handler, ...)interrupt.h:165挂中断(配合 P4-02 的 MSI-X)remove 里严格逆序:free_irq(interrupt.h:196)→ iounmap → pci_release_regions(pci.c:4254)→ pci_disable_device(pci.c:2379)。🐧 内核机制对照:驱动协议 vs 用户态"野路子"环节内核驱动(本模块)DPDK 用户态(bus/pci)设备发现driver core 匹配 id_table → probe自己扫 /sys/bus/pci/devices/"接管"动作probe 返回 0,设备归属本驱动bind 到 vfio-pci/uio 驱动后拿 fdBAR 访问ioremap → 内核虚拟地址mmap,pci_common_uio.c:178-179中断request_irq + 上半部/NAPI轮询或 epoll 中断线程资源冲突防护pci_request_regions 互斥依赖内核驱动让位(bound to VFIO)DPDK 不是"没有驱动协议",而是把协议放进了用户态:内核里那个 vfio-pci 驱动替它完成了 probe/接管,用户态只负责 mmap 和轮询。面试 3 问probe 什么时候被调用? → 设备注册或驱动注册后 driver core 完成 match 即调;前提是 id_table 非 NULL(pci.h:931 注释);入口 pci_device_probe(pci-driver.c:444)先 pci_assign_irq 再调驱动的 probe(pci-driver.c:453-460)。PCI_ANY_ID 表项的风险? → 匹配所有设备(pci_scan.c:12-13),probe 返回 0 会接管每一个设备(pci_scan.c:9),真实驱动必须用 PCI_DEVICE 精确匹配(pci.h:962-964)。remove 里不释放资源会怎样? → IRQ 方面内核会兜底 pcibios_free_irq(pci-driver.c:486),但 ioremap 的地址和 request_regions 占用的 resource 泄漏,再加载驱动会 EBUSY——所以 remove 必须逆序归还(见上表)。🔧 动手实验cd code/dpu_dev/01_pci make # Makefile:1-2,obj-m += pci_scan.o sudo insmod pci_scan.ko dmesg | tail -30 # 每个 PCI 设备一行:vendor:device rev BAR0 IRQ sudo rmmod pci_scan升级练习(在实验机上,不要在主力机上):给 probe 加 pci_enable_device + pci_request_regions,验证 /proc/iomem 里出现 pci_scan 名字段;再试 pci_ioremap_bar 读 4 字节网卡寄存器与 lspci -xxxx 对拍。数据落点 perf-data/P4/03-pci-scan-dmesg.txt(dmesg 原文 + iomem 段 + 升级版对比)。下一篇:P5-01 · DMA/IOVA/IOMMU:设备怎么看到内存
2026年08月18日
0 阅读
0 评论
0 点赞
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 点赞
第 1/1 页 · 本页 3 条 · 共 3 条