Reactos 第 10 章 网络操作 — 10.3.2 LAN驱动模块

第 10 章 网络操作 --- 10.3.2 LAN驱动模块

本节剖析 LAN Manager(lan.sys)协议驱动的实现。 LAN Manager 是 NDIS 协议驱动 ,在网络栈中位于 TCP/IP 协议栈NDIS 库 之间。ReactOS 的 lan.sys 实现在 drivers/network/lan/lan/(file:///d:/reactos/drivers/network/lan/lan/),主要源文件 lan.croutines.cinfo.creceive.c 等。理解 LAN Manager 的关键是把握 TDI 上层接口 + NDIS Protocol 下层接口 的桥接。


概述

LAN Manager 在 ReactOS 网络栈中扮演 "协议驱动+下层通信" 双重角色:

  1. 作为 NDIS 协议驱动:通过 NDIS 协议 API 接收/发送网络包
  2. 作为 TDI 提供者:通过 TDI 把 TCP/IP 的请求转发给 NIC

实际上,TCP/IP 协议栈tcpip.sys)也使用 NDIS 协议 API,而 lan.sys 在某些版本中作为 中间层 (实际 ReactOS 中 tcpip.sys 是 NDIS 协议驱动,不依赖 lan.sys)。

NDIS 协议驱动的工作原理

NDIS 协议驱动的核心思想是对称性 :Miniport 驱动向下对接硬件,协议驱动向上对接网络栈,两者通过 NDIS 库在中间"握手"。协议驱动通过 NdisRegisterProtocol 向 NDIS 注册一组回调函数,这些回调与 Miniport 的回调形成镜像关系------Miniport 调用 NdisMSendComplete 通知发送完成,NDIS 就调用协议的 SendComplete 回调;Miniport 调用 NdisMIndicateReceivePacket 上报接收包,NDIS 就调用协议的 ReceivePacket 回调。

这种对称设计使得 NDIS 库成为完全中立的"消息总线":它不关心包的内容,只负责在 Miniport 和 Protocol 之间传递。一个 Miniport 可以同时绑定多个协议(如 TCP/IP、IPX、NetBEUI),一个协议也可以绑定多个 Miniport(多网卡场景)。NDIS 通过绑定句柄(BindingHandle)区分不同的绑定关系。

TDI(Transport Driver Interface)的角色

TDI 是 Windows NT 内核中传输层驱动的标准接口,位于协议驱动(如 TCP/IP)和用户态 Winsock 之间。TDI 定义了一组 IRP(I/O Request Packet)代码,如 IRP_MJ_CREATE(打开连接端点)、IRP_MJ_WRITE(发送数据)、IRP_MJ_READ(接收数据)、IRP_MJ_DEVICE_CONTROL(控制命令)。

在 ReactOS 中,TDI 提供者(tcpip.sys)接收来自 AFD(Ancillary Function Driver,Winsock 的内核态后端)的 TDI 请求,将其转换为 NDIS 包的发送操作。这个过程涉及多次数据拷贝和上下文切换:

复制代码
用户态 send() → AFD (IRP_MJ_WRITE) → TDI → tcpip.sys → NdisSend → Miniport → NIC

lan.sys 在早期 Windows NT 中充当这个链条中的"协议驱动"角色,即 TDI 请求先到达 lan.sys,再由 lan.sys 通过 NDIS 协议 API 转发。但在 ReactOS 的当前架构中,tcpip.sys 基于 lwIP 实现,直接调用 NDIS 协议 API,跳过了 lan.sys 这一层。

协议绑定的生命周期

协议驱动与 Miniport 的绑定经历以下状态转换:

  1. 注册阶段DriverEntry 调用 NdisRegisterProtocol,NDIS 在全局协议列表中创建 NDIS_PROTOCOL_BLOCK,保存所有回调函数指针。

  2. 枚举阶段 :NDIS 维护一个已安装适配器的列表(通过注册表 HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Linkage 中的 Bind 键值)。协议驱动读取这个列表,对每个适配器调用 NdisOpenAdapter

  3. 绑定阶段NdisOpenAdapter 触发异步操作。NDIS 查找对应的 Miniport 驱动,如果 Miniport 尚未初始化则先调用 MiniportInitialize。完成后调用协议的 OpenAdapterComplete 回调,传递绑定句柄。

  4. 运行阶段 :协议通过绑定句柄调用 NdisSendNdisRequest(OID 查询)等 API。NDIS 通过回调通知协议接收事件、状态变化。

  5. 解绑阶段 :系统关闭或适配器移除时,协议调用 NdisCloseAdapter。NDIS 等待所有未完成的发送/请求完成后,调用 CloseAdapterComplete,释放绑定资源。

