第 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.c、routines.c、info.c、receive.c 等。理解 LAN Manager 的关键是把握 TDI 上层接口 + NDIS Protocol 下层接口 的桥接。
概述
LAN Manager 在 ReactOS 网络栈中扮演 "协议驱动+下层通信" 双重角色:
- 作为 NDIS 协议驱动:通过 NDIS 协议 API 接收/发送网络包
- 作为 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 的绑定经历以下状态转换:
-
注册阶段 :
DriverEntry调用NdisRegisterProtocol,NDIS 在全局协议列表中创建NDIS_PROTOCOL_BLOCK,保存所有回调函数指针。 -
枚举阶段 :NDIS 维护一个已安装适配器的列表(通过注册表
HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Linkage中的Bind键值)。协议驱动读取这个列表,对每个适配器调用NdisOpenAdapter。 -
绑定阶段 :
NdisOpenAdapter触发异步操作。NDIS 查找对应的 Miniport 驱动,如果 Miniport 尚未初始化则先调用MiniportInitialize。完成后调用协议的OpenAdapterComplete回调,传递绑定句柄。 -
运行阶段 :协议通过绑定句柄调用
NdisSend、NdisRequest(OID 查询)等 API。NDIS 通过回调通知协议接收事件、状态变化。 -
解绑阶段 :系统关闭或适配器移除时,协议调用
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) :用于同步异步操作。NdisOpenAdapter 和 NdisCloseAdapter 是异步的,协议驱动创建事件对象,在 OpenAdapterComplete 和 CloseAdapterComplete 回调中设置事件,主线程等待事件完成。
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.sys 在 LanOpenAdapter 完成后查询 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_CONNECT 和 NDIS_STATUS_MEDIA_DISCONNECT 通知,感知链路状态变化。
当系统进入休眠(S3 状态)时,NDIS 调用协议的 ProtocolPnPEvent 回调,通知协议适配器即将进入低功耗状态。协议驱动可以:
- 保存适配器状态(如 MAC 地址、多播列表)
- 配置唤醒模式(如 Wake-on-LAN,接收特定魔术包后唤醒系统)
- 释放不必要的资源
当系统恢复时,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 协议驱动,它的核心工作流:
- 初始化 :调用
NdisRegisterProtocol注册为 NDIS 协议 - 绑定适配器 :调用
NdisOpenAdapter绑定到特定 NIC - 发送 :通过
NdisSend发送包 - 接收 :在
ProtocolReceivePacket回调中接收包 - 关闭 :调用
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 总结
关键要点:
- LAN Manager 是 NDIS 协议驱动 :使用
NdisRegisterProtocol注册 - 回调族:Open/Close/Send/Receive/Status 等 10+ 回调
NdisSend发送包 :LanSend调用 NDIS 协议 APIProtocolReceivePacket接收包:把数据传递给 TCP/IP- TCP/IP 也使用 NDIS 协议 API :实际不一定经过
lan.sys - 适配器结构 :
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 之间。它的主要职责是:
- 为上层协议驱动提供统一的 NDIS 接口抽象
- 管理多个网络适配器的绑定
- 处理 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 库注册一组回调函数。这些回调函数覆盖了协议驱动生命周期的所有阶段:
-
适配器绑定回调:
OpenAdapterComplete:NdisOpenAdapter异步完成时调用CloseAdapterComplete:NdisCloseAdapter异步完成时调用
-
数据传输回调:
SendComplete:NdisSend异步完成时调用TransferDataComplete:NdisTransferData异步完成时调用Receive:NDIS 3.x/4.x 风格的包接收ReceiveComplete:一批包接收完成的通知
-
状态回调:
Status:状态变化通知(如链路状态改变)StatusComplete:状态变化处理完成ResetComplete:适配器复位完成
-
请求回调:
RequestComplete:OID 查询/设置请求完成
这些回调的异步特性是 NDIS 设计的核心。NDIS 的大多数操作都是异步的------调用方发起请求后立即返回,NDIS 库在操作完成后通过回调通知调用方。这种设计允许 NDIS 在单线程环境中高效处理多个并发操作。
适配器绑定过程详解
当 lan.sys 绑定到一个网络适配器时,经历以下步骤:
-
协议注册 :
DriverEntry调用NdisRegisterProtocol,将回调函数注册到 NDIS 全局协议列表。 -
适配器枚举:NDIS 库通知协议驱动可用的适配器列表。协议驱动选择要绑定的适配器。
-
打开适配器 :协议驱动调用
NdisOpenAdapter,传入协议句柄和适配器名称。NDIS 库查找对应的 Miniport 驱动,调用MiniportInitialize初始化硬件。 -
异步完成 :Miniport 初始化完成后,NDIS 调用协议的
OpenAdapterComplete回调,传递绑定句柄和状态。 -
介质检查 :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 存在以下差异:
-
功能范围 :Windows 的
lan.sys包含 NetBIOS 支持和 SMB 重定向器功能。ReactOS 的实现更加精简,专注于 NDIS 协议驱动的基本功能。 -
源文件结构 :ReactOS 的
lan.sys只有lan.c和routines.c两个主要源文件,而 Windows 的实现包含更多模块。 -
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.sys。lan.sys 主要服务于其他需要 NDIS 协议接口的组件。
LAN_ADAPTER 结构的详细分析
PLAN_ADAPTER 是 lan.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 |