第 10 章 网络操作 --- 10.2 NDIS及其实现
本节剖析 NDIS(Network Driver Interface Specification)库的设计与实现。 NDIS 是 协议驱动和 NIC 驱动之间的中间层 ,定义了 三套标准接口 :Miniport 接口、IM 接口、Protocol 接口。ReactOS 的 NDIS 库实现在 drivers/network/ndis/ndis/(file:///d:/reactos/drivers/network/ndis/ndis/),主要文件 main.c、miniport.c、protocol.c、buffer.c、co.c、cl.c、cm.c、io.c、memory.c、object.c、time.c、workitem.c 等。
概述
NDIS 库位于协议驱动和 Miniport 驱动之间,提供:
- 注册和管理:维护协议/Miniport 全局列表
- 通信转发:把 Protocol API 调用转发到 Miniport,反之亦然
- 资源抽象:包池、缓冲池、内存管理
- 同步与序列化:确保多协议多实例的并发安全
NDIS 库的核心架构
NDIS 库不是一个简单的"胶水层",而是一个完整的网络驱动框架。它的核心设计理念是 "协议无关性" ------ 上层协议驱动(如 TCP/IP)不需要知道底层 NIC 的具体型号和特性,下层 Miniport 驱动也不需要知道上层使用什么协议。这种解耦通过 NDIS 定义的两套标准接口实现:
- Protocol 接口 :协议驱动调用的 API(如
NdisOpenAdapter、NdisSend、NdisCloseAdapter) - Miniport 接口 :Miniport 驱动实现的回调(如
MiniportInitialize、MiniportSend、MiniportISR)
NDIS 库的核心数据结构是三个全局链表:
c
LIST_ENTRY ProtocolListHead; // 所有已注册的协议驱动
LIST_ENTRY MiniportListHead; // 所有已注册的 Miniport 驱动
LIST_ENTRY AdapterListHead; // 所有网络适配器实例
这三个链表分别由 ProtocolListLock、MiniportListLock、AdapterListLock 保护,确保多处理器环境下的并发安全。当一个协议驱动调用 NdisRegisterProtocol 时,NDIS 库创建一个 NDIS_PROTOCOL_BLOCK 结构并将其插入到 ProtocolListHead;当一个 Miniport 驱动调用 NdisMRegisterMiniport 时,创建一个 NDIS_M_DRIVER_BLOCK 结构并插入到 MiniportListHead。
NDIS 的四层抽象模型
从架构角度看,NDIS 库实现了四层抽象:
第一层:硬件抽象层
Miniport 驱动通过 NdisMRegisterMiniport 注册到 NDIS,提供 MiniportInitialize、MiniportSend、MiniportReset 等回调。NDIS 库不关心 Miniport 如何操作硬件寄存器或管理 DMA,只要求它遵守统一的接口契约。
第二层:包管理层
NDIS 库提供 NdisAllocatePacket、NdisFreePacket、NdisAllocateBuffer、NdisChainBufferAtBack 等函数,抽象了网络包的内存管理。协议驱动不需要关心包的实际存储方式,只需通过这些 API 分配和释放包。
第三层:绑定管理层
当协议驱动调用 NdisOpenAdapter 时,NDIS 库在 AdapterListHead 中查找匹配的适配器,建立协议驱动和 Miniport 驱动之间的绑定关系。绑定成功后,协议驱动获得一个 BindingHandle,后续的发送和接收都通过这个句柄进行。
第四层:协议无关层
NDIS 库负责将 Protocol API 调用转发到正确的 Miniport 驱动。例如,当 TCP/IP 协议调用 NdisSend 时,NDIS 库根据 BindingHandle 找到对应的 Miniport 驱动,然后调用其 MiniportSend 回调。这个过程对协议驱动完全透明。
NDIS 库的关键设计决策
设计决策一:异步发送模型
NdisSend 采用异步语义 ------ 返回成功只表示包已进入发送队列,不表示包已发送到网卡。Miniport 驱动在后台通过 DPC 处理发送队列,完成后调用 NdisMSendComplete 通知协议驱动。这种设计允许协议驱动在发送繁忙时继续处理其他任务,提高了整体吞吐量。
设计决策二:批量接收优化
NdisMIndicateReceivePacket 支持一次指示多个包,这是对现代网卡中断合并特性的优化。当网卡在单个中断中接收到多个包时,Miniport 驱动可以将它们打包成数组一次性指示给 NDIS 库,减少了函数调用开销和锁竞争。
设计决策三:内存池优化
NDIS 库使用 lookaside list 优化频繁分配的小对象(如 NDIS_PACKET、NDIS_BUFFER)。NdisAllocateFromNPagedLookasideList 比 ExAllocatePoolWithTag 更快,因为它避免了内存管理器的锁竞争。
设计决策四:严格的 IRQL 规则
NDIS 定义了严格的 IRQL 规则:MiniportISR 在 DIRQL 执行,MiniportSend 在 DISPATCH_LEVEL 执行,ProtocolReceive 在 PASSIVE_LEVEL 执行。这种分层确保了中断处理的快速响应和协议处理的灵活性。
NDIS 与 Windows 内核的交互
NDIS 库深度集成到 Windows 内核的多个子系统:
- PnP 子系统 :NDIS 响应
IRP_MN_START_DEVICE、IRP_MN_STOP_DEVICE、IRP_MN_REMOVE_DEVICE等 PnP IRP,管理适配器的即插即用生命周期。 - 电源管理子系统 :NDIS 支持设备电源状态转换(D0-D3),通过
MiniportSetPower回调控制网卡的电源状态。 - 内存管理子系统 :NDIS 使用
ExAllocatePoolWithTag分配非分页池,使用MmMapIoSpace映射设备 I/O 空间。 - 同步原语 :NDIS 使用
KeAcquireSpinLock、ExAcquireFastMutex、KeWaitForSingleObject等同步机制保护共享数据结构。
ReactOS NDIS 的实现特点
ReactOS 的 NDIS 实现基于 Windows NDIS 5.x 规范,具有以下特点:
- 兼容性优先:实现了 NDIS 5.x 的核心 API,支持大多数 Windows 2000/XP 时代的 NIC 驱动。
- 模块化设计 :代码按功能划分为多个模块:
main.c(初始化)、miniport.c(Miniport 接口)、protocol.c(Protocol 接口)、buffer.c(缓冲区管理)、memory.c(内存分配)等。 - lwIP 集成 :与 lwIP TCP/IP 协议栈紧密集成,通过
NdisSend和ProtocolReceive完成数据包的收发。 - 测试覆盖:包含单元测试和集成测试,确保核心功能的正确性。
一个典型的 NDIS 初始化流程
系统启动 → ndis.sys 加载 → DriverEntry 执行
↓
NdisInitializeWrapper() → 初始化全局链表和锁
↓
Miniport 驱动加载 → NdisMRegisterMiniport() → 创建 NDIS_M_DRIVER_BLOCK
↓
PnP 管理器检测 NIC → IRP_MN_START_DEVICE → MiniportInitialize()
↓
创建适配器实例 → NdisMSetAttributes() → 插入 AdapterListHead
↓
协议驱动加载 → NdisRegisterProtocol() → 创建 NDIS_PROTOCOL_BLOCK
↓
协议绑定 → NdisOpenAdapter() → 建立 BindingHandle
↓
网络就绪 → 可以开始收发数据
这个流程涉及多个内核子系统的协作:PnP 管理器负责设备枚举,NDIS 库负责驱动注册和绑定,协议驱动负责协议处理,Miniport 驱动负责硬件操作。
本节内容概览
- 10.2.0 框架图
- 10.2.1 NDIS 库的体系结构
- 10.2.2
DriverEntry与全局数据结构 - 10.2.3 Miniport 驱动注册
- 10.2.4 Protocol 驱动注册
- 10.2.5 Miniport 回调
- 10.2.6 Protocol 回调
- 10.2.7 数据发送与接收
- 10.2.8 NDIS 包(Packet)管理
- 10.2.9 总结与代码索引
学习目标
- 理解 NDIS 在网络栈中的位置
- 掌握 Miniport/Protocol 注册流程
- 知道 NDIS_PACKET 结构和池管理
- 跟踪数据发送和接收路径
涉及的内核子系统
| 子系统 | 头文件/源文件 | 核心作用 |
|---|---|---|
| NDIS 主入口 | drivers/network/ndis/ndis/main.c(file:///d:/reactos/drivers/network/ndis/ndis/main.c) | DriverEntry |
| Miniport | miniport.c(file:///d:/reactos/drivers/network/ndis/ndis/miniport.c) | Miniport 注册、回调 |
| Protocol | protocol.c(file:///d:/reactos/drivers/network/ndis/ndis/protocol.c) | Protocol 注册、回调 |
| 缓冲 | buffer.c(file:///d:/reactos/drivers/network/ndis/ndis/buffer.c) | 包缓冲 |
| 内存 | memory.c(file:///d:/reactos/drivers/network/ndis/ndis/memory.c) | 内存分配 |
| 包管理 | co.c(file:///d:/reactos/drivers/network/ndis/ndis/co.c) | CoNDIS(连接导向) |
| Client | cl.c(file:///d:/reactos/drivers/network/ndis/ndis/cl.c) | NDIS 5.x 客户端 API |
| CallMgr | cm.c(file:///d:/reactos/drivers/network/ndis/ndis/cm.c) | Call Manager |
| 同步 | io.c(file:///d:/reactos/drivers/network/ndis/ndis/io.c) | I/O 处理 |
| 工作项 | workitem.c(file:///d:/reactos/drivers/network/ndis/ndis/workitem.c) | WorkItem 调度 |
| 计时 | time.c(file:///d:/reactos/drivers/network/ndis/ndis/time.c) | 计时器 |
| 对象 | object.c(file:///d:/reactos/drivers/network/ndis/ndis/object.c) | NDIS_HANDLE 管理 |
10.2.0 框架图
+-------------------------+
| Protocol Driver |
| (tcpip.sys / lan.sys) |
+-------------------------+
|
v NdisSend / NdisReceive
+-------------------------+
| NDIS 库 (ndis.sys) | 本节主题
| - ProtocolList |
| - MiniportList |
| - AdapterList |
| - Packet Pool |
| - Buffer Pool |
+-------------------------+
|
v MiniportXxx 回调
+-------------------------+
| Miniport Driver |
| (e1000.sys / dc21x4.sys)|
+-------------------------+
|
v
硬件 NIC
10.2.1 NDIS 库的体系结构
NDIS 5.x 库在 NDIS 4.x 基础上增加了:
- CoNDIS(Connection-oriented NDIS):支持面向连接的服务
- WMI:网络驱动 WMI 支持
- Plug and Play:热插拔支持
- Power Management:电源管理
NDIS 版本
| 版本 | 时间 | 关键特性 |
|---|---|---|
| NDIS 3.1 | Windows for Workgroups 3.11 | 基础 Miniport/Protocol |
| NDIS 4.0 | Windows 95 OSR2 | PnP 基础 |
| NDIS 5.0 | Windows 2000 | CoNDIS、WMI、PM |
| NDIS 5.1 | Windows XP | 中间层驱动 |
| NDIS 6.0 | Vista | RSS、LSO |
| NDIS 6.x | Win7-10 | 性能优化 |
ReactOS 实现 NDIS 5.x 兼容。
主要组件
c
// ndissys.h
typedef struct _NDIS_PROTOCOL_BLOCK { ... } NDIS_PROTOCOL_BLOCK;
typedef struct _NDIS_M_DRIVER_BLOCK { ... } NDIS_M_DRIVER_BLOCK;
typedef struct _NDIS_ADAPTER_BLOCK { ... } NDIS_ADAPTER_BLOCK;
typedef struct _NDIS_PACKET { ... } NDIS_PACKET;
typedef struct _NDIS_BUFFER { ... } NDIS_BUFFER; // 实际是 MDL
10.2.2 DriverEntry 与全局数据结构
main.c(file:///d:/reactos/drivers/network/ndis/ndis/main.c#L40):
c
NTSTATUS NTAPI
DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
NDIS_DbgPrint(MAX_TRACE, ("Called.\n"));
InitializeListHead(&ProtocolListHead);
KeInitializeSpinLock(&ProtocolListLock);
InitializeListHead(&MiniportListHead);
KeInitializeSpinLock(&MiniportListLock);
InitializeListHead(&AdapterListHead);
KeInitializeSpinLock(&AdapterListLock);
DriverObject->DriverUnload = MainUnload;
CancelId = 0;
return STATUS_SUCCESS;
}
全局数据结构
| 全局变量 | 含义 |
|---|---|
ProtocolListHead |
已注册的协议驱动列表 |
MiniportListHead |
已注册的 Miniport 驱动列表 |
AdapterListHead |
网络适配器列表 |
CancelId |
取消操作 ID 计数器 |
主要结构
NDIS_PROTOCOL_BLOCK
c
typedef struct _NDIS_PROTOCOL_BLOCK {
LIST_ENTRY ProtocolListEntry;
NDIS_HANDLE NdisProtocolHandle; // 协议驱动句柄
PDRIVER_OBJECT DriverObject; // 协议驱动
NDIS_PROTOCOL_CHARACTERISTICS ProtocolCharacteristics; // 协议回调
// ... 内部字段
} NDIS_PROTOCOL_BLOCK;
NDIS_M_DRIVER_BLOCK
c
typedef struct _NDIS_M_DRIVER_BLOCK {
LIST_ENTRY MiniportListEntry;
NDIS_HANDLE NdisMiniportDriverHandle; // Miniport 句柄
PDRIVER_OBJECT DriverObject;
NDIS_MINIPORT_CHARACTERISTICS MiniportCharacteristics; // Miniport 回调
// ... 内部字段
} NDIS_M_DRIVER_BLOCK;
NDIS_ADAPTER_BLOCK
c
typedef struct _NDIS_ADAPTER_BLOCK {
LIST_ENTRY AdapterListEntry;
NDIS_HANDLE NdisAdapterHandle; // 适配器句柄
PNDIS_M_DRIVER_BLOCK Miniport; // 关联 Miniport
PNDIS_PROTOCOL_BLOCK *Protocols; // 关联协议
ULONG MaxPacketSize;
// ... 资源、队列、状态
} NDIS_ADAPTER_BLOCK;
10.2.3 Miniport 驱动注册
Miniport 驱动调用 NdisMRegisterMiniport 注册:
c
// 在 Miniport 驱动的 DriverEntry
NTSTATUS NTAPI
DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
NDIS_MINIPORT_CHARACTERISTICS Characteristics = {0};
Characteristics.MajorNdisVersion = 5;
Characteristics.MinorNdisVersion = 0;
Characteristics.InitializeHandler = MyMiniportInitialize;
Characteristics.HaltHandler = MyMiniportHalt;
Characteristics.SendHandler = MyMiniportSend;
Characteristics.TransferDataHandler = MyMiniportTransferData;
Characteristics.ResetHandler = MyMiniportReset;
Characteristics.QueryInformationHandler = MyMiniportQueryInformation;
Characteristics.SetInformationHandler = MyMiniportSetInformation;
// ... 更多回调
NdisMRegisterMiniport(&Status, DriverObject, &Characteristics, sizeof(Characteristics));
return Status;
}
NdisMRegisterMiniport 内部
c
VOID NdisMRegisterMiniport(PNDIS_STATUS Status, PDRIVER_OBJECT DriverObject,
PNDIS_MINIPORT_CHARACTERISTICS Characteristics, UINT CharacteristicsLength)
{
PNDIS_M_DRIVER_BLOCK MiniportBlock;
// 1. 分配 Miniport 块
MiniportBlock = ExAllocatePoolWithTag(NonPagedPool, sizeof(NDIS_M_DRIVER_BLOCK), 'pmnD');
RtlZeroMemory(MiniportBlock, sizeof(NDIS_M_DRIVER_BLOCK));
// 2. 复制回调
RtlCopyMemory(&MiniportBlock->MiniportCharacteristics, Characteristics, CharacteristicsLength);
MiniportBlock->DriverObject = DriverObject;
// 3. 加入全局列表
ExInterlockedInsertHeadList(&MiniportListHead, &MiniportBlock->MiniportListEntry, &MiniportListLock);
*Status = NDIS_STATUS_SUCCESS;
}
10.2.4 Protocol 驱动注册
协议驱动调用 NdisRegisterProtocol 注册:
c
NDIS_HANDLE NdisProtocolHandle;
VOID RegisterProtocol(PDRIVER_OBJECT DriverObject)
{
NDIS_PROTOCOL_CHARACTERISTICS ProtChars = {0};
ProtChars.MajorNdisVersion = 5;
ProtChars.OpenAdapterCompleteHandler = MyOpenAdapterComplete;
ProtChars.CloseAdapterCompleteHandler = MyCloseAdapterComplete;
ProtChars.SendCompleteHandler = MySendComplete;
ProtChars.TransferDataCompleteHandler = MyTransferDataComplete;
ProtChars.ResetCompleteHandler = MyResetComplete;
ProtChars.RequestCompleteHandler = MyRequestComplete;
ProtChars.ReceiveHandler = MyReceive;
ProtChars.ReceiveCompleteHandler = MyReceiveComplete;
ProtChars.StatusHandler = MyStatus;
ProtChars.StatusCompleteHandler = MyStatusComplete;
// ...
NdisRegisterProtocol(&Status, &NdisProtocolHandle, &ProtChars, sizeof(ProtChars));
}
NdisRegisterProtocol 内部
c
VOID NdisRegisterProtocol(PNDIS_STATUS Status, PNDIS_HANDLE NdisProtocolHandle,
PNDIS_PROTOCOL_CHARACTERISTICS ProtocolCharacteristics,
UINT CharacteristicsLength)
{
PNDIS_PROTOCOL_BLOCK ProtBlock;
// 1. 分配协议块
ProtBlock = ExAllocatePoolWithTag(NonPagedPool, sizeof(NDIS_PROTOCOL_BLOCK), 'torP');
// 2. 复制回调
RtlCopyMemory(&ProtBlock->ProtocolCharacteristics, ProtocolCharacteristics, CharacteristicsLength);
// 3. 加入全局列表
ExInterlockedInsertHeadList(&ProtocolListHead, &ProtBlock->ProtocolListEntry, &ProtocolListLock);
*NdisProtocolHandle = ProtBlock;
*Status = NDIS_STATUS_SUCCESS;
}
10.2.5 Miniport 回调
MiniportInitialize
c
NDIS_STATUS MyMiniportInitialize(PNDIS_STATUS OpenStatus, UINT SelectedMediumIndex,
PNDIS_MEDIUM Medium, UINT MediumCount,
NDIS_HANDLE MiniportAdapterContext,
NDIS_HANDLE WrapperConfigurationContext)
{
// 1. 读取硬件配置
// 2. 分配硬件资源(I/O 端口、IRQ、DMA)
// 3. 注册中断
// 4. 注册 DMA
// 5. 复位 HBA
// 6. 设置 MAC 地址
return NDIS_STATUS_SUCCESS;
}
MiniportSend
c
NDIS_STATUS MyMiniportSend(NDIS_HANDLE MiniportAdapterContext,
PNDIS_PACKET Packet, UINT Flags)
{
// 1. 从 Packet 中提取数据缓冲
// 2. 构造 PRDT(Physical Region Descriptor Table)
// 3. 启动 DMA
// 4. 返回 NDIS_STATUS_PENDING
return NDIS_STATUS_PENDING;
}
MiniportHalt
c
VOID MyMiniportHalt(NDIS_HANDLE MiniportAdapterContext)
{
// 1. 取消未完成操作
// 2. 停止 NIC
// 3. 释放中断
// 4. 释放资源
}
10.2.6 Protocol 回调
ProtocolReceive
c
INT MyReceive(NDIS_HANDLE ProtocolBindingContext, NDIS_HANDLE MacReceiveContext,
UCHAR HeaderBuffer[], UINT HeaderBufferSize,
UCHAR LookaheadBuffer[], UINT LookaheadBufferSize, UINT PacketSize)
{
// 1. 检查数据包
// 2. 接受或拒绝
if (PacketSize > MyMaxPacket) {
return 0; // 拒绝
}
// 3. 复制 Lookahead 到本驱动缓冲
RtlCopyMemory(MyBuffer, LookaheadBuffer, LookaheadBufferSize);
// 4. 返回剩余字节
return LookaheadBufferSize;
}
ProtocolSendComplete
c
VOID MySendComplete(NDIS_HANDLE ProtocolBindingContext,
PNDIS_PACKET Packet, NDIS_STATUS Status)
{
// 1. 回收包到池
NdisFreePacket(Packet);
// 2. 通知上层 I/O 完成
}
ProtocolReceivePacket(NDIS 4.0+)
c
INT MyReceivePacket(NDIS_HANDLE ProtocolBindingContext, PNDIS_PACKET Packet)
{
// 1. 处理包
// 2. 返回接收的字节数
}
10.2.7 数据发送与接收
发送路径
Protocol Driver
|
| NdisSend(BindingHandle, Packet, Flags)
v
NDIS 库
|
| 调用 MiniportSend 回调
v
Miniport Driver
|
| 启动硬件 DMA
v
NIC 发送
|
| 中断 -> ISR -> MiniportReturnPackets
v
NDIS 库
|
| 调用 ProtocolSendComplete
v
Protocol Driver
接收路径
NIC 接收
|
| 触发中断
v
Miniport ISR
|
| 调用 NdisMIndicateReceivePacket
v
NDIS 库
|
| 调用 ProtocolReceivePacket
v
Protocol Driver
NdisSend
c
VOID NdisSend(PNDIS_STATUS Status, NDIS_HANDLE NdisBindingHandle,
PNDIS_PACKET Packet)
{
PNDIS_PROTOCOL_BLOCK Prot = NdisBindingHandle;
PNDIS_ADAPTER_BLOCK Adapter = Prot->Adapter;
PNDIS_M_DRIVER_BLOCK Miniport = Adapter->Miniport;
// 调用 Miniport 的 Send 回调
*Status = Miniport->MiniportCharacteristics.SendHandler(
Miniport->MiniportAdapterContext, Packet, 0);
}
NdisMIndicateReceivePacket
c
VOID NdisMIndicateReceivePacket(NDIS_HANDLE NdisAdapterHandle, PPNDIS_PACKET PacketArray, UINT NumberOfPackets)
{
PNDIS_ADAPTER_BLOCK Adapter = NdisAdapterHandle;
PNDIS_PROTOCOL_BLOCK Protocol;
// 对每个已绑定协议调用 ReceivePacket
for (i = 0; i < Adapter->ProtocolCount; i++) {
Protocol = Adapter->Protocols[i];
Protocol->ProtocolCharacteristics.ReceivePacketHandler(
Protocol->ProtocolBindingContext, PacketArray[i]);
}
}
10.2.8 NDIS 包(Packet)管理
NDIS_PACKET
c
typedef struct _NDIS_PACKET {
NDIS_PACKET_PRIVATE Private; // NDIS 内部
// ... 头部
} NDIS_PACKET;
NDIS_PACKET_PRIVATE 包含:
- 包的池、分配者
- 第一个和后续缓冲(
NDIS_BUFFER链) - 完成状态
- 包统计
NdisAllocatePacketPool
c
VOID NdisAllocatePacketPool(PNDIS_STATUS Status, PNDIS_HANDLE PoolHandle,
UINT NumberOfDescriptors, UINT ProtocolReservedLength)
{
PNDIS_PACKET_POOL Pool;
Pool = ExAllocatePoolWithTag(NonPagedPool, sizeof(NDIS_PACKET_POOL), 'papD');
InitializeListHead(&Pool->FreeList);
// 预分配包
for (i = 0; i < NumberOfDescriptors; i++) {
PNDIS_PACKET Packet = ExAllocatePoolWithTag(NonPagedPool, sizeof(NDIS_PACKET), 'kapD');
InsertTailList(&Pool->FreeList, &Packet->Private.FreeListEntry);
}
*PoolHandle = Pool;
*Status = NDIS_STATUS_SUCCESS;
}
NdisAllocatePacket
c
VOID NdisAllocatePacket(PNDIS_STATUS Status, PNDIS_PACKET *Packet, NDIS_HANDLE PoolHandle)
{
PNDIS_PACKET_POOL Pool = PoolHandle;
if (IsListEmpty(&Pool->FreeList)) {
*Status = NDIS_STATUS_RESOURCES;
*Packet = NULL;
return;
}
PLIST_ENTRY Entry = RemoveHeadList(&Pool->FreeList);
*Packet = CONTAINING_RECORD(Entry, NDIS_PACKET, Private.FreeListEntry);
*Status = NDIS_STATUS_SUCCESS;
}
NdisChainBufferAtFront
c
VOID NdisChainBufferAtFront(PNDIS_PACKET Packet, PNDIS_BUFFER Buffer)
{
if (Packet->Private.Head == NULL) {
// 第一个缓冲
Packet->Private.Head = Buffer;
Packet->Private.Tail = Buffer;
} else {
// 链接到头部
NdisSetNextBuffer(Buffer, Packet->Private.Head);
Packet->Private.Head = Buffer;
}
}
10.2.9 总结
关键要点:
- NDIS 是中间层库:连接协议驱动和 Miniport 驱动
- 三套接口:Miniport(NIC)、IM(中间层)、Protocol(协议)
DriverEntry初始化全局列表:ProtocolList、MiniportList、AdapterListNdisMRegisterMiniport/NdisRegisterProtocol:注册驱动- 包(Packet)池管理 :
NdisAllocatePacketPool/NdisAllocatePacket/NdisFreePacket - 发送/接收回调 :
MiniportSend/ProtocolReceive NdisMIndicateReceivePacket:Miniport 通知协议
NDIS 库的深度技术分析
ReactOS 的 NDIS 库实现是一个复杂的系统工程案例,涉及并发控制、资源管理、异步 I/O 等多个核心技术领域。本节深入分析 NDIS 库的关键技术细节。
并发控制与同步机制
NDIS 库面临的核心挑战之一是并发控制。在多处理器系统中,多个协议驱动可能同时向同一个 Miniport 发送数据包,而 Miniport 的中断处理程序可能随时触发接收回调。ReactOS 采用了多层次的同步策略:
-
全局列表锁 :
ProtocolListLock、MiniportListLock、AdapterListLock保护全局注册列表。这些锁使用KeInitializeSpinLock初始化,通过KeAcquireSpinLock和KeReleaseSpinLock进行加锁和解锁。 -
适配器级锁:每个适配器实例有自己的锁,保护适配器的内部状态(如发送队列、接收队列)。这种细粒度锁设计减少了锁竞争,提高了并发性能。
-
DPC(Deferred Procedure Call)机制 :Miniport 的中断处理程序(ISR)运行在高中断级别(DIRQL),只能执行最基本的操作(如读取中断状态寄存器)。复杂的处理(如包接收、发送完成通知)被延迟到 DPC 级别执行。ReactOS 通过
NdisMDpc机制实现这种延迟处理。
包池管理的优化
NDIS 包池(Packet Pool)是性能关键组件。每次网络 I/O 都需要分配和释放包描述符,如果每次都调用 ExAllocatePool,会导致严重的性能瓶颈和内存碎片。ReactOS 采用预分配池技术:
c
VOID NdisAllocatePacketPool(PNDIS_STATUS Status, PNDIS_HANDLE PoolHandle,
UINT NumberOfDescriptors, UINT ProtocolReservedLength)
{
PNDIS_PACKET_POOL Pool;
Pool = ExAllocatePoolWithTag(NonPagedPool, sizeof(NDIS_PACKET_POOL), 'papD');
InitializeListHead(&Pool->FreeList);
// 预分配所有包描述符
for (i = 0; i < NumberOfDescriptors; i++) {
PNDIS_PACKET Packet = ExAllocatePoolWithTag(NonPagedPool, sizeof(NDIS_PACKET), 'kapD');
InsertTailList(&Pool->FreeList, &Packet->Private.FreeListEntry);
}
*PoolHandle = Pool;
}
这种设计的关键是:
- 预分配:在池创建时一次性分配所有包描述符,避免运行时分配开销
- 空闲链表 :使用双向链表(
LIST_ENTRY)管理空闲包,分配和释放都是 O(1) 操作 - 非分页池 :使用
NonPagedPool确保包描述符在中断上下文中可访问 - 内存标签:使用 'papD'、'kapD' 等标签便于内存泄漏调试
绑定(Binding)机制的实现
NDIS 的绑定机制是协议驱动和 Miniport 驱动之间的关联。当一个协议驱动(如 TCP/IP)绑定到一个适配器时,NDIS 库创建一个 ADAPTER_BINDING 结构,记录协议和适配器之间的关联:
c
typedef struct _ADAPTER_BINDING {
LIST_ENTRY AdapterListEntry; // 在适配器的绑定列表中
LIST_ENTRY ProtocolListEntry; // 在协议的绑定列表中
PLOGICAL_ADAPTER Adapter; // 关联的适配器
PNDIS_PROTOCOL_BLOCK Protocol; // 关联的协议
NDIS_HANDLE BindingHandle; // 绑定句柄(对外暴露)
// ... 其他字段
} ADAPTER_BINDING;
绑定过程的关键步骤:
- 协议驱动调用
NdisOpenAdapter,传入协议句柄和适配器名称 - NDIS 库查找适配器,验证协议和适配器兼容(如介质类型匹配)
- 分配
ADAPTER_BINDING结构,初始化字段 - 将绑定加入适配器的绑定列表和协议的绑定列表
- 调用 Miniport 的
MiniportInitialize初始化硬件 - 异步完成后,调用协议的
OpenAdapterCompleteHandler回调
PnP(即插即用)支持
NDIS 5.x 引入了 PnP 支持,允许网络适配器的热插拔和动态配置。ReactOS 的 PnP 实现位于 miniport.c 和 protocol.c 中,关键机制包括:
- 设备枚举:系统启动时,PnP 管理器枚举所有 PCI 设备,识别网络设备(Class Code = 0x02)。
- 驱动加载:根据硬件 ID 从注册表加载对应的 Miniport 驱动。
- 资源分配:PnP 管理器为适配器分配 I/O 端口、内存地址、IRQ 等资源。
- 绑定通知:当适配器启动或停止时,NDIS 通知所有绑定的协议驱动。
ReactOS 通过 NET_PNP_EVENT 结构传递 PnP 事件:
c
PNET_PNP_EVENT
ProSetupPnPEvent(
NET_PNP_EVENT_CODE EventCode,
PVOID EventBuffer,
ULONG EventBufferLength)
{
PNET_PNP_EVENT PnPEvent;
PnPEvent = ExAllocatePool(PagedPool, sizeof(NET_PNP_EVENT));
PnPEvent->NetEvent = EventCode;
if (EventBuffer != NULL) {
PnPEvent->Buffer = ExAllocatePool(PagedPool, EventBufferLength);
RtlCopyMemory(PnPEvent->Buffer, EventBuffer, PnPEvent->BufferLength);
}
return PnPEvent;
}
错误处理与恢复
NDIS 库实现了多层次的错误处理机制:
-
Miniport 复位 :当 Miniport 检测到硬件错误(如 DMA 错误、总线超时)时,调用
NdisMReset。NDIS 库调用 Miniport 的ResetHandler,尝试恢复硬件状态。 -
挂起检测 :NDIS 库定期调用 Miniport 的
CheckForHangHandler,检测硬件是否挂起。如果检测到挂起,触发复位。 -
错误日志 :NDIS 库通过
NdisWriteErrorLogEntry将错误写入系统事件日志,便于诊断。
与 Windows NDIS 的差异
ReactOS 的 NDIS 实现与 Windows 存在以下差异:
-
NDIS 版本:ReactOS 实现 NDIS 5.1,不支持 NDIS 6.x 的新特性(如 NDIS 6.0 的 NetBuffer 结构、NDIS 6.20 的 VMQ)。
-
CoNDIS 支持 :ReactOS 包含 CoNDIS(连接导向 NDIS)代码(
co.c、cl.c、cm.c),但实际使用较少。CoNDIS 用于支持 ATM、帧中继等面向连接的网络技术。 -
中间层驱动:ReactOS 对 NDIS IM(Intermediate)驱动的支持有限。IM 驱动用于实现防火墙、VPN、QoS 等功能,位于协议驱动和 Miniport 之间。
-
性能优化:Windows NDIS 包含大量性能优化,如接收端缩放(RSS)、大发送卸载(LSO)、校验和卸载等。ReactOS 未实现这些优化。
NDIS 库的调试支持
ReactOS 的 NDIS 库包含丰富的调试支持:
-
跟踪级别 :通过
DebugTraceLevel变量控制跟踪输出的详细程度(MIN_TRACE、MID_TRACE、MAX_TRACE)。 -
包转储 :
MiniDisplayPacket函数可以将包内容以十六进制格式输出到调试控制台,便于分析网络流量。 -
断点 :
BREAK_ON_MINIPORT_INIT宏可以在 Miniport 初始化时触发调试器断点,便于调试驱动启动过程。
这些调试机制在开发过程中发挥了重要作用,帮助开发者定位网络栈中的问题。
NDIS 库的未来发展方向
ReactOS 的 NDIS 库仍有改进空间:
- NDIS 6.x 支持:逐步引入 NDIS 6.x 特性,提高与现代网卡的兼容性。
- 性能优化:实现 RSS、LSO 等性能优化特性,提高网络吞吐量。
- 安全性:加强输入验证,防止恶意网络包导致的缓冲区溢出等安全问题。
- 虚拟化支持:支持 SR-IOV(Single Root I/O Virtualization),提高虚拟机网络性能。
NDIS 库是 ReactOS 网络栈的基石,其稳定性和性能直接影响整个系统的网络表现。随着 ReactOS 的不断发展,NDIS 库也将持续演进,逐步缩小与 Windows 的差距。
10.2.10 设计哲学问答
Q1:为什么 NDIS 库要维护三个独立的全局链表(Protocol/Miniport/Adapter)?
A :职责分离 + 并发优化。
三个链表分别管理不同层次的对象:ProtocolListHead 管理协议驱动(如 TCP/IP),MiniportListHead 管理硬件驱动(如 Intel e1000),AdapterListHead 管理具体的适配器实例(如 eth0)。这种分离使得:
- 协议驱动可以独立加载/卸载,不影响硬件驱动;
- 多个协议可以绑定到同一适配器(如 TCP/IP 和 IPX);
- 每个链表有独立的锁,减少锁竞争。
在 main.c(file:///d:/reactos/drivers/network/ndis/ndis/main.c#L40) 的 DriverEntry 中,三个锁分别初始化,确保并发安全。
Q2:为什么 NdisSend 返回成功不代表包已发送到网卡?
A :异步发送模型 + 流量控制。
NdisSend 的语义是"包已进入发送队列",而非"已到达网线"。如果 Miniport 的发送环已满,NdisSend 返回 NDIS_STATUS_RESOURCES,协议栈需要等待重试。这种设计允许:
- 协议驱动在发送繁忙时继续处理其他任务;
- Miniport 驱动在后台通过 DPC 批量处理发送队列;
- 实现流量控制(通过返回资源不足来反向压力)。
实际发送完成后,Miniport 调用 NdisMSendComplete 通知协议驱动释放包资源。
Q3:为什么 MiniportISR 在 DIRQL 执行而 ProtocolReceive 在 PASSIVE_LEVEL?
A :中断响应速度 vs 功能灵活性。
MiniportISR 运行在 DIRQL(设备中断请求级别),只能执行最基本的操作(读取中断状态、启动 DMA、调度 DPC),目的是尽快完成中断处理。而 ProtocolReceive 运行在 PASSIVE_LEVEL,可以执行复杂操作(内存分配、协议解析、数据拷贝)。
这种分层确保:
- 中断响应时间最小化(通常 < 10 µs);
- 复杂处理延迟到 DPC 级别,不阻塞中断;
- 协议驱动可以自由使用分页内存。
Q4:为什么 NDIS 需要自己的包池管理而不是直接用 ExAllocatePool?
A :性能优化 + 内存碎片控制。
网络 I/O 涉及大量临时包分配,如果每次都调用 ExAllocatePool,会导致:
- 严重的内存碎片(频繁分配/释放小对象);
- 内存管理器锁竞争(多处理器环境下);
- 不可预测的分配延迟(可能触发页面调度)。
NdisAllocatePacketPool 预分配固定数量的包描述符,通过链表管理空闲包,分配/释放操作只是链表操作,几乎零延迟。在 memory.c(file:///d:/reactos/drivers/network/ndis/ndis/memory.c) 中可以看到 lookaside list 的实现。
Q5:为什么需要 NdisMIndicateReceivePacket 批量指示多个包?
A :中断合并优化 + 减少函数调用开销。
现代千兆网卡支持中断合并(Interrupt Coalescing)------在单个中断中接收多个包。如果每收到一个包就调用一次指示,会产生大量函数调用开销和锁竞争。NdisMIndicateReceivePacket 支持一次指示多个包,协议驱动的 ProtocolReceivePacket 可以批量处理,减少了:
- 函数调用次数(N 次合并为 1 次);
- 锁获取/释放次数;
- CPU 缓存失效。
Q6:为什么绑定过程需要异步完成?
A :初始化可能耗时 + 不阻塞调用线程。
绑定过程涉及:
- 查找适配器;
- 验证协议兼容性;
- 初始化硬件(可能涉及固件加载);
- 分配资源(DMA 缓冲区、中断)。
这些操作可能耗时数十毫秒,如果同步等待会阻塞调用线程。NdisOpenAdapter 返回 NDIS_STATUS_PENDING,完成后通过 OpenAdapterCompleteHandler 回调通知协议驱动。这种设计允许协议驱动在绑定完成前执行其他任务。
Q7:为什么 NDIS_BUFFER 实际上是 MDL?
A :DMA 兼容性 + 内存映射支持。
NDIS_BUFFER 是 MDL(Memory Descriptor List)的别名。MDL 描述了一段虚拟地址对应的物理页帧,这正是 DMA 操作所需的。当 Miniport 驱动需要发送数据时,它从 NDIS_PACKET 中提取 NDIS_BUFFER 链,构造 PRDT(Physical Region Descriptor Table),然后启动 DMA 传输。
使用 MDL 的好处:
- 支持分散/聚集 I/O(数据可能分布在多个物理页);
- 直接映射到用户态缓冲区(零拷贝);
- 与 Windows 内存管理无缝集成。
Q8:为什么 NDIS 5.x 需要 PnP 支持?
A :热插拔设备 + 动态配置。
NDIS 5.x 之前的版本(如 NDIS 3.x)不支持 PnP,适配器必须在系统启动时存在。随着 USB 网卡、PCMCIA 网卡的普及,热插拔变得必不可少。PnP 支持允许:
- 动态识别新插入的网卡;
- 自动加载对应驱动;
- 在不重启系统的情况下配置网络;
- 安全移除设备。
在 miniport.c(file:///d:/reactos/drivers/network/ndis/ndis/miniport.c) 中可以看到 IRP_MN_START_DEVICE 的处理。
Q9:为什么需要中间驱动(IM)层?
A :协议无关的流量处理 + 功能扩展。
IM 驱动位于 Protocol 和 Miniport 之间,可以:
- 过滤数据包(防火墙);
- 修改数据包(QoS 标记);
- 协议转换(例如桥接不同介质);
- 监控流量(网络分析工具)。
IM 驱动对上层是"另一个 Miniport",对下层是"另一个 Protocol",这种对称性使得多个 IM 可以串联成链,每层独立演进。
Q10:为什么 ReactOS 选择实现 NDIS 5.x 而非 6.x?
A :兼容性与复杂度的权衡。
NDIS 6.x(Windows Vista+)引入了大量新特性:
- RSS(Receive Side Scaling)------多 CPU 接收分发;
- LSO(Large Send Offload)------大帧卸载;
- VMQ(Virtual Machine Queue)------虚拟化支持;
- MSI-X 中断------减少中断开销。
这些特性显著提高了性能,但实现复杂度大幅增加。ReactOS 选择实现 NDIS 5.x,理由是:
- 支持大多数 Windows 2000/XP 时代的 NIC 驱动;
- 代码量可控,易于维护;
- 满足基本网络需求(TCP/IP、文件共享、网页浏览);
- 可以在未来逐步添加 NDIS 6.x 特性。
本章代码索引
| 文件 | 内容 |
|---|---|
| main.c(file:///d:/reactos/drivers/network/ndis/ndis/main.c) | DriverEntry |
| miniport.c(file:///d:/reactos/drivers/network/ndis/ndis/miniport.c) | Miniport 接口 |
| protocol.c(file:///d:/reactos/drivers/network/ndis/ndis/protocol.c) | Protocol 接口 |
| buffer.c(file:///d:/reactos/drivers/network/ndis/ndis/buffer.c) | 缓冲操作 |
| co.c(file:///d:/reactos/drivers/network/ndis/ndis/co.c) | CoNDIS |
| cl.c(file:///d:/reactos/drivers/network/ndis/ndis/cl.c) | NDIS 5.x Client |
| cm.c(file:///d:/reactos/drivers/network/ndis/ndis/cm.c) | Call Manager |
| io.c(file:///d:/reactos/drivers/network/ndis/ndis/io.c) | I/O |
| memory.c(file:///d:/reactos/drivers/network/ndis/ndis/memory.c) | 内存 |
| object.c(file:///d:/reactos/drivers/network/ndis/ndis/object.c) | 对象 |
| time.c(file:///d:/reactos/drivers/network/ndis/ndis/time.c) | 计时器 |
| workitem.c(file:///d:/reactos/drivers/network/ndis/ndis/workitem.c) | 工作项 |