尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Linux PCI驱动框架解析:从设备匹配到probe/remove的完整生命周期

发布时间:2026/9/26 10:25:39

资讯中心
01
ARTICLE

Linux PCI驱动框架解析:从设备匹配到probe/remove的完整生命周期

Linux PCI驱动框架解析:从设备匹配到probe/remove的完整生命周期
1. 为什么搞驱动要先啃PCI这块硬骨头做Linux驱动开发的朋友早晚会撞上PCI。不管你是写网卡驱动、显卡驱动、NVMe硬盘驱动还是各类采集卡、加速卡、FPGA板卡的驱动底层几乎都是PCI或PCIe接口。说白了PCI就是CPU与外部高速设备之间最通用的一条高速公路而PCI驱动框架就是Linux内核为这条路修的一套标准收费站体系。我第一次接触PCI驱动的时候手头是一个国产PCIe采集卡芯片手册几百页读得头皮发麻。后来真正动手才发现芯片内部逻辑再复杂接入Linux内核时反而有一套固定的套路可走声明一个pci_driver结构体填上设备ID表实现probe和remove回调再补上必要的资源初始化驱动就立起来了。真正折磨人的不是框架本身而是框架背后那些文档里不会明说的匹配规则、资源分配陷阱、DMA掩码时机、MSI中断申请顺序。这篇文章是Linux PCI驱动框架分析系列的开篇目标是把整套框架的骨架和运行机制讲透——设备是怎么被找到的、probe是怎么被调起来的、remove是怎么被触发的、资源是怎么挂到设备上的。适合刚接触内核驱动、被一堆结构体绕晕的入门者也适合写过驱动但没认真梳理过框架逻辑的老手。后面的系列文章会继续深入中断处理、DMA映射、BAR空间操作、电源管理等具体环节。2. Linux PCI子系统的顶层设计总线、设备与驱动如何被串起来2.1 从PCI枚举到Linux设备模型两条线索如何交汇先聊一个最基础也最关键的问题Linux是怎么知道系统里插了哪些PCI设备的这个活儿不归驱动开发人员管而是由固件和内核的PCI子系统共同完成的。传统的x86平台上BIOS/UEFI在开机阶段会做PCI枚举把每个PCI总线上的设备号(BDF)、厂商ID、设备ID、BAR空间大小等信息收集起来形成一张设备列表交给内核。而很多嵌入式平台比如ARM、RISC-V没有标准BIOS靠的是设备树(DT)或ACPI描述PCI控制器和设备信息。无论哪种来源最终内核的PCI子系统都会扫描这些信息为每一个物理存在的PCI设备创建一个struct pci_dev对象。有了pci_dev剩下的事就交给设备模型来串了。Linux内核从2.6时代开始引入了统一设备驱动模型kobject/ktype/kset那一套核心思想就一句话设备和驱动各自注册由总线上的match逻辑负责牵线搭桥。PCI子系统在Linux中就是一个典型的总线bus_type名叫pci_bus_type。它干的活包括管理所有PCI设备挂在bus的device链表上管理所有PCI驱动挂在bus的driver链表上提供match方法判断一个设备该绑到哪个驱动上提供probe、remove、shutdown等方法在匹配成功后通知对应的驱动所以从驱动开发者的视角看你写的pci_driver结构体本质上就是向pci_bus_type登记了一个相亲对象PCI设备则是待相亲的候选人。系统每发现一个新PCI设备都会走一遍match - 配对成功 - probe - 开始干活的流程。这个模型的精妙之处在于驱动代码完全不需要关心设备是怎么被发现的——你只管声明我支持哪些设备剩下的交给总线。2.2 PCI设备挂载的层次结构Bus、Slot、Function与拓扑理解PCI设备树必须先理解三个概念总路线Bus、设备号Device/Slot、功能号Function。三者组合起来就是常说的BDF地址比如0000:03:00.0意思是总线3、设备0、功能0。现代PCIe拓扑里一个PCIe Root Port就是一条独立的总线往下挂的Endpoint设备通常是单功能设备占据Dev0如果设备是多功能的比如一个网卡芯片同时提供管理网口和数据网口就会出现03:00.0、03:00.1这样的多个function。再往下如果Endpoint本身是PCIe Switch或PCI桥bridge它还能扩展出次级总线形成一棵完整的设备树。在内核里这棵树通过struct pci_bus的parent指针和children链表组织起来。每个pci_bus上挂着它的设备列表devices链表。驱动开发中你一般不需要手动遍历这棵树但理解它有助于解决两类实际问题一是设备无法被发现时的排查思路BDF对不对、总线号冲突、桥设备没使能等二是中断路由和DMA拓扑的判断设备在哪条总线上、上游桥的bus number影响ACS和IOMMU分组等。2.3 pci_bus_type的match逻辑pci_match_id的工作过程接下来深入到真正牵线搭桥的代码里。当内核注册一个PCI设备或一个PCI驱动时bus_type核心会调用pci_bus_match()它内部最终落到pci_match_id()。这个函数做的事情并不神秘获取设备的vendor ID和device ID必要时还有class、subvendor、subdevice等。遍历驱动声明的pci_device_id表。只要有一项匹配成功即认为驱动支持该设备。理解这个匹配过程的关键在于pci_device_id各字段的意义。很多人只知道填vendor和device忽略了class_mask、subvendor等字段的用途这在某些场景下会出大问题。比如一个设备厂商的同一颗芯片被做成多个子型号device ID相同但subsystem ID不同这时你就得通过subvendor/subdevice来区分并匹配不同的驱动逻辑。另外PCI设备的class code也是匹配维度之一。class code分为base class、sub class和programming interface三级分别描述设备大类型存储、网络、显示等、子类型和具体编程接口。当你希望一个驱动能接管某个大类下所有设备典型的如通用USB控制器驱动、通用VGA驱动可以用class匹配而不必逐个列举vendor/device ID。实际项目里我见过有人把class和vendor混在一起填结果驱动被绑定到完全不相干的设备上这是非常常见的低级事故。3. 核心数据结构拆解pci_driver、pci_device_id 与 pci_dev3.1 pci_driver驱动生命周期的总控台任何一个PCI驱动的起点都是一个struct pci_driver实例。顶多一两百行代码就定义了驱动的名字、设备ID表、probe函数、remove函数、电源管理回调等等。拿一个典型的网络控制器驱动来示意static struct pci_driver myeth_driver { .name myeth, .id_table myeth_pci_ids, .probe myeth_probe, .remove myeth_remove, .shutdown myeth_shutdown, .driver.pm myeth_pm_ops, };name字段会出现在/sys/bus/pci/drivers/目录下也是驱动在日志输出里最常见的标识。命名最好与设备型号或芯片名强关联否则日后调试时lsmod和dmesg对不上非常痛苦。id_table就是前面说的PCI设备ID表后面细讲。probe是驱动的主入口设备匹配成功后第一个被调用的函数。remove是设备被拔掉或驱动被卸载时的清理函数。shutdown在系统关机或重启时调用通常需要把设备置为安静状态比如停止DMA、屏蔽中断。driver.pm是电源管理回调组包含suspend/resume等函数现在很多驱动也选择不实现pm回调让设备的默认行为兜底。还有个不太起眼但很实用的字段err_handler用于PCI高级错误报告AER中的错误恢复回调。如果你的设备是要用在服务器上AER是跑不掉的即使驱动不实现err_handler发生AER错误时内核也会有默认处理但设备的恢复速度和状态恢复质量就差很多。3.2 pci_device_id驱动与设备之间的身份证对照表pci_device_id结构体各字段虽然不长但每一个都可能成为踩坑点。看一眼定义struct pci_device_id { __u32 vendor, device; // 厂商ID和设备ID __u32 subvendor, subdevice; // 子系统厂商ID和设备ID __u32 class, class_mask; // 设备类别及其掩码 unsigned long driver_data; // 驱动私有数据 };这个结构体的匹配逻辑简单说就是vendor和device必须匹配除非两者都是PCI_ANY_IDsubvendor和subdevice默认匹配规则比较特别如果你填了具体值必须精确匹配如果你填了PCI_ANY_ID则忽略子系统信息但如果不填即0则只有在设备的子系统信息也是0时才匹配这一点坑了很多人。实际开发中几乎总是建议显式设为PCI_ANY_ID除非你真的需要严格限定子系统。class和class_mask的配合逻辑相当于只看大类和子类忽略编程接口比如class PCI_CLASS_NETWORK_ETHERNET 8mask 0xffff00这种写法就是匹配所有以太网设备。driver_data字段不参与匹配但它会在匹配成功后原封不动地传给probe函数方便驱动在多个设备型号之间做差异化处理后面讲probe时还会再提。下面用一张表概括各字段的匹配策略字段组合匹配要求典型使用场景vendor device必须与设备完全一致绝大多数设备驱动subvendor subdevice PCI_ANY_ID忽略子系统信息芯片广泛复用、板卡各异subvendor subdevice 具体值必须完全匹配OEM定制板卡专属驱动class class_mask按类别匹配通用显示/存储控制器驱动driver_data不参与匹配传给probe同ID表内多型号差异化3.3 pci_dev你在驱动里最常打交道的设备对象一旦设备与驱动匹配成功你在probe里拿到的第一个参数就是struct pci_dev *pdev。这个结构体是PCI设备在内核里的化身字段极多但驱动开发真正高频使用的主要是这些dev通用设备对象注册后会在sysfs里生成对应节点vendor / device / subsystem_vendor / subsystem_device设备的身份信息class设备类别bus / devfn所在总线和BDF中的devfuncresource[]最多6个BAR空间描述IORESOURCE_MEM或IORESOURCE_IOirqINTx中断号注意不是MSI/MSI-Xmsi_enabled / msix_enabled中断模式标识current_state当前电源状态is_managed是否是devres管理的设备error_stateAER错误状态io_state、broken等。pdev贯穿驱动整个生命周期几乎所有PCI API都要以它为第一参数。有一点必须记住pdev是总线子系统管理的对象驱动不要尝试去释放它也不要保存它的指针跨过remove之后继续使用否则就是典型的UAF释放后使用漏洞。4. 驱动全生命周期解析从match到probe再到remove4.1 match之后发生了什么从pci_bus_type到probe的调用链现在把整套启动流程串起来。假设驱动已经insmod成功pci_bus_type中新增了一个pci_driver系统中某个PCI设备的vendor/device恰好匹配它的id_table。接下来内核执行的大致路径是pci_bus_type的match方法先被调用确认匹配成功设备模型核心调用really_probe()先检查设备是否已经被其他驱动占用如果设备之前绑定的驱动不是当前这个会先把旧驱动remove掉调用当前驱动的probe方法传入pci_dev指针probe成功返回0设备和驱动正式绑定设备状态变为online。值得注意的是probe并不一定代表设备可用。probe返回0之后驱动通常还要自己完成设备初始化使能设备、分配DMA缓冲区、注册中断等。如果probe中途失败必须返回负的错误码最常见的是-ENODEV不支持的设备、-ENOMEM内存不足、-EIO设备IO错误。内核会把这个错误码记录到sysfs的uevent或日志里用于排查问题。4.2 probe函数怎么设计初始化不能乱序probe函数是驱动里逻辑最密集的部分。以我调试过的某款PCIe转并口/SPI控制器为例一个稳扎稳打的设计顺序大概是这样static int mydev_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct mydev_dev *dev; int ret; // 1. 分配私有数据结构并挂到pci_set_drvdata dev kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) return -ENOMEM; pci_set_drvdata(pdev, dev); // 2. 使能PCI设备 ret pcim_enable_device(pdev); if (ret) goto err_free; // 3. 请求BAR资源并映射 ret pcim_iomap_regions(pdev, BIT(0), mydev); if (ret) goto err_free; dev-base pcim_iomap_table(pdev)[0]; // 4. 设置DMA掩码 ret dma_set_mask_and_coherent(pdev-dev, DMA_BIT_MASK(64)); if (ret) ret dma_set_mask_and_coherent(pdev-dev, DMA_BIT_MASK(32)); if (ret) goto err_free; // 5. 注册中断 ret request_threaded_irq(pci_irq_vector(pdev, 0), NULL, mydev_irq_thread, IRQF_ONESHOT, mydev, dev); if (ret) goto err_free; // 6. 杂项设备或字符设备注册 ret misc_register(dev-miscdev); if (ret) goto err_free; return 0; err_free: // pcim_* 系列会自动做资源清理手动释放剩余内容 return ret; }几个关键点展开说一下。第一步为什么要先分配私有结构体并立刻pci_set_drvdataprobe后所有回调函数中断、remove、pm回调可能在任何时间被触发如果中断处理函数已经在跑、而私有数据结构还没准备好就是野生指针跑飞的事故。所以顺序很严格先建好窝再打开设备电源。pcim_enable_device和pci_enable_device的区别推荐用pcim_开头的managed版本。它和普通版本最大的不同是一旦probe失败或remove调用内核会自动帮你释放已经申请的BAR映射、DMA区域等资源。我实际项目中大量使用的是pcim_enable_device、pcim_iomap_regions这种几行代码省掉一整套错误处理路径也显著降低内存泄漏的概率。DMA掩码为什么要先试64再退32这是业界标准做法。先声明设备支持64位地址成功则后续DMA可以访问高端内存减少bounce buffer的开销和IOMMU压力失败就降级到32位。顺序如果反过来设备明明支持64位却被限制在32位性能就白白浪费了。中断注册推荐request_threaded_irq处理PCIe设备中断的一个现实是设备中断处理函数里往往要做寄存器读写、队列操作等耗时任务放在硬中断上下文里会拖慢系统。threaded irq把中断处理放到内核线程里执行还能用IRQF_ONESHOT保证中断屏蔽到线程处理完非常省心。4.3 remove函数严谨的逆序退出remove的调用场景有几种设备被物理移除、驱动程序被卸载、系统重启进入shutdown前的清理。不管哪种原则都是和probe严格逆序先停止设备活动关掉中断、停掉DMA、把设备内部状态机复位注销用户态能看到的一切miscdev/字符设备、sysfs属性、proc节点等释放中断释放DMA缓冲区、取消IOMMU映射手动解除BAR映射如果用pcim系列函数其实可以省略释放私有数据结构。这里有一个特别容易犯的错误remove函数里只做资源释放却忘了先跟设备说再见。比如你直接free掉DMA缓冲区但设备DMA引擎还在运行、还在往那块内存写数据这就会导致内核访问已释放内存。更严重的还会把设备端的事务挂起连后续的bar访问都卡死。我在实际调试中遇到过好几次remove卡死的问题最后定位都是因为DMA没停干净。4.4 probe失败后的自愈机制与驱动重绑流程驱动开发中probe失败是常态。关键是要理解失败之后内核会做什么内核会把设备状态清理到未绑定状态并把它挂回未绑定设备链表如果同时存在其他支持该设备的驱动还会尝试交给下一个驱动去probe。这就意味着驱动probe失败返回-ENODEV时可能被另一个通用驱动接管设备。这种情况在某些系统中会造成两个驱动来回抢设备的假象。调试方法是看/sys/bus/pci/drivers_autoprobe和/sys/bus/pci/drivers_probe这两个节点前者控制是否自动探测后者可以手动触发重新探测。另外/sys/bus/pci/devices/0000:03:00.0/driver_override这个属性可以强制设备绑定到指定驱动这在驱动开发期非常实用——你不用每次改完驱动都重启机器。5. 资源管理的实战细节BAR空间、DMA掩码与中断申请顺序5.1 BAR空间分配与映射从resource数组到ioremapPCI设备的寄存器空间通过BARBase Address Register暴露给CPU。设备可以有0到6个BAR每个BAR要么是内存空间要么是IO空间。Linux内核对每个BAR都生成一个对应的struct resource存放在pdev-resource[0]到resource[5]。拿到BAR资源后要做两步操作请求资源所有权request_mem_region和地址映射ioremap。现代写法更推荐直接用pcim_iomap_regions(pdev, BIT(bar), name)一条命令同时完成request和ioremap并把结果放在pcim_iomap_table(pdev)返回的数组里。推荐用BIT(n)来指代BAR编号比如BIT(2)表示只映射BAR2这样在代码里一眼就能看出映射了哪几个BAR。一个实际案例某PCIe桥片有BAR0用于主控寄存器和BAR2用于FIFO缓冲区。如果我只映射BAR0忘了BAR2启动时读写FIFO就会触发Unsupported Request导致设备报AER错误甚至不可恢复。所以probe里必须对每个实际用到的BAR都做映射。还有一个判断映射之前先看resource的flags。如果是IORESOURCE_MEM用ioremap如果是IORESOURCE_IO要用ioport_map或直接inb/outb访问。混用的后果是内存序和IO序语义混乱在非x86平台上尤其严重。5.2 DMA掩码设置64位还是32位别拍脑袋前面提过dma_set_mask_and_coherent的最佳实践顺序这里再展开说说为什么这个顺序如此重要。先看一个生产环境里真实踩过的坑某PCIe设备硬件手册写着支持64位DMA地址驱动代码里也写了64位掩码但实际运行时发现性能只有预期的六成。排查到最后发现驱动确实设置了64位DMA掩码但没设置dma_set_coherent_mask导致一致性DMAcoherent DMA仍被限制在32位相邻物理内存的分配效率极低。dma_set_mask_and_coherent这个函数同时设置streaming DMA和coherent DMA两个掩码加上它就不会出现这种一半支持64位一半只在32位的割裂状态。当然如果设备在总线地址空间上是32位受限的比如某些32位PCI老设备就老老实实只设32位掩码不要勉强。另外一个容易忽略的点是DMA掩码设置应该在设备enable之后、启动DMA之前。如果设备已经跑起来了再去改掩码设备端的DMA地址翻译可能已经缓存了旧值行为不确定。所以probe函数里把DMA掩码放在pci_enable_device之后、中断和DMA使能之前是标准的顺序。5.3 中断申请MSI/MSI-X优先INTx兜底PCI设备中断有三种模式传统INTx、MSI、MSI-X。现代PCIe设备只要支持优先用MSI或MSI-X原因很简单INTx需要共享中断线中断处理程序需要读中断状态寄存器来辨别是否是本设备的中断而且一个设备只有一个中断线MSI-X可以有很多向量每个向量有独立的中断处理函数跟多队列网卡之类的设备是天作之合。申请MSI-X的标准流程是int nr_vecs pci_alloc_irq_vectors(pdev, 1, max_nr_vecs, PCI_IRQ_MSIX); if (nr_vecs 0) nr_vecs pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI); if (nr_vecs 0) nr_vecs pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_INTX);pci_alloc_irq_vectors是我很喜欢的API——它把MSI-X、MSI和INTx的统一申请收口了。返回值是实际分配到的向量数之后通过pci_irq_vector(pdev, index)获取每个向量的IRQ号。我个人的习惯是给每个MSI-X向量配一个独立的中断处理线程而不是把所有向量揉到一个handler里。这样处理函数简单、可读性强也方便在/proc/interrupts里直观看到每个队列的中断分布。代价是多几个tasklet或thread但对现代多核系统来说不值一提。5.4 devres API的取舍方便背后的约束前面反复推荐managed版APIpcim_enable_device、pcim_iomap_regions因为它能让错误处理更简洁。但它在有些场景下也是坑。比如你用dma_alloc_coherent分配的一致性内存映射去掉手动释放后remove函数里就不再显式调用dma_free_coherent了。这本是好事。但如果你不止一次在probe里重新分配了DMA缓冲区且旧缓冲区还没有被释放devres会在remove时按后进先出顺序自动释放可如果缓冲区指针还被设备DMA引擎引用着自动释放的顺序就可能导致悬空引用。我遇到过一次非常隐蔽的死锁就是devres自动释放DMA缓冲区时、设备DMA还在写那块内存最后起调了IOMMU fault。所以合理的做法是能用devres的地方BAR映射、设备使能、IRQ注册尽量用但DMA缓冲区这种设备正在使用的内存还是要手工管理生命周期。这可能是把你从一堆手册里捞出来的最实用的经验之一。6. 从PCI驱动到更上层框架的衔接字符设备与V4L2的典型链路6.1 字符设备框架在PCI驱动里的位置probe注册、remove注销很多PCI驱动最终要给用户态一个访问入口字符设备是最简单直接的方式。这里要理清一个基本关系PCI驱动负责管理硬件生命周期字符设备负责管理用户态接口生命周期。在probe里流程是初始化私有数据结构为字符设备初始化cdev结构体分配设备号主次cdev_add到内核创建/dev节点或依赖devtmpfs自动创建。在remove里反过来先删除/dev节点、再cdev_del、最后释放设备号。常用做法是用misc_register注册杂项设备因为misc设备自动分配主设备号10次设备号动态分配省去自己管理设备号空间的负担。缺点是次设备号总量有限最多255个如果你的驱动可能创建多个同类型节点就要考虑用register_chrdev_region分配一段连续设备号。有个细节值得注意用户态可能在驱动remove的瞬间正打开着设备文件。所以字符设备的fops回调里open就应该拿到私有数据的引用计数比如kref_getrelease时才kref_put这样即使内核remove了PCI设备已打开句柄的用户态程序在close之前依然可以安全访问私有数据只是访问硬件时可能返回-ENODEV。这个模式在网卡驱动、音视频采集驱动里几乎是标配。6.2 V4L2驱动的PCI实现路径从mem2mem到DMA-BUFV4L2驱动框架是另一个在PCI设备上常见的上层层级典型的就是视频采集卡、图像采集卡、电视卡等。V4L2框架与PCI框架的分工非常清晰PCI层管设备发现、BAR资源、中断、DMA映射V4L2层管视频设备节点、格式协商、buffer队列、streamon/streamoff。写一个PCI接口的视频采集驱动时probe里除了常规的PCI初始化还要注册一个video_device实例并实现v4l2_ioctl_ops、v4l2_file_operations等回调。核心的数据通路则是用户态通过V4L2的VIDIOC_REQBUFS申请帧缓冲驱动在底层分配DMA缓冲区通常用dma_alloc_coherent或DMA-BUF并把物理地址配置给视频采集芯片的DMA引擎当一帧采集完成硬件触发中断驱动在中断线程里把buffer标记为done送入v4l2的done队列用户态通过DQBUF取走。这里真正的复杂度从不在于PCI本身而在于v4l2的buffer状态机如何与PCI设备的DMA引擎状态机协同。比如一个常见的问题是streamoff时DMA正在工作如果驱动只是简单地把v4l2状态切到OFF但硬件DMA还跑着下一帧数据就会写到已经释放的buffer里。正确的顺序是先停硬件DMA、等pending的中断清空再切换V4L2状态。6.3 为什么先吃透PCI框架上层框架才不慌说了这么多其实是想表达一个观点PCI驱动框架是整个Linux设备驱动体系里最底层、也最标准的一层。字符设备框架也好、V4L2框架也好、ALSA框架也好它们做的是面向用户态的接口标准化而PCI框架做的是面向硬件的接口标准化。你把PCI层搞通了各种上层框架其实大概率能直接从probe开始把代码填进去剩下的只是学习对应框架的API习惯而已。反过来也一样如果有人在V4L2框架里卡了三天最后发现源头其实是PCI的BAR映射错误或者DMA掩码没配好这种案例我在各种内核邮件列表和论坛上见到太多太多了。所以这个系列的第一篇先把PCI框架的骨头拆明白后面的文章无论是V4L2、ALSA还是网卡驱动都能在这副骨架上自由长肉。7. 写在最后实战中反复出现的几个小规律最后简单分享几个我写PCI驱动这两年总结出来的规律不算严谨的学术结论但对排查问题很有帮助。第一个规律报错先看dmesg再看/sys再谈代码。PCI设备驱动出问题dmesg里通常会有明确的层次——设备发现阶段的报错、probe阶段的报错、中断阶段的报错含义完全不同。比如nvme驱动没起来dmesg里如果能看到pci 0000:03:00.0: [xxxx:xxxx] type 00 class 0x010802说明设备已经被正确枚举问题在probe如果连这行都没有就得回头查PCIe链路训练和拓扑了。第二个规律读硬件手册时先圈BAR与中断再读寄存器细节。PCI设备手册动辄几百页但驱动框架真正关心的就那几个问题有几个BAR每个BAR是做什么的支持哪种中断INTx/MSI/MSI-X最多几个向量DMA地址宽度是多少有没有特殊的电源管理要求。先把这些打勾再沉下心读功能寄存器效率会高很多。第三个规律不要在probe里做太耗时的事。probe是内核线程调用的会阻塞整个设备的发现流程。比如固件下载几十MB这种操作应该放到工作队列或者放到首次open时的懒加载里。我见过某款WiFi网卡驱动在probe里刷固件直接让系统启动慢了好几秒。这个系列的第一篇到这里打住。框架层面的东西讲清楚了下一篇文章我会深入BAR空间的精细操作和DMA映射的各种模式流式映射、一致性映射、SGL、IOMMU都是PCI设备驱动开发中真正考验功力的地方。如果你正在写PCI驱动或者正要开始写欢迎照着这篇的结构先搭一个最小可运行的驱动出来设备哪怕空转都行——跑通了再谈优化也不迟。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。