Reactos 第 10 章 网络操作 — 10.2 NDIS及其实现

第 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.cminiport.cprotocol.cbuffer.cco.ccl.ccm.cio.cmemory.cobject.ctime.cworkitem.c 等。


概述

NDIS 库位于协议驱动和 Miniport 驱动之间,提供:

  1. 注册和管理:维护协议/Miniport 全局列表
  2. 通信转发:把 Protocol API 调用转发到 Miniport,反之亦然
  3. 资源抽象:包池、缓冲池、内存管理
  4. 同步与序列化:确保多协议多实例的并发安全

NDIS 库的核心架构

NDIS 库不是一个简单的"胶水层",而是一个完整的网络驱动框架。它的核心设计理念是 "协议无关性" ------ 上层协议驱动(如 TCP/IP)不需要知道底层 NIC 的具体型号和特性,下层 Miniport 驱动也不需要知道上层使用什么协议。这种解耦通过 NDIS 定义的两套标准接口实现:

  • Protocol 接口 :协议驱动调用的 API(如 NdisOpenAdapterNdisSendNdisCloseAdapter
  • Miniport 接口 :Miniport 驱动实现的回调(如 MiniportInitializeMiniportSendMiniportISR

NDIS 库的核心数据结构是三个全局链表:

c 复制代码
LIST_ENTRY ProtocolListHead;   // 所有已注册的协议驱动
LIST_ENTRY MiniportListHead;   // 所有已注册的 Miniport 驱动
LIST_ENTRY AdapterListHead;    // 所有网络适配器实例

这三个链表分别由 ProtocolListLockMiniportListLockAdapterListLock 保护,确保多处理器环境下的并发安全。当一个协议驱动调用 NdisRegisterProtocol 时,NDIS 库创建一个 NDIS_PROTOCOL_BLOCK 结构并将其插入到 ProtocolListHead;当一个 Miniport 驱动调用 NdisMRegisterMiniport 时,创建一个 NDIS_M_DRIVER_BLOCK 结构并插入到 MiniportListHead

NDIS 的四层抽象模型

从架构角度看,NDIS 库实现了四层抽象:

第一层:硬件抽象层

Miniport 驱动通过 NdisMRegisterMiniport 注册到 NDIS,提供 MiniportInitializeMiniportSendMiniportReset 等回调。NDIS 库不关心 Miniport 如何操作硬件寄存器或管理 DMA,只要求它遵守统一的接口契约。

第二层:包管理层

NDIS 库提供 NdisAllocatePacketNdisFreePacketNdisAllocateBufferNdisChainBufferAtBack 等函数,抽象了网络包的内存管理。协议驱动不需要关心包的实际存储方式,只需通过这些 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_PACKETNDIS_BUFFER)。NdisAllocateFromNPagedLookasideListExAllocatePoolWithTag 更快,因为它避免了内存管理器的锁竞争。

设计决策四:严格的 IRQL 规则

NDIS 定义了严格的 IRQL 规则:MiniportISR 在 DIRQL 执行,MiniportSend 在 DISPATCH_LEVEL 执行,ProtocolReceive 在 PASSIVE_LEVEL 执行。这种分层确保了中断处理的快速响应和协议处理的灵活性。

NDIS 与 Windows 内核的交互

NDIS 库深度集成到 Windows 内核的多个子系统:

  • PnP 子系统 :NDIS 响应 IRP_MN_START_DEVICEIRP_MN_STOP_DEVICEIRP_MN_REMOVE_DEVICE 等 PnP IRP,管理适配器的即插即用生命周期。
  • 电源管理子系统 :NDIS 支持设备电源状态转换(D0-D3),通过 MiniportSetPower 回调控制网卡的电源状态。
  • 内存管理子系统 :NDIS 使用 ExAllocatePoolWithTag 分配非分页池,使用 MmMapIoSpace 映射设备 I/O 空间。
  • 同步原语 :NDIS 使用 KeAcquireSpinLockExAcquireFastMutexKeWaitForSingleObject 等同步机制保护共享数据结构。

ReactOS NDIS 的实现特点

ReactOS 的 NDIS 实现基于 Windows NDIS 5.x 规范,具有以下特点:

  1. 兼容性优先:实现了 NDIS 5.x 的核心 API,支持大多数 Windows 2000/XP 时代的 NIC 驱动。
  2. 模块化设计 :代码按功能划分为多个模块:main.c(初始化)、miniport.c(Miniport 接口)、protocol.c(Protocol 接口)、buffer.c(缓冲区管理)、memory.c(内存分配)等。
  3. lwIP 集成 :与 lwIP TCP/IP 协议栈紧密集成,通过 NdisSendProtocolReceive 完成数据包的收发。
  4. 测试覆盖:包含单元测试和集成测试,确保核心功能的正确性。

一个典型的 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 基础上增加了:

  1. CoNDIS(Connection-oriented NDIS):支持面向连接的服务
  2. WMI:网络驱动 WMI 支持
  3. Plug and Play:热插拔支持
  4. 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 总结

关键要点

  1. NDIS 是中间层库:连接协议驱动和 Miniport 驱动
  2. 三套接口:Miniport(NIC)、IM(中间层)、Protocol(协议)
  3. DriverEntry 初始化全局列表:ProtocolList、MiniportList、AdapterList
  4. NdisMRegisterMiniport / NdisRegisterProtocol:注册驱动
  5. 包(Packet)池管理NdisAllocatePacketPool / NdisAllocatePacket / NdisFreePacket
  6. 发送/接收回调MiniportSend / ProtocolReceive
  7. NdisMIndicateReceivePacket:Miniport 通知协议

NDIS 库的深度技术分析

ReactOS 的 NDIS 库实现是一个复杂的系统工程案例,涉及并发控制、资源管理、异步 I/O 等多个核心技术领域。本节深入分析 NDIS 库的关键技术细节。

并发控制与同步机制

NDIS 库面临的核心挑战之一是并发控制。在多处理器系统中,多个协议驱动可能同时向同一个 Miniport 发送数据包,而 Miniport 的中断处理程序可能随时触发接收回调。ReactOS 采用了多层次的同步策略:

  1. 全局列表锁ProtocolListLockMiniportListLockAdapterListLock 保护全局注册列表。这些锁使用 KeInitializeSpinLock 初始化,通过 KeAcquireSpinLockKeReleaseSpinLock 进行加锁和解锁。

  2. 适配器级锁:每个适配器实例有自己的锁,保护适配器的内部状态(如发送队列、接收队列)。这种细粒度锁设计减少了锁竞争,提高了并发性能。

  3. 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;

绑定过程的关键步骤:

  1. 协议驱动调用 NdisOpenAdapter,传入协议句柄和适配器名称
  2. NDIS 库查找适配器,验证协议和适配器兼容(如介质类型匹配)
  3. 分配 ADAPTER_BINDING 结构,初始化字段
  4. 将绑定加入适配器的绑定列表和协议的绑定列表
  5. 调用 Miniport 的 MiniportInitialize 初始化硬件
  6. 异步完成后,调用协议的 OpenAdapterCompleteHandler 回调

PnP(即插即用)支持

NDIS 5.x 引入了 PnP 支持,允许网络适配器的热插拔和动态配置。ReactOS 的 PnP 实现位于 miniport.cprotocol.c 中,关键机制包括:

  1. 设备枚举:系统启动时,PnP 管理器枚举所有 PCI 设备,识别网络设备(Class Code = 0x02)。
  2. 驱动加载:根据硬件 ID 从注册表加载对应的 Miniport 驱动。
  3. 资源分配:PnP 管理器为适配器分配 I/O 端口、内存地址、IRQ 等资源。
  4. 绑定通知:当适配器启动或停止时,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 库实现了多层次的错误处理机制:

  1. Miniport 复位 :当 Miniport 检测到硬件错误(如 DMA 错误、总线超时)时,调用 NdisMReset。NDIS 库调用 Miniport 的 ResetHandler,尝试恢复硬件状态。

  2. 挂起检测 :NDIS 库定期调用 Miniport 的 CheckForHangHandler,检测硬件是否挂起。如果检测到挂起,触发复位。

  3. 错误日志 :NDIS 库通过 NdisWriteErrorLogEntry 将错误写入系统事件日志,便于诊断。

与 Windows NDIS 的差异

ReactOS 的 NDIS 实现与 Windows 存在以下差异:

  1. NDIS 版本:ReactOS 实现 NDIS 5.1,不支持 NDIS 6.x 的新特性(如 NDIS 6.0 的 NetBuffer 结构、NDIS 6.20 的 VMQ)。

  2. CoNDIS 支持 :ReactOS 包含 CoNDIS(连接导向 NDIS)代码(co.ccl.ccm.c),但实际使用较少。CoNDIS 用于支持 ATM、帧中继等面向连接的网络技术。

  3. 中间层驱动:ReactOS 对 NDIS IM(Intermediate)驱动的支持有限。IM 驱动用于实现防火墙、VPN、QoS 等功能,位于协议驱动和 Miniport 之间。

  4. 性能优化:Windows NDIS 包含大量性能优化,如接收端缩放(RSS)、大发送卸载(LSO)、校验和卸载等。ReactOS 未实现这些优化。

NDIS 库的调试支持

ReactOS 的 NDIS 库包含丰富的调试支持:

  1. 跟踪级别 :通过 DebugTraceLevel 变量控制跟踪输出的详细程度(MIN_TRACEMID_TRACEMAX_TRACE)。

  2. 包转储MiniDisplayPacket 函数可以将包内容以十六进制格式输出到调试控制台,便于分析网络流量。

  3. 断点BREAK_ON_MINIPORT_INIT 宏可以在 Miniport 初始化时触发调试器断点,便于调试驱动启动过程。

这些调试机制在开发过程中发挥了重要作用,帮助开发者定位网络栈中的问题。

NDIS 库的未来发展方向

ReactOS 的 NDIS 库仍有改进空间:

  1. NDIS 6.x 支持:逐步引入 NDIS 6.x 特性,提高与现代网卡的兼容性。
  2. 性能优化:实现 RSS、LSO 等性能优化特性,提高网络吞吐量。
  3. 安全性:加强输入验证,防止恶意网络包导致的缓冲区溢出等安全问题。
  4. 虚拟化支持:支持 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?

ADMA 兼容性 + 内存映射支持

NDIS_BUFFERMDL(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) 工作项
相关推荐
zhangfeng11331 小时前
国家超算中心 昆山站 异构加速卡1 显存16GB详细配置, 海光 Z100SM HCU
linux·网络·深度学习·c#
青瓦梦滋1 小时前
Linux:TCP协议的socket套接字
网络·网络协议·tcp/ip
烂白菜1 小时前
码道启辰:定时任务自由编排
运维·服务器·网络
梁辰兴2 小时前
计算机网络基础:对称加密密码体制
网络·计算机网络·计算机·对称加密·计算机网络基础·梁辰兴
Zhan8611242 小时前
WebSocket心跳与断线重连实战:芬兰赫尔辛基指数行情数据接口接入记录
网络·websocket·网络协议
悠悠121382 小时前
Linux 7.1 来了:新 NTFS 驱动、干掉 i486、FRED 默认开启,这次更新有点东西
linux·运维·服务器
睡不醒男孩0308232 小时前
CLup篇之达梦数据库管理
运维·服务器·数据库
BomanGe32 小时前
NSK直线导轨LH20HL替代升级指南
运维·服务器·数据库·经验分享·规格说明书
pingglala2 小时前
winscp连接linux失败解决方法
java·linux·服务器