ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Windows NDIS协议驱动开发实战:注册绑定收发包与避坑指南

Windows NDIS协议驱动开发实战:注册绑定收发包与避坑指南 简介一份面向 Windows 底层网络驱动开发者的 NDIS 与协议驱动学习资料包聚焦网络驱动接口标准、协议驱动及外网接口驱动的实现思路适合正在学习 WDK/WDM 驱动模型、需要掌握驱动注册和网络数据包收发流程的开发者。压缩包共 31 个文件大小仅 69KB涵盖 C/C 源码.cpp/.h、编译生成的驱动文件.sys、工程配置文件.dsp/.dsw/.plg/.suo以及安装脚本.inf等基本覆盖驱动项目从编码、编译到安装调试的完整环节。已有 77 人学习下载。内容围绕“第8章”的多个示例工程展开包含协议驱动与 NDIS 的绑定、数据包发送与接收、驱动安装等可编译源码并附有说明文本与可执行程序方便边读代码边对照验证对理解 NDIS 的中断处理、内存管理与同步机制有直接帮助。适合网络设备制造商、系统集成商以及想深入底层网络技术的开发者作为简洁实用的入门参考。1. Windows 网络驱动接口标准协议驱动和外网接口驱动到底要解决什么做 Windows 网络驱动的人迟早都会碰到一个现象用户态程序用套接字收发包很顺系统自带的 TCP/IP 协议栈也很稳可一旦要自己接一个物理网口、加一种私有链路协议或者把网卡数据拿到自己手上过一遍套接字就不顶用了。接住这类需求的是 NDIS——Windows 网络驱动接口标准。它定义了协议驱动、微端口驱动、过滤驱动之间协作的通信规则。标题里的“协议驱动”和“外网接口驱动”并不是两套并列的技术而是同一套 NDIS 链路上的两个侧面协议驱动负责把数据送进或抽出网络栈外网接口驱动负责在外部链路上建立接口抽象并绑定到具体网卡。这篇笔记从这套接口标准讲起一直落到注册、绑定、收发包和 OID 查询代码再交代最容易翻车的边界坑。2. 协议驱动的选型与工程准备从 NDIS 层次结构到可编译工程骨架2.1 协议驱动站在 NDIS 栈的哪一层绑定关系决定数据走向在 Windows 内核里一条网络数据从应用进程到网卡大致要经过用户态套接字、AFD 驱动、tcpip.sys、NDIS 库、微端口驱动最后到硬件。NDIS 库在这里扮演的是“调度员”角色它维护着协议驱动与微端口驱动之间的绑定关系并统一分发收包、发包、状态通知和 OID 请求。协议驱动就处在协议侧和网卡侧的中间但它并不是只能等着上层系统调用它可以主动注册自己的收包入口把网卡收到的帧直接接走也可以在绑定成功后自行构造数据包发出去。我把“外网接口驱动”也归到这层来理解网络搜索里这个词经常被混用实际场景往往是需要驱动表现为一个完整的外部网络接口能够被上层识别、枚举、查询状态。用 NDIS 的术语讲这类驱动要做的第一步不是写业务逻辑而是完成一次成功的绑定。绑定的产物是一个绑定句柄后续所有收包、发包、OID 查询都以这个句柄为入口。搞清楚了这一点就不会再去翻那些基于套接字的用户态方案方向直接就对了。2.2 协议驱动、过滤驱动、微端口驱动三种方案怎么选动手写代码前先花点时间把三种驱动模型分清楚。很多人一搜“网络驱动”就奔着协议驱动示例去了结果做到一半发现需求其实是过滤或者反过来用过滤框架去实现独立链路最后回调链被自己绕晕。驱动模型注册入口绑定对象典型用途协议驱动NdisRegisterProtocolDriver微端口适配器自定义收发包通路、非 IP 协议接入、链路状态管理过滤驱动NdisFRegisterFilterDriver协议驱动与微端口驱动的绑定上流量拦截、修改、日志审计微端口驱动NdisMRegisterMiniportDriver网卡设备对象网卡厂商驱动、虚拟网卡我的选型习惯是如果只是想在现成 TCP/IP 路径上做流量检查或修改优先写过滤驱动它不是协议驱动应该干的事如果要做的是把一种不存在于系统协议栈里的链路协议跑起来或者需要驱动自己掌握收发时机、自己管理数据包生命周期那协议驱动就是对的。标题既然明确落到了“协议驱动”上说明你大概率属于后者。还要注意一个反直觉点协议驱动虽然名字里带“协议”但 NDIS 并不规定你实现的必须是 TCP/IP。你可以注册一个完全自定义的协议驱动绑定到以太网适配器上收上来的帧自己解析、自己转发只要回调函数和数据结构符合 NDIS 规则即可。这也是网络驱动开发最容易被低估的自由度。2.3 工程骨架与工具链WDK、测试签名和 .rar 源码包的验收习惯开发环境常见做法是 Visual Studio 2019 或 2022 配 Windows Driver Kit。装好 WDK 后在 VS 里新建“Kernel Mode Driver, Empty”工程手动引用 ndis.h 和 ndis.lib就能进入协议驱动的开发状态。协议驱动属于内核模式驱动整套代码运行在 Ring 0编译产物是一个 .sys 文件驱动加载前必须先让测试机的签名策略允许未签名驱动。bcdedit /set testsigning on打开测试签名后重启系统桌面右下角出现“测试模式”水印就说明生效了。我建议在虚拟机里做这一整套流程不要拿主力机做未签名驱动的试验蓝屏恢复起来很痛苦。很多网上下载的“网络驱动开发 .rar”源码包解压后结构通常就是 src、inc、tools 这类目录里面带一两个 vcxproj 工程和编译脚本。我的习惯是不直接安装包里自带的 .sys 文件而是把工程路径改到本机 WDK 环境重新编译一遍确保代码确实是自己可控的。源码包里的文件数量、示例工程个数不能代表质量真正要看的是 NDIS 版本是否和当前系统匹配以及回调函数名字是否还停留在 NDIS 5.x 的旧接口。老代码拿到 Windows 11 上经常出现编译失败不是代码本身错而是 NDIS 6.x 之后很多结构体字段彻底变了。3. 协议驱动的核心实现注册、绑定、收发与 OID 查询全流程3.1 注册协议驱动NdisRegisterProtocolDriver 参数逐行说明协议驱动的入口还是常规的 DriverEntry但它的任务不是创建设备对象而是把自己登记到 NDIS 库里。这一步填写的结构体直接决定 NDIS 会回调你哪些函数漏填一个关键回调驱动就像被切了手后面根本跑不起来。#include ndis.h NDIS_HANDLE g_protoHandle NULL; typedef struct _DRV_CONTEXT { NDIS_HANDLE ProtoHandle; ULONG Flags; } DRV_CONTEXT; NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { NDIS_PROTOCOL_DRIVER_CHARACTERISTICS Chars; NTSTATUS status; NdisZeroMemory(Chars, sizeof(Chars)); Chars.Header.Type NDIS_OBJECT_TYPE_PROTOCOL_DRIVER; Chars.Header.Size sizeof(NDIS_PROTOCOL_DRIVER_CHARACTERISTICS); Chars.Header.Revision NDIS_PROTOCOL_DRIVER_CHARACTERISTICS_REVISION_2; Chars.MajorVersion 6; Chars.MinorVersion 0; Chars.Name NDIS_STRING_CONST(SampleProto); Chars.BindAdapterHandler SampleBindAdapter; Chars.UnbindAdapterHandler SampleUnbindAdapter; Chars.OpenAdapterCompleteHandler SampleOpenAdapterComplete; Chars.CloseAdapterCompleteHandler SampleCloseAdapterComplete; Chars.ReceiveNetBufferListsHandler SampleReceiveNetBufferLists; Chars.SendNetBufferListsCompleteHandler SampleSendNetBufferListsComplete; Chars.OidRequestCompleteHandler SampleOidRequestComplete; Chars.StatusHandler SampleStatus; Chars.NetPnpEventHandler SampleNetPnpEvent; status NdisRegisterProtocolDriver( DriverObject, Chars, g_protoHandle); if (status ! NDIS_STATUS_SUCCESS) { return status; } DriverObject-DriverUnload SampleDriverUnload; return NDIS_STATUS_SUCCESS; }这段代码做的事就是“报到”把协议驱动的名字、版本、所有回调函数地址封装成一个特征结构体交给 NdisRegisterProtocolDriver。第一个参数是系统传给 DriverEntry 的 DriverObject第三个参数是输出参数NDIS 会返回一个协议句柄后面注销和绑定操作都要用到它。参数里最容易踩坑的是 Header 三段式Type、Size、Revision 必须匹配。Type 填错会被 NDIS 直接拒绝Size 一定要填结构体真实大小不能图省事填 0Revision 建议填 R2因为 R1 对应 NDIS 6.0 早期版本缺少新版收发包回调字段功能上会受限。3.2 绑定网卡BindAdapter 与 OpenAdapterComplete 的接力注册成功不等于能收数据。NDIS 发现系统里有符合条件的网卡适配器时会回调 BindAdapterHandler驱动要在里面调用 NdisOpenAdapterEx真正把适配器“打开”。这一步属于异步操作打开结果最终由 OpenAdapterComplete 回调带回不是 NdisOpenAdapterEx 的返回值。NDIS_STATUS SampleBindAdapter( NDIS_HANDLE ProtocolDriverContext, NDIS_HANDLE MiniportAdapterHandle, NDIS_HANDLE ProtocolBindingContext, PNDIS_STRING DeviceName, PVOID MiniportDeviceContext) { PDRV_CONTEXT drv (PDRV_CONTEXT)ProtocolDriverContext; PDEV_CONTEXT dev; NDIS_OPEN_PARAMETERS OpenParams; NDIS_STATUS status; dev AllocateDeviceContext(); if (dev NULL) { return NDIS_STATUS_RESOURCES; } dev-BindingContext ProtocolBindingContext; NdisZeroMemory(OpenParams, sizeof(OpenParams)); OpenParams.Header.Type NDIS_OBJECT_TYPE_OPEN_PARAMETERS; OpenParams.Header.Size sizeof(NDIS_OPEN_PARAMETERS); OpenParams.Header.Revision NDIS_OPEN_PARAMETERS_REVISION_1; OpenParams.AdapterName *DeviceName; OpenParams.BindingContext dev; status NdisOpenAdapterEx( MiniportAdapterHandle, g_protoHandle, ProtocolBindingContext, OpenParams, dev-OpenHandle); if (status NDIS_STATUS_PENDING) { return status; } return status; } VOID SampleOpenAdapterComplete( NDIS_HANDLE ProtocolBindingContext, NDIS_STATUS Status, NDIS_STATUS ErrorStatus, NDIS_HANDLE OpenAdapterHandle) { PDEV_CONTEXT dev GetDeviceFromBindingContext(ProtocolBindingContext); if (Status NDIS_STATUS_SUCCESS) { dev-OpenHandle OpenAdapterHandle; } else { dev-OpenHandle NULL; } }绑定回调里要注意一处细节NdisOpenAdapterEx 的第三个参数 ProtocolBindingContext 是一个不透明上下文NDIS 在后续所有与该绑定相关的回调里都会原样传回。官方语义是“协议驱动可以用它保存本绑定的实例数据”实际开发里我通常用它指向一个 Per-Binding 的设备上下文结构。这样同一个协议驱动绑定多块网卡时每块网卡都有自己的状态不会互相串数据。如果 OpenAdapterComplete 里拿到失败状态说明适配器打开失败。常见原因包括微端口驱动没有准备好、协议特征结构体里版本不兼容、或绑定请求被更高层拒绝。不要在这里急着做重试先看内核调试输出的状态码。3.3 收包路径ReceiveNetBufferLists 的 IRQL 与资源限制绑定成功后网卡收到的帧会通过微端口驱动一路送到协议驱动的 ReceiveNetBufferLists 回调。这个回调运行在 DISPATCH_LEVEL这意味着它里面不能碰分页内存、不能睡眠也不能做耗时很长的处理。最常见的翻车就是把这里当普通函数写一边收包一边申请锁、等事件导致系统卡顿甚至蓝屏。VOID SampleReceiveNetBufferLists( NDIS_HANDLE ProtocolBindingContext, NDIS_HANDLE PortNumber, PNET_BUFFER_LIST NetBufferLists, ULONG ReceiveFlags) { PDEV_CONTEXT dev GetDeviceFromBindingContext(ProtocolBindingContext); PNET_BUFFER_LIST nbl NetBufferLists; PNET_BUFFER_LIST nextNbl; if (ReceiveFlags NDIS_RECEIVE_FLAGS_RESOURCES) { // 网卡侧资源紧张这批 NBL 必须尽快处理完并归还 // 不能把 NBL 挂到自己的队列里等后台线程慢慢消费 } while (nbl ! NULL) { PNET_BUFFER nb NET_BUFFER_LIST_FIRST_NB(nbl); PMDL mdl NET_BUFFER_CURRENT_MDL(nb); ULONG dataLength NET_BUFFER_DATA_LENGTH(nb); PVOID addr NULL; nextNbl NET_BUFFER_LIST_NEXT_NBL(nbl); if (mdl ! NULL) { addr MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority); } if (addr ! NULL dataLength 0) { // 在这里对数据做检查或拷贝 // 要长时间保留数据时必须先拷贝到自己的非分页池 } nbl nextNbl; } NdisReturnNetBufferLists(dev-OpenHandle, NetBufferLists, 0); }这段逻辑的核心是NDIS 传进来的 NetBufferLists 是一条链表协议驱动可以遍历它但最终必须把这整条链表归还给 NDIS。归还动作调用 NdisReturnNetBufferLists第三个参数 ReturnFlags 一般填 0表示正常归还。如果没有归还NDIS 会认为驱动还在消费这批数据缓冲区泄漏长时间运行会耗尽网卡缓冲区。关于 IRQL 有一点要形成肌肉记忆DISPATCH_LEVEL 下 MmGetSystemAddressForMdlSafe 可以调用但要用 NormalPagePriority 请求不要传需要分页的优先级拿到地址后只能做内存读写和简单运算不能调用等待类函数。如果你确实需要把包丢给工作线程处理标准做法是立刻把数据拷贝到非分页池再把池地址挂到自己的队列里。3.4 发包路径NdisSendNetBufferLists 与完成回调的配对协议驱动主动发包时要构造一条 NET_BUFFER_LIST 链填好内存描述符然后交给 NdisSendNetBufferLists。发包是异步的真正发送完成时 NDIS 会回调 SendNetBufferListsCompleteHandler驱动必须在这个回调里释放之前分配的资源。忘记释放就是内存泄漏重复释放就是蓝屏这对回调必须严格配对。NDIS_STATUS SampleSendPacket( PDEV_CONTEXT dev, PVOID data, ULONG dataLength) { PNET_BUFFER_LIST nbl; PNET_BUFFER nb; PMDL mdl; NDIS_STATUS status; nbl NdisAllocateNetBufferList( dev-SendPoolHandle, 0, 0); if (nbl NULL) { return NDIS_STATUS_RESOURCES; } nb NET_BUFFER_LIST_FIRST_NB(nbl); mdl NdisAllocateMdl( dev-MdlPoolHandle, data, dataLength, FALSE, FALSE, NULL); if (mdl NULL) { NdisFreeNetBufferList(nbl); return NDIS_STATUS_RESOURCES; } NET_BUFFER_FIRST_MDL(nb) mdl; NET_BUFFER_DATA_LENGTH(nb) dataLength; NET_BUFFER_CURRENT_MDL(nb) mdl; NET_BUFFER_CURRENT_MDL_OFFSET(nb) 0; NdisSendNetBufferLists(dev-OpenHandle, nbl, 0); return NDIS_STATUS_PENDING; } VOID SampleSendNetBufferListsComplete( NDIS_HANDLE ProtocolBindingContext, PNET_BUFFER_LIST NetBufferLists, ULONG SendCompleteFlags) { PNET_BUFFER_LIST nbl NetBufferLists; while (nbl ! NULL) { PNET_BUFFER_LIST nextNbl NET_BUFFER_LIST_NEXT_NBL(nbl); PNET_BUFFER nb NET_BUFFER_LIST_FIRST_NB(nbl); if (nb ! NULL) { PMDL mdl NET_BUFFER_FIRST_MDL(nb); if (mdl ! NULL) { NdisFreeMdl(mdl); NET_BUFFER_FIRST_MDL(nb) NULL; } } NdisFreeNetBufferList(nbl); nbl nextNbl; } }SampleSendPacket 里有一个容易忽略的选择NdisSendNetBufferLists 的返回值。正常情况下发送请求被接管立即返回 NDIS_STATUS_PENDING真正的结果靠完成回调通知。如果返回值直接是错误状态说明参数不对或绑定句柄失效此时调用方仍然要负责释放资源。更好的做法是让完成回调统一处理释放发送函数只负责构造和提交这样无论走哪条路径都不会泄漏。NdisAllocateMdl 的前两个参数分别是 MDL 池句柄和数据缓冲区地址。要注意 data 必须是非分页内存因为发包路径同样运行在较高 IRQL。NET_BUFFER_CURRENT_MDL_OFFSET 设置成 0 表示从缓冲区开头发送如果是抓取用户态数据重新组包通常会用保留头空间这个偏移量就得跟着调。3.5 OID 查询用 OID_GEN_LINK_STATE 拿链路状态的正确姿势协议驱动和微端口驱动之间的“配置通道”是 OID 请求。想查网卡当前链路状态、速率、MAC 地址都是构造一个 NDIS_OID_REQUEST调用 NdisOidRequest 发出去。OID 请求同样可能是异步的结果由 OidRequestComplete 回调带回。NDIS_STATUS SampleQueryLinkState( PDEV_CONTEXT dev, PULONG pMediaConnectState) { NDIS_OID_REQUEST OidReq; NDIS_LINK_STATE LinkState; NDIS_STATUS status; ULONG bytesReturned 0; NdisZeroMemory(OidReq, sizeof(OidReq)); OidReq.Header.Type NDIS_OBJECT_TYPE_OID_REQUEST; OidReq.Header.Revision NDIS_OID_REQUEST_REVISION_1; OidReq.Header.Size sizeof(NDIS_OID_REQUEST); OidReq.RequestType NdisRequestQueryInformation; OidReq.DATA.QUERY_INFORMATION.Oid OID_GEN_LINK_STATE; OidReq.DATA.QUERY_INFORMATION.InformationBuffer LinkState; OidReq.DATA.QUERY_INFORMATION.InformationBufferLength sizeof(LinkState); OidReq.DATA.QUERY_INFORMATION.BytesRead bytesReturned; status NdisOidRequest(dev-OpenHandle, OidReq); if (status NDIS_STATUS_SUCCESS) { *pMediaConnectState LinkState.MediaConnectState; return NDIS_STATUS_SUCCESS; } if (status NDIS_STATUS_PENDING) { return NDIS_STATUS_PENDING; } return status; }这段代码的适用范围是同步查询场景如果 NdisOidRequest 直接返回 NDIS_STATUS_SUCCESSInformationBuffer 里就是查询结果LinkState.MediaConnectState 会给出连接状态枚举值。需要特别注意的是NDIS_OID_REQUEST 结构体本身和 InformationBuffer 都放在栈上这在同步返回时没有问题但一旦状态是 PENDING栈上内存会在函数返回后失效而异步完成回调还会去读这块内存结果就是访问无效内存。所以正式驱动里我不会把 OidReq 放栈上而是从非分页池分配一块 NDIS_OID_REQUEST连同 InformationBuffer 一起放到 Per-Binding 上下文里等 OidRequestComplete 回调处理完再释放。同步版本只在调试阶段快速验证用生产代码必须走异步路径。4. 协议驱动开发避坑蓝屏、丢包、绑定失败的五个教训4.1 现象驱动一加载重启后直接蓝屏原因协议驱动在 DriverEntry 注册成功后就允许 NDIS 调用回调但你的 BindAdapter 回调里用到的全局状态可能还没初始化完或者设备上下文分配失败后没有处理导致空指针被回调函数解引用。这是协议驱动最常见的首发蓝屏。解决注册协议驱动之前把该分配的池、该初始化的锁全部完成。BindAdapter 回调里每次都要检查分配函数返回值。宁可绑定失败返回状态码也不要带着 NULL 上下文往下走。提示蓝屏现场用 WinDbg 内核调试看堆栈如果发现崩溃点在 NdisOpenAdapterEx 之后的回调链里先查上下文指针传递对不对。4.2 现象抓包工具接不到任何包协议驱动静悄悄原因回调函数注册漏了。NDIS_PROTOCOL_DRIVER_CHARACTERISTICS 里 ReceiveNetBufferListsHandler 没填NDIS 不会报错它只是不把数据交给你的驱动。另一个常见原因是绑定其实没成功驱动以为自己在工作但 OpenAdapterComplete 里没有把 OpenHandle 存好后续收包回调拿到无效绑定句柄。解决加载驱动后用调试器看绑定状态确认协议名出现在绑定列表里。绑定不成功时检查 AdapterName 和 OpenParams.Header.Revision 是否匹配。如果绑定成功了还收不到包逐个确认特征结构体里所有网络相关回调都指向了有效函数。4.3 现象绑不上网卡返回 NDIS_STATUS_NOT_SUPPORTED原因NDIS_OPEN_PARAMETERS 的 Header 结构没对齐版本或者 OpenParameters 整体没清零。这种问题经常发生在老代码移植时旧版结构体字段名和 NDIS 6.x 不一致赋值的时候实际写错了偏移。解决任何 NDIS 对象先 NdisZeroMemory 清一遍再填字段。NDIS_OPEN_PARAMETERS 用 REVISION_1 就好别盲目追求新版。NDIS 对 Header.Size 很敏感填入 sizeof(NDIS_OPEN_PARAMETERS) 最稳妥不要手写大小。4.4 现象OID 请求返回 NDIS_STATUS_INVALID_LENGTH原因InformationBufferLength 比 OID 实际需要返回的数据量小。OID_GEN_LINK_STATE 需要的缓冲区是 NDIS_LINK_STATE 大小如果你填成了 ULONG 大小NDIS 会直接判定长度非法。还有一种情况是信息缓冲区本身指向不可访问的内存NDIS 无法完成内部拷贝。解决查询前先确认目标 OID 的输出结构体大小。非标准 OID 或厂商私有 OID先发一个长度为零的请求通过 BytesRead 拿到所需大小再分配匹配的缓冲发起第二次查询。InformationBuffer 必须指向非分页内存不能是栈上的局部变量异步场景下尤其危险。4.5 现象驱动编译安装了调试器里就是看不到日志输出原因这问题一半是环境一半是本身就没输出。调试器没接上驱动里的 DbgPrint / KdPrint 内容根本不会落盘测试签名没生效驱动加载失败回调自然不走。真正走了回调但看不到日志通常是因为 InfoLevel 或掩码设置把输出级别挡掉了。解决先把 WinDbg 内核调试配通敲 !ndiskd 能看到协议列表再谈驱动日志。DebugPrint 级别调成警告级以下用 DbgPrintEx(DPFLTR_IHVNETWORK_ID, DPFLTR_ERROR_LEVEL, ...) 这种带组件过滤的写法比裸 DbgPrint 好排查。输出看不到时检查输出掩码kd ed nt!Kd_Default_Mask 0xf5. 验证与进阶让协议驱动从能跑到可靠5.1 先用 !ndiskd 验证绑定关系再谈性能驱动加载后不要急着看业务逻辑对不对先在内核调试器里确认 NDIS 层关系。!ndiskd.protocol 能看到当前系统注册了哪些协议驱动!ndiskd.protocol SampleProto 可以列出它绑定的适配器!ndiskd.adapter 则显示微端口适配器的工作状态。这套组合拳比任何日志都直接它验证的是“协议驱动有没有真正进入 NDIS 体系”这一层进不了这一层后面全白搭。测试机上再配一句 Get-NetAdapter 看系统视角的网卡状态确认驱动没有扰乱上层网络枚举。收发数据验证要用双向流量不要只在同一台机器上回环发回环路径往往绕过了物理网卡的微端口驱动验证不出协议驱动的真实链路。5.2 进阶多队列接收与发送预留空间性能上最容易提升的点是适配网卡多队列。现代网卡支持 RSS接收端缩放NDIS 6.x 里微端口驱动会把不同连接哈希到不同 CPU 队列协议驱动的 ReceiveNetBufferLists 可能被多个 CPU 并发调用。这时 Per-Binding 上下文里的自旋锁或锁粒度就要拆细不能一把锁保护所有统计变量。我一般会把计数器和 Ring Buffer 指针拆成按 CPU 隔离的数组用 NdisInterlocked 操作减少互斥开销。发送侧还有一个常被忽略的细节NdisAllocateNetBufferList 时第二个参数 Headroom 用于预留包头空间。如果网卡支持硬件校验和分载预留 14 字节以太网头加若干字节的协议头空间组装新包时就省一次内存搬移。这个参数不是越大越好太大会浪费非分页池建议按实际协议头大小预留。5.3 验收重启回归和驱动签名协议驱动验收不能只看功能还要做重启回归。驱动在系统启动早期会被 NDIS 加载如果 DriverEntry 或绑定流程依赖了唤醒过迟的设备就会出现“开机第一次加载成功、重启后绑定失败”的怪异现象。我的习惯是测试脚本里循环 20 次重启每次开机后用 !ndiskd 确认协议句柄和绑定句柄都在再跑一轮收发用例。签名问题建议在提测前解决掉测试模式只能用于开发环境交付物必须走 WHQL 签名或至少内核模式代码签名。未签名驱动在开启 Secure Boot 的设备上根本无法安装这也是很多源码包在用户机器上装上就失效、被当成“系统问题”的真正原因。协议驱动开发最考验人的不是写回调而是理解每一次回调背后的 IRQL 限制、资源归属和异步语义。我踩过最深的坑就是把栈上结构体传给异步 OID 完成回调内存被回收后依然被 NDIS 访问调试器现场几乎没法看。后来所有异步请求对象统一从非分页池分配再也没犯过同样的错。希望这些经验和代码能帮你在 NDIS 协议驱动的路上少走几步弯路。本文还有配套的精品资源点击获取
返回列表