ReactOS 中 lan.sys 与 tcpip.sys 的关系

ReactOS 的网络栈有两种数据路径:

路径 A(直接路径,主要路径)

复制代码
AFD → TDI → tcpip.sys (lwIP) → NDIS Protocol API → NDIS → Miniport → NIC

路径 B(通过 lan.sys,遗留路径)

复制代码
AFD → TDI → tcpip.sys → lan.sys → NDIS Protocol API → NDIS → Miniport → NIC

在路径 A 中,tcpip.sys 自身实现了 NDIS 协议驱动接口(通过 NdisRegisterProtocol),直接与 NDIS 交互。这是 ReactOS 当前的主要路径,也是 Windows XP 及以后版本的标准架构。

在路径 B 中,tcpip.sys 将包传递给 lan.sys,由 lan.sys 作为 NDIS 协议驱动转发。这是 Windows NT 3.x/4.0 的遗留架构,ReactOS 保留 lan.sys 以兼容这种设计。

两条路径的选择取决于 tcpip.sys 的编译配置。在 ReactOS 的当前构建中,默认使用路径 A,lan.sys 作为备用组件存在。

协议驱动的并发模型

NDIS 协议驱动必须处理复杂的并发场景:多个线程可能同时发送数据,网卡中断可能随时触发接收回调,适配器状态可能因链路断开而突然改变。ReactOS 的协议驱动使用以下同步机制:

自旋锁(SpinLock):保护适配器状态和发送队列。协议驱动在发送包时获取自旋锁,防止多个线程同时操作同一个适配器的发送队列。接收回调在 DPC(延迟过程调用)级别执行,也需要获取自旋锁才能访问适配器结构。

引用计数 :跟踪未完成的发送请求。协议驱动在调用 NdisSend 前递增引用计数,在 SendComplete 回调中递减。解绑时等待引用计数归零,确保所有发送请求都已完成。

事件对象(KEVENT) :用于同步异步操作。NdisOpenAdapterNdisCloseAdapter 是异步的,协议驱动创建事件对象,在 OpenAdapterCompleteCloseAdapterComplete 回调中设置事件,主线程等待事件完成。

OID(Object Identifier)查询与设置

NDIS 使用 OID 机制查询和设置适配器的配置参数。协议驱动通过 NdisRequest 发起 OID 请求,NDIS 转发给 Miniport 处理,完成后通过 RequestComplete 回调返回结果。

常见的 OID 包括:

  • OID_GEN_MAC_OPTIONS:查询 MAC 层选项(如是否支持多播、广播)
  • OID_GEN_CURRENT_LOOKAHEAD:设置接收前看大小(默认 256 字节)
  • OID_802_3_CURRENT_ADDRESS:查询/设置当前 MAC 地址
  • OID_GEN_LINK_SPEED:查询链路速度(单位 100bps)
  • OID_GEN_MEDIA_CONNECT_STATUS:查询介质连接状态(连接/断开)

协议驱动在绑定完成后通常会查询这些参数,缓存到适配器结构中。例如,lan.sysLanOpenAdapter 完成后查询 OID_GEN_MAC_OPTIONS,判断适配器是否支持多播,决定是否注册多播地址。

多播(Multicast)处理

以太网多播允许一个网卡接收发往特定多播 MAC 地址的包。协议驱动需要管理多播地址列表,通过 OID 设置通知 Miniport 配置网卡的接收过滤器。

ReactOS 的协议驱动维护一个多播地址列表(MULTICAST_ADDRESS 结构链表)。当上层协议(如 IGMP)需要加入多播组时,协议驱动调用 NdisSetInformation 设置 OID_802_3_MULTICAST_LIST,NDIS 转发给 Miniport,Miniport 配置网卡的接收过滤器(如 dc21x4 的 CSR 寄存器中的多播位掩码)。

多播地址的数量受网卡硬件限制。dc21x4 支持 512 位的多播位掩码,可以过滤 512 个多播地址。如果多播地址超过硬件限制,Miniport 会启用"全多播模式",接收所有多播包,由协议驱动在软件中过滤。

电源管理与唤醒

NDIS 5.0 引入了电源管理支持。协议驱动可以注册 ProtocolStatus 回调,接收 NDIS_STATUS_MEDIA_CONNECTNDIS_STATUS_MEDIA_DISCONNECT 通知,感知链路状态变化。

当系统进入休眠(S3 状态)时,NDIS 调用协议的 ProtocolPnPEvent 回调,通知协议适配器即将进入低功耗状态。协议驱动可以:

  1. 保存适配器状态(如 MAC 地址、多播列表)
  2. 配置唤醒模式(如 Wake-on-LAN,接收特定魔术包后唤醒系统)
  3. 释放不必要的资源

当系统恢复时,NDIS 再次调用 ProtocolPnPEvent,协议驱动恢复适配器状态,重新初始化硬件。

ReactOS 的 lan.sys 实现了基本的电源管理支持,但不包括 Wake-on-LAN 功能。

本节内容概览

  • 10.3.2.0 框架图
  • 10.3.2.1 LAN Manager 角色
  • 10.3.2.2 lan.c 主入口
  • 10.3.2.3 NdisRegisterProtocol 注册
  • 10.3.2.4 ProtocolOpenAdapter / ProtocolCloseAdapter
  • 10.3.2.5 ProtocolSend / ProtocolSendComplete
  • 10.3.2.6 ProtocolReceivePacket 接收
  • 10.3.2.7 总结与代码索引

学习目标

  • 理解 LAN Manager 在协议栈中的位置
  • 掌握 NDIS Protocol API 的使用
  • 知道 NdisRegisterProtocol 注册的回调
  • 跟踪包从 TCP/IP 到 NIC 的路径

涉及的内核子系统

子系统 头文件/源文件 核心作用
LAN Manager 主 drivers/network/lan/lan/lan.c(file:///d:/reactos/drivers/network/lan/lan/lan.c) DriverEntry、协议注册
LAN Routines drivers/network/lan/lan/routines.c(file:///d:/reactos/drivers/network/lan/lan/routines.c) 通用例程
LAN Info drivers/network/lan/lan/info.c(file:///d:/reactos/drivers/network/lan/lan/info.c) OID 查询/设置
LAN 头 drivers/network/lan/lan/lan.h(file:///d:/reactos/drivers/network/lan/lan/lan.h) 数据结构
NDIS drivers/network/ndis/ndis/(file:///d:/reactos/drivers/network/ndis/ndis/) NDIS 库

10.3.2.0 框架图

复制代码
+-------------------------------------+
| TCP/IP 协议栈 (tcpip.sys)           |
+-------------------------------------+
              |  (TCP/IP 也可直接走 NDIS Protocol)
              |
              v
+-------------------------------------+
| LAN Manager (lan.sys)               |  本节主题
| - NDIS 协议驱动                      |
| - 通过 NdisSend 发送包              |
| - 接收回调把包给上层                |
+-------------------------------------+
              | NdisSend / NdisReceive
              v
+-------------------------------------+
| NDIS 库                             |
+-------------------------------------+
              |
              v
+-------------------------------------+
| NIC Miniport                         |
+-------------------------------------+

10.3.2.1 LAN Manager 角色

LAN Manager 是 NDIS 协议驱动,它的核心工作流:

  1. 初始化 :调用 NdisRegisterProtocol 注册为 NDIS 协议
  2. 绑定适配器 :调用 NdisOpenAdapter 绑定到特定 NIC
  3. 发送 :通过 NdisSend 发送包
  4. 接收 :在 ProtocolReceivePacket 回调中接收包
  5. 关闭 :调用 NdisCloseAdapter 解绑

与 TCP/IP 的关系

ReactOS 中 TCP/IP(tcpip.sys直接 是 NDIS 协议驱动(基于 lwIP)。lan.sys 作为 协议驱动 与 TCP/IP 并列,都绑定到 NIC。

Windows NT 历史上tcpip.sys 内部使用 lan.sys(LAN Manager)作为 NDIS 协议驱动,这种"两层协议"结构是为了支持多协议栈共存。


10.3.2.2 lan.c 主入口

lan.c(file:///d:/reactos/drivers/network/lan/lan/lan.c):

c 复制代码
NTSTATUS NTAPI
DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
    NDIS_PROTOCOL_CHARACTERISTICS ProtChars = {0};
    NDIS_HANDLE NdisProtocolHandle;
    NDIS_STATUS Status;
    
    ProtChars.MajorNdisVersion = 5;
    ProtChars.MinorNdisVersion = 0;
    
    // 注册回调
    ProtChars.OpenAdapterCompleteHandler = LanOpenAdapterComplete;
    ProtChars.CloseAdapterCompleteHandler = LanCloseAdapterComplete;
    ProtChars.SendCompleteHandler = LanSendComplete;
    ProtChars.TransferDataCompleteHandler = LanTransferDataComplete;
    ProtChars.ResetCompleteHandler = LanResetComplete;
    ProtChars.RequestCompleteHandler = LanRequestComplete;
    ProtChars.ReceiveHandler = LanReceive;
    ProtChars.ReceiveCompleteHandler = LanReceiveComplete;
    ProtChars.StatusHandler = LanStatus;
    ProtChars.StatusCompleteHandler = LanStatusComplete;
    
    NdisRegisterProtocol(&Status, &NdisProtocolHandle, &ProtChars, sizeof(ProtChars));
    return Status;
}

协议驱动回调

回调 何时调用
OpenAdapterComplete NdisOpenAdapter 异步完成
CloseAdapterComplete NdisCloseAdapter 异步完成
SendComplete NdisSend 异步完成
TransferDataComplete NdisTransferData 异步完成
ResetComplete 总线复位完成
RequestComplete OID 请求完成
Receive 包到达(NDIS 4 风格)
ReceiveComplete NdisMIndicateReceiveComplete 后调用
Status 状态变化(链接 down 等)

10.3.2.3 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');
    RtlZeroMemory(ProtBlock, sizeof(NDIS_PROTOCOL_BLOCK));
    
    // 2. 复制回调
    RtlCopyMemory(&ProtBlock->ProtocolCharacteristics, ProtocolCharacteristics, CharacteristicsLength);
    
    // 3. 关联 DriverObject
    ProtBlock->DriverObject = ...;  // 由调用方提供
    
    // 4. 加入全局列表
    InsertHeadList(&ProtocolListHead, &ProtBlock->ProtocolListEntry);
    
    *NdisProtocolHandle = ProtBlock;
    *Status = NDIS_STATUS_SUCCESS;
}

注册后,LAN Manager 获得 NdisProtocolHandle,后续用此句柄调用 NdisOpenAdapter 等 API。


10.3.2.4 ProtocolOpenAdapter / ProtocolCloseAdapter

绑定 NIC

LAN Manager 通过 NdisOpenAdapter 绑定到 NIC:

c 复制代码
NDIS_STATUS LanOpenAdapter(NDIS_HANDLE NdisProtocolHandle,
                            NDIS_HANDLE ProtocolBindingContext,
                            PNDIS_OPEN_BLOCK OpenBlock,
                            PNDIS_BIND_PARAMETERS BindParameters)
{
    PLAN_ADAPTER Adapter;
    NDIS_STATUS Status;
    
    // 1. 分配 Adapter 结构
    Adapter = ExAllocatePoolWithTag(NonPagedPool, sizeof(LAN_ADAPTER), 'panL');
    RtlZeroMemory(Adapter, sizeof(LAN_ADAPTER));
    
    // 2. 保存绑定的 ProtocolBindingContext
    Adapter->ProtocolBindingContext = ProtocolBindingContext;
    Adapter->SelectedMedium = BindParameters->SelectedMediumIndex;
    
    // 3. 缓存介质类型
    Adapter->Medium = BindParameters->Medium;
    
    // 4. 保存 Adapter 句柄
    Adapter->NdisAdapterHandle = BindParameters->BindingHandle;
    
    return NDIS_STATUS_SUCCESS;
}

ProtocolBindingContext 是协议驱动在绑定时关联的私有上下文。


10.3.2.5 ProtocolSend / ProtocolSendComplete

LanSend(同步发送)

routines.c(file:///d:/reactos/drivers/network/lan/lan/routines.c):

c 复制代码
NDIS_STATUS LanSend(NDIS_HANDLE NdisBindingContext, NDIS_HANDLE NdisPacket) {
    PLAN_ADAPTER Adapter = NdisBindingContext;
    PNDIS_PACKET Packet = NdisPacket;
    
    // 调用 NDIS 协议 API 发送
    NdisSend(&Status, Adapter->NdisAdapterHandle, Packet);
    
    return Status;
}

LanSendComplete(发送完成)

c 复制代码
VOID LanSendComplete(NDIS_HANDLE NdisBindingContext, PNDIS_PACKET Packet, NDIS_STATUS Status) {
    // 1. 回收包到池
    NdisFreePacket(Packet);
    
    // 2. 通知上层(TCP/IP)
    // 通过 TDI 通知或回调
}

完整发送流程

复制代码
TCP/IP 内部 lwIP 调用
    |
    v
NdisSend 宏展开
    |
    v
NDIS 库
    |
    v
调用 Miniport 的 Send 回调
    |
    v
NIC 发送
    |
    v
中断 -> Miniport 调用 NdisMSendComplete
    |
    v
NDIS 库
    |
    v
LanSendComplete

10.3.2.6 ProtocolReceivePacket 接收

c 复制代码
INT LanReceivePacket(NDIS_HANDLE ProtocolBindingContext, PNDIS_PACKET Packet) {
    PLAN_ADAPTER Adapter = ProtocolBindingContext;
    
    // 1. 验证包
    if (!Adapter || !Adapter->ReceiveHandler) {
        return 0;  // 拒绝
    }
    
    // 2. 解析包(取数据)
    PNDIS_BUFFER FirstBuffer;
    UINT TotalLength;
    NdisQueryPacket(Packet, NULL, NULL, &FirstBuffer, &TotalLength);
    
    PVOID Buffer;
    UINT Length;
    NdisQueryBuffer(FirstBuffer, &Buffer, &Length);
    
    // 3. 调用上层接收处理(TCP/IP 内部)
    Adapter->ReceiveHandler(Adapter->ReceiveContext, Buffer, Length, ...);
    
    // 4. 返回实际处理的字节数
    return Length;
}

LanReceiveComplete

c 复制代码
VOID LanReceiveComplete(NDIS_HANDLE ProtocolBindingContext) {
    // 上层(TCP/IP)可处理完整接收
    if (Adapter->ReceiveCompleteHandler) {
        Adapter->ReceiveCompleteHandler(Adapter->ReceiveContext);
    }
}

10.3.2.7 总结

关键要点

  1. LAN Manager 是 NDIS 协议驱动 :使用 NdisRegisterProtocol 注册
  2. 回调族:Open/Close/Send/Receive/Status 等 10+ 回调
  3. NdisSend 发送包LanSend 调用 NDIS 协议 API
  4. ProtocolReceivePacket 接收包:把数据传递给 TCP/IP
  5. TCP/IP 也使用 NDIS 协议 API :实际不一定经过 lan.sys
  6. 适配器结构PLAN_ADAPTER 维护绑定状态

LAN Manager 驱动的深度技术分析

LAN Manager(lan.sys)在 ReactOS 网络栈中扮演着协议驱动的角色,是理解 Windows 网络驱动架构的关键组件。本节深入分析 LAN Manager 的设计背景、实现细节以及在 ReactOS 网络栈中的定位。

LAN Manager 的历史背景

LAN Manager 起源于 IBM 和 Microsoft 联合开发的 LAN Manager 网络操作系统。在 Windows NT 3.1 中,LAN Manager 是一个用户态服务,负责管理网络重定向器(Redirector)和服务器服务。随着 NT 内核的演进,LAN Manager 的核心功能逐渐移入内核态,lan.sys 成为内核态的 NDIS 协议驱动。

在 Windows NT 4.0 和 Windows 2000 中,lan.sys 作为"中间层"协议驱动,位于网络协议栈(如 TCP/IP、IPX)和 NDIS 之间。它的主要职责是:

  1. 为上层协议驱动提供统一的 NDIS 接口抽象
  2. 管理多个网络适配器的绑定
  3. 处理 NetBIOS over NDIS 的协议转换

ReactOS 中 lan.sys 的实际角色

在 ReactOS 的当前实现中,lan.sys 的角色有些特殊。从源代码分析来看,ReactOS 的 TCP/IP 协议栈(tcpip.sys)直接实现了 NDIS 协议驱动接口,不依赖 lan.sys 作为中间层。lan.sys 更多地作为一个独立的 NDIS 协议驱动存在,与 TCP/IP 并列绑定到 NIC。

这种架构与 Windows NT 3.x/4.0 的设计一致,但与 Windows 2000/XP 的设计有所不同。在 Windows XP 中,tcpip.sys 内部使用 ndis.sys 的协议 API,不需要 lan.sys 作为中介。ReactOS 的实现反映了这种更简洁的架构。

协议驱动注册与回调机制

lan.sys 通过 NdisRegisterProtocol 向 NDIS 库注册一组回调函数。这些回调函数覆盖了协议驱动生命周期的所有阶段:

  1. 适配器绑定回调

    • OpenAdapterCompleteNdisOpenAdapter 异步完成时调用
    • CloseAdapterCompleteNdisCloseAdapter 异步完成时调用
  2. 数据传输回调

    • SendCompleteNdisSend 异步完成时调用
    • TransferDataCompleteNdisTransferData 异步完成时调用
    • Receive:NDIS 3.x/4.x 风格的包接收
    • ReceiveComplete:一批包接收完成的通知
  3. 状态回调

    • Status:状态变化通知(如链路状态改变)
    • StatusComplete:状态变化处理完成
    • ResetComplete:适配器复位完成
  4. 请求回调

    • RequestComplete:OID 查询/设置请求完成

这些回调的异步特性是 NDIS 设计的核心。NDIS 的大多数操作都是异步的------调用方发起请求后立即返回,NDIS 库在操作完成后通过回调通知调用方。这种设计允许 NDIS 在单线程环境中高效处理多个并发操作。

适配器绑定过程详解

lan.sys 绑定到一个网络适配器时,经历以下步骤:

  1. 协议注册DriverEntry 调用 NdisRegisterProtocol,将回调函数注册到 NDIS 全局协议列表。

  2. 适配器枚举:NDIS 库通知协议驱动可用的适配器列表。协议驱动选择要绑定的适配器。

  3. 打开适配器 :协议驱动调用 NdisOpenAdapter,传入协议句柄和适配器名称。NDIS 库查找对应的 Miniport 驱动,调用 MiniportInitialize 初始化硬件。

  4. 异步完成 :Miniport 初始化完成后,NDIS 调用协议的 OpenAdapterComplete 回调,传递绑定句柄和状态。

  5. 介质检查 :NDIS 告知协议选中的介质类型(如 NdisMedium802_3 表示以太网)。协议验证支持的介质类型。

在 ReactOS 的 lan.sys 中,绑定过程在 LanOpenAdapter 函数中处理:

c 复制代码
NDIS_STATUS LanOpenAdapter(NDIS_HANDLE NdisProtocolHandle,
                            NDIS_HANDLE ProtocolBindingContext,
                            PNDIS_OPEN_BLOCK OpenBlock,
                            PNDIS_BIND_PARAMETERS BindParameters)
{
    PLAN_ADAPTER Adapter;
    
    // 分配适配器上下文
    Adapter = ExAllocatePoolWithTag(NonPagedPool, sizeof(LAN_ADAPTER), 'panL');
    
    // 保存绑定参数
    Adapter->ProtocolBindingContext = ProtocolBindingContext;
    Adapter->NdisAdapterHandle = BindParameters->BindingHandle;
    Adapter->Medium = BindParameters->Medium;
    
    return NDIS_STATUS_SUCCESS;
}

数据接收的 Lookahead 机制

NDIS 的接收机制使用"前看(Lookahead)"优化。当 Miniport 收到一个包时,它不需要将整个包传递给协议驱动,而是先传递包头部的一部分(前看数据)。协议驱动可以根据头部信息决定是否接受这个包。

c 复制代码
INT MyReceive(NDIS_HANDLE ProtocolBindingContext,
              NDIS_HANDLE MacReceiveContext,
              UCHAR HeaderBuffer[], UINT HeaderBufferSize,
              UCHAR LookaheadBuffer[], UINT LookaheadBufferSize,
              UINT PacketSize)
{
    // 检查以太网目标地址
    if (IsMulticast(HeaderBuffer)) {
        // 检查是否注册了该多播地址
        if (!IsRegisteredMulticast(...))
            return 0;  // 拒绝
    }
    
    // 检查 IP 协议类型
    USHORT EtherType = *(USHORT*)(HeaderBuffer + 12);
    if (EtherType != 0x0800)  // 不是 IP
        return 0;  // 拒绝
    
    // 接受包,调用 NdisTransferData 获取完整数据
    NdisTransferData(&Status, BindingHandle, MacReceiveContext,
                     0, Packet, &BytesTransferred);
    return PacketSize;
}

前看大小由 OID_GEN_CURRENT_LOOKAHEAD 控制,通常为 256 字节。对于以太网帧(最大 1514 字节),如果包大于前看大小,协议驱动需要调用 NdisTransferData 获取剩余数据。

NDIS 4.0 的 ReceivePacket 改进

NDIS 4.0 引入了 ReceivePacket 回调,替代了旧的 Receive + NdisTransferData 模式。ReceivePacket 直接将完整的 NDIS_PACKET 传递给协议驱动,避免了额外的数据复制:

c 复制代码
INT MyReceivePacket(NDIS_HANDLE ProtocolBindingContext, PNDIS_PACKET Packet)
{
    // 直接访问包数据
    PNDIS_BUFFER Buffer;
    UINT TotalLength;
    NdisQueryPacket(Packet, NULL, NULL, &Buffer, &TotalLength);
    
    PVOID Data;
    UINT DataLength;
    NdisQueryBuffer(Buffer, &Data, &DataLength);
    
    // 处理数据包
    ProcessPacket(Data, DataLength);
    
    return TotalLength;  // 返回处理的字节数
}

ReactOS 的 lan.sys 同时支持两种接收模式,通过检查 NDIS 版本和回调是否注册来决定使用哪种模式。

与 Windows lan.sys 的对比

ReactOS 的 lan.sys 实现与 Windows 存在以下差异:

  1. 功能范围 :Windows 的 lan.sys 包含 NetBIOS 支持和 SMB 重定向器功能。ReactOS 的实现更加精简,专注于 NDIS 协议驱动的基本功能。

  2. 源文件结构 :ReactOS 的 lan.sys 只有 lan.croutines.c 两个主要源文件,而 Windows 的实现包含更多模块。

  3. NDIS 版本支持 :ReactOS 的 lan.sys 主要针对 NDIS 5.0,不包含 NDIS 6.x 的 NetBuffer 支持。

lan.sys 在网络栈中的实际数据流

在 ReactOS 的当前架构中,lan.sys 的数据流路径为:

发送方向:

复制代码
上层协议 → lan.sys LanSend → NdisSend → NDIS 库 → Miniport → NIC

接收方向:

复制代码
NIC → Miniport → NDIS 库 → lan.sys LanReceivePacket → 上层协议

实际上,由于 tcpip.sys 直接实现了 NDIS 协议驱动,大多数网络流量不经过 lan.syslan.sys 主要服务于其他需要 NDIS 协议接口的组件。

LAN_ADAPTER 结构的详细分析

PLAN_ADAPTERlan.sys 的核心数据结构,维护适配器的运行时状态:

c 复制代码
typedef struct _LAN_ADAPTER {
    LIST_ENTRY ListEntry;              // 适配器列表链接
    NDIS_HANDLE NdisAdapterHandle;     // NDIS 绑定句柄
    NDIS_HANDLE ProtocolBindingContext; // 协议绑定上下文
    NDIS_MEDIUM Medium;                // 介质类型
    NDIS_STATUS Status;                // 当前状态
    
    // 接收处理
    PVOID ReceiveHandler;              // 上层接收回调
    PVOID ReceiveContext;              // 接收上下文
    PVOID ReceiveCompleteHandler;      // 接收完成回调
    
    // 统计
    ULONG PacketsSent;
    ULONG PacketsReceived;
    ULONG SendErrors;
} LAN_ADAPTER;

这个结构在适配器绑定时分配,在适配器解绑时释放。它作为协议驱动和上层之间的桥梁,保存了通信所需的所有上下文信息。

10 问为什么

Q1:为什么 NDIS 协议驱动需要注册这么多回调函数,而不是使用简单的同步 API?

因为网络操作本质上是异步的。发送一个包可能需要几微秒到几毫秒(取决于 NIC 的 DMA 队列长度),如果协议驱动同步等待,CPU 会被浪费在空转上。NDIS 的回调机制允许协议驱动发起操作后立即返回,继续处理其他任务,NDIS 在操作完成后通过回调通知协议驱动。这种设计使得 NDIS 能够在单线程环境中高效处理多个并发操作,最大化 CPU 利用率。

Q2:为什么一个 Miniport 可以同时绑定多个协议驱动?

因为 NDIS 采用"一对多"的绑定模型。一个网卡(Miniport)可以同时接收来自 TCP/IP、IPX、NetBEUI 等多个协议的数据包。NDIS 通过维护一个绑定列表,将每个协议驱动与 Miniport 关联。当 Miniport 收到数据包时,NDIS 将包复制给所有绑定的协议驱动,由每个协议驱动根据自己的过滤规则决定是否接受。这种设计支持多协议栈共存,是 Windows NT 早期网络架构的核心特性。

Q3:为什么协议驱动需要维护引用计数来跟踪未完成的发送请求?

因为 NdisSend 是异步操作,调用后立即返回,实际的发送由 NIC 硬件在后台完成。如果协议驱动在发送未完成时解绑适配器,会导致 NIC 访问已释放的内存,引发系统崩溃。引用计数确保协议驱动在解绑前等待所有发送请求完成。每次调用 NdisSend 递增计数,每次 SendComplete 回调递减计数,解绑时等待计数归零。

Q4:为什么 NDIS 使用 OID(Object Identifier)机制查询适配器状态,而不是直接访问寄存器?

因为 OID 提供了硬件抽象层。不同的网卡使用不同的寄存器布局和状态报告方式,如果协议驱动直接访问寄存器,就必须为每种网卡编写特定代码。OID 将硬件状态抽象为标准化的查询接口(如 OID_GEN_LINK_SPEED 表示链路速度),协议驱动只需调用 NdisRequest 查询 OID,NDIS 将请求转发给 Miniport,Miniport 读取硬件寄存器并返回结果。这样协议驱动完全不需要知道硬件细节。

Q5:为什么协议驱动需要使用自旋锁保护发送队列?

因为多个线程可能同时调用 NdisSend 发送数据,而 NIC 的发送描述符环是共享资源。如果两个线程同时修改描述符环的指针,会导致数据损坏或发送失败。自旋锁确保同一时刻只有一个线程能访问发送队列。自旋锁运行在 DIRQL(设备中断请求级别),可以防止接收中断打断发送操作,保证发送队列的一致性。

Q6:为什么 ReactOS 中 tcpip.sys 可以直接调用 NDIS 协议 API,而不经过 lan.sys?

因为 ReactOS 采用了 Windows XP 及以后版本的简化架构。在 Windows NT 3.x/4.0 中,tcpip.sys 通过 lan.sys 作为中间层调用 NDIS,这种设计是为了支持多协议栈共存和遗留兼容性。但在 Windows XP 中,Microsoft 简化了网络栈,tcpip.sys 直接实现 NDIS 协议驱动接口,减少了数据拷贝和上下文切换的开销。ReactOS 的 tcpip.sys 基于 lwIP 实现,采用了同样的直接路径设计,提高了网络性能。

Q7:为什么 NDIS 的接收机制使用"前看(Lookahead)"优化?

因为以太网帧的最大长度是 1514 字节,但大多数协议(如 TCP/IP)只需要检查帧头部的前几十个字节就能决定是否接受这个包。如果 Miniport 每次都传递完整的 1514 字节给协议驱动,会浪费大量 CPU 时间在数据拷贝上。前看机制允许 Miniport 只传递帧头部的一部分(默认 256 字节),协议驱动根据头部信息快速过滤,只接受需要的包,然后调用 NdisTransferData 获取完整数据。这种设计减少了不必要的数据拷贝,提高了接收性能。

Q8:为什么协议驱动需要处理多播地址列表,而不是让网卡接收所有多播包?

因为多播地址的数量可能非常大(IPv6 多播地址有 2^112 个),如果网卡接收所有多播包,会占用大量 CPU 时间处理不需要的包。协议驱动维护一个多播地址列表,通过 OID 设置通知 Miniport 配置网卡的接收过滤器(如 dc21x4 的 512 位多播位掩码),网卡只接收列表中的多播地址,过滤掉其他多播包。这种硬件过滤机制大大减少了 CPU 负担。

Q9:为什么 NDIS 5.0 引入了电源管理支持?

因为移动设备(笔记本电脑)需要在电池供电下延长续航时间。当系统进入休眠(S3 状态)时,网卡可以进入低功耗模式,停止发送和接收数据。NDIS 5.0 的电源管理 API 允许协议驱动在系统休眠前保存适配器状态,配置唤醒模式(如 Wake-on-LAN,接收特定魔术包后唤醒系统),在系统恢复时重新初始化硬件。这种设计使得网络设备能够支持电源管理,减少电池消耗。

Q10:为什么 ReactOS 保留 lan.sys 作为遗留组件,而不是完全删除?

因为 ReactOS 的目标是兼容 Windows NT/2000/XP 的驱动模型。虽然当前的 tcpip.sys 直接调用 NDIS 协议 API,但某些遗留的网络组件(如 NetBIOS over NDIS、旧版网络协议)可能依赖 lan.sys 作为中间层。保留 lan.sys 确保 ReactOS 能够支持这些遗留组件,提高与 Windows 应用程序的兼容性。此外,lan.sys 作为一个独立的 NDIS 协议驱动,可以用于测试 NDIS 协议 API 的完整性,验证 NDIS 库的正确性。


本章代码索引

文件 内容
lan.c(file:///d:/reactos/drivers/network/lan/lan/lan.c) DriverEntry、协议注册
routines.c(file:///d:/reactos/drivers/network/lan/lan/routines.c) LanSend 等通用例程
info.c(file:///d:/reactos/drivers/network/lan/lan/info.c) OID 处理
lan.h(file:///d:/reactos/drivers/network/lan/lan/lan.h) 数据结构
ndis/protocol.c(file:///d:/reactos/drivers/network/ndis/ndis/protocol.c) NdisRegisterProtocol
相关推荐
DeboPXK1 小时前
NSK VH25EM 高防尘法兰型导轨技术手册
服务器·网络·数据库·经验分享·规格说明书
超级赛博搬砖工2 小时前
SEO代理解析:成功搜索引擎抓取你需要了解的事项
大数据·运维·服务器·网络
换个昵称都难2 小时前
webrtc peerconnection_server 模块介绍
运维·服务器·webrtc
isyangli_blog2 小时前
SDN 基本应用实践 —— 使用命令行实现简易防火墙功能实验报告
服务器·php·apache
行走__Wz2 小时前
【网工入门-eNSP模拟-07】单臂路由
网络·智能路由器
vx-Biye_Design2 小时前
springboot安阳地区研学旅游服务小程序-计算机毕业设计源码12785
java·vue.js·windows·spring boot·tomcat·maven·mybatis
gc_22992 小时前
学习在Windows中基于Docker部署Dify的步骤
windows·docker·dify
网络研究院2 小时前
德国网络安全法律与实践
网络·安全·法律·实践·德国
盟接之桥2 小时前
电子数据交换(EDI)|制造业汽车零配件场景方案
大数据·网络·人工智能·安全·低代码·汽车·制造
闪电悠米2 小时前
黑马点评-Redis ZSet-实现关注 Feed 流
服务器·网络·数据库·redis·缓存·junit·lua