第 10 章 网络操作 --- 10.1 概述
本节概览 Windows 网络体系结构。 Windows 网络是一个 多层次、模块化、协议独立 的栈。ReactOS 实现了核心组件:NDIS(Network Driver Interface Specification)、TCP/IP 协议栈、AFD(Ancillary Function Driver)、Winsock。理解 Windows 网络的关键是把握 OSI 层次、NDIS 三类驱动、TDI/Winsock 编程模型。
为什么 Windows 网络栈如此分层?
理解 Windows 网络分层设计的核心在于把握两个关键词:接口标准化 和 关注点分离。
传统的 UNIX 网络栈将协议实现和网络硬件绑定在一起------IP 协议栈的代码中散落着对网卡驱动的直接调用。这导致每当新网络技术出现(如千兆以太网、光纤通道、无线 802.11),协议栈都需要大改。Windows 网络栈通过在协议层和硬件层之间引入 NDIS(Network Driver Interface Specification),彻底解耦了这个依赖关系:TCP/IP 协议栈只需要面向 NDIS 定义的统一接口编程,而网卡硬件只需要实现 NDIS Miniport 接口,两者各自独立演进,互不感知。
第二层关键解耦来自 Winsock 与 AFD 。早期的 Windows NT(1993 年)使用 TDI(Transport Driver Interface) 作为传输层接口,TDI 将所有传输协议(TCP、UDP、IPX)统一包装为"文件系统风格"的设备对象------\Device\Tcp、\Device\Udp。应用程序通过 CreateFile/ReadFile/WriteFile 访问网络,协议栈与文件系统共享同一套 I/O 模型。这种设计的优点是概念统一,缺点是 API 笨重(每个操作都需要构造 IRP),无法高效支持 Win32 的重叠 I/O 模型。从 Windows 95 开始,微软引入了 Winsock(AFA) 作为 Win32 应用的推荐网络 API,并在内核态实现了 AFD(Ancillary Function Driver) 作为 Winsock 的内核代理。AFD 将 Winsock 的 Socket 语义翻译为 IRP 下发到协议栈,同时原生支持重叠 I/O 完成端口(IOCP)等现代机制。
从一次 HTTP 请求看网络栈的完整路径
当浏览器调用 socket() → connect() → send() → recv() 发起一次 HTTP 请求时,数据在 Windows 网络栈中经历的路径如下:
应用层:Winsock API (ws2_32.dll)
↓ WS2_32!connect/send/recv(用户态 DLL)
↓ AFD.sys(内核态 Socket 抽象层)
↓ TCPIP.sys(协议栈:TCP 状态机、IP 路由)
↓ NDIS(Network Driver Interface Specification)
↓ Miniport 驱动(如 Intel e1000 / Realtek RTL8139)
↓ 物理网卡(DMA、硬件中断)
这条路径中,每一层都只与相邻层通信:Winsock 不知道 TCP 的三次握手细节,TCP/IP 不知道网卡的型号,网卡不知道数据的含义(它只搬运以太网帧)。模块化设计使得 Windows 可以同时支持 IPv4/IPv6/Teredo 隧道,可以在不修改应用程序的情况下更换网卡驱动,可以在同一台机器上运行数十个网络协议而互不干扰。
NDIS 三类驱动的协作模式
NDIS 是理解 Windows 网络栈的核心概念。NDIS 定义了三种网络驱动类型,它们共同构成了 Windows 的数据链路层和网络接口层:
Miniport 驱动(底层) :Miniport 是最接近硬件的驱动,它直接与网卡交互,负责初始化网卡、配置 Media Access Control(MAC)地址、发送和接收以太网帧、处理硬件中断、管理 DMA 缓冲区。常见的 Miniport 包括 Intel e1000(千兆以太网)、Realtek RTL8139(百兆以太网)、Microsoft 的 Hyper-V 虚拟网卡(hv_netvsc)。Miniport 驱动通过 NDIS 库(ndis.sys)向协议栈注册发送/接收回调函数,它不知道也不关心上层使用什么协议------它只处理原始以太网帧。
中间驱动(IM,Middle) :IM 驱动位于 Miniport 和协议栈之间,可以拦截、修改、过滤、监控流量。Windows 自带的 IM 驱动包括 WFP(Windows Filtering Platform) 子系统(fwpkclnt.sys)、WAN 微型端口 (用于 RAS/拨号)、桥接驱动(用于网络桥接)。IM 驱动对于协议栈是"另一个 Miniport",对于下层是"另一个协议"。这种对称性使得 IM 驱动可以串联成链(如防火墙 → QoS → 流量监控),每层 IM 可以修改或拒绝流量,而不需要修改上层或下层的代码。
Protocol 驱动(上层) :Protocol 驱动是真正的协议实现者。TCP/IP 协议栈(tcpip.sys)是最典型的 Protocol 驱动------它通过 NDIS 的 NdisOpenAdapter 绑定到 Miniport(或经过 IM 链),注册 ProtocolReceive 和 ProtocolSend 回调。当下层 Miniport 收到一个以太网帧时,NDIS 库将帧递交给协议栈的 ProtocolReceive,协议栈解析 IP 头、TCP 头,最终将应用数据交给 AFD。当应用程序发送数据时,协议栈构造 IP 报头、TCP 报头,通过 NDIS 的 NdisSend 下发给 Miniport。
关键设计:NDIS 库的核心作用
ndis.sys(NDIS 库)不仅仅是一个接口规范,它是一个实际运行的框架代码,提供以下关键服务:
-
协议绑定管理 :NDIS 维护一个全局的"适配器绑定列表"(
AdapterList),记录每个 Miniport 的能力(最大帧长度、支持的媒体类型)和其上绑定的 Protocol 驱动。NdisOpenAdapter在这个列表上查找匹配的 Miniport,NdisCloseAdapter解除绑定关系。 -
发送/接收队列 :NDIS 库为每个 Miniport 管理一个发送队列(Send Queue)和一个接收队列(Receive Queue)。协议栈调用
NdisSend提交数据包,NDIS 将包放入发送队列,Miniport 的MiniportSend回调从队列中取走数据包。接收方向类似,Miniport 的MiniportHandleInterrupt将收到的帧放入接收队列,然后通过NdisIndicateReceive通知协议栈取走数据。 -
资源共享与序列化 :Miniport 驱动可能在多个 CPU 核心上被并发调用,但 Miniport 内部通常不是线程安全的。NDIS 库提供 NDIS_SPIN_LOCK (基于 KeAcquireSpinLock)和 NdisMSynchronizeWithInterrupt(在 DIRQL 执行回调)来帮助 Miniport 实现资源保护。
Winsock 的两种传输提供者
Windows 的 Winsock 架构实际上存在两个层次:基础 Winsock Provider 和 Namespace Provider 。基础提供者(MSAFD)实现实际的 Socket 语义,通过 AFD.sys 与内核交互。Namespace 提供者(如 dnscache)则负责名称解析(gethostbyname、getaddrinfo)和协议无关的 Socket 选项。ReactOS 的 dll/win32/ws2_32/ 是 Winsock 2.2 的用户态 DLL 实现,dll/win32/msafd/ 是 MSAFD 的用户态代理------注意,ReactOS 的 MSAFD 目前是存根实现,实际的内核 Winsock 功能完全由 drivers/network/afd/ 中的 AFD.sys 提供。
概述
Windows 网络体系结构按 OSI 7 层模型 映射到 3 个核心组件:
| OSI 层 | Windows 组件 | 主要文件 |
|---|---|---|
| 应用层 | Winsock (ws2_32.dll) |
dll/win32/ws2_32/ |
| 表示层 | msvcrt / RPC | - |
| 会话层 | msvcrt / RPC | - |
| 传输层 | AFD (afd.sys) + TCP/UDP |
drivers/network/afd/ |
| 网络层 | tcpip (tcpip.sys) |
drivers/network/tcpip/ |
| 数据链路层 | LAN (lan.sys / NBF) |
drivers/network/lan/ |
| 物理层 | NIC Miniport | drivers/network/dd/ |
本节内容概览
- 10.1.0 框架图
- 10.1.1 OSI 7 层模型
- 10.1.2 Windows 网络组件层次
- 10.1.3 Winsock 编程模型
- 10.1.4 NDIS 三类驱动
- 10.1.5 用户态 / 内核态接口
- 10.1.6 ReactOS 网络栈状态
- 10.1.7 总结与代码索引
学习目标
- 理解 Windows 网络的层次划分
- 区分 NDIS Miniport / IM / Protocol 驱动
- 知道 Winsock 应用程序的接口
- 跟踪 Socket 调用到 NIC 的完整路径
涉及的内核子系统
| 子系统 | 头文件/源文件 | 核心作用 |
|---|---|---|
| Winsock DLL | dll/win32/ws2_32/(file:///d:/reactos/dll/win32/ws2_32/) | Win32 API socket、send、recv |
| MSAFD | dll/win32/msafd/(file:///d:/reactos/dll/win32/msafd/) | Microsoft 特定 Winsock 实现 |
| AFD | drivers/network/afd/afd/(file:///d:/reactos/drivers/network/afd/afd/) | 内核态 Winsock |
| TCP/IP | drivers/network/tcpip/tcpip/(file:///d:/reactos/drivers/network/tcpip/tcpip/) | lwIP + 协议栈 |
| LAN Manager | drivers/network/lan/lan/(file:///d:/reactos/drivers/network/lan/lan/) | NDIS 协议驱动 |
| NDIS | drivers/network/ndis/ndis/(file:///d:/reactos/drivers/network/ndis/ndis/) | NDIS 库 |
| NIC DD | drivers/network/dd/(file:///d:/reactos/drivers/network/dd/) | 设备驱动 |
10.1.0 框架图
+-------------------------------------+
| 应用层:浏览器/FTP/SMTP/... |
+-------------------------------------+
|
v Winsock API (ws2_32.dll)
+-------------------------------------+
| Winsock Kernel (msafd.dll) |
| - 翻译 socket/WSASendto 为 IRP |
+-------------------------------------+
|
v IRP_MJ_*
+-------------------------------------+
| AFD (afd.sys) |
| - socket/end-point 内核对象 |
| - select/async 通知 |
+-------------------------------------+
|
v TDI (Transport Driver Interface)
+-------------------------------------+
| TCP/IP 协议栈 (tcpip.sys) |
| - lwIP 内核:IP/ICMP/ARP/TCP/UDP |
+-------------------------------------+
|
v NDIS Protocol Interface
+-------------------------------------+
| LAN Manager (lan.sys) |
| - NDIS 协议驱动 |
+-------------------------------------+
|
v NDIS Miniport Interface
+-------------------------------------+
| NIC Miniport (e1000/dc21x4/...) |
| - 操作硬件寄存器、DMA |
+-------------------------------------+
|
v
网络硬件 (NIC)
10.1.1 OSI 7 层模型
OSI(Open Systems Interconnection)模型是 ISO 定义的网络通信参考模型:
| 层 | 名称 | 主要功能 | 协议/技术 |
|---|---|---|---|
| 7 | 应用层 | 用户接口、HTTP/FTP/SMTP | HTTP、FTP、SMTP、DNS |
| 6 | 表示层 | 数据表示、加密、压缩 | SSL/TLS、JPEG、MPEG |
| 5 | 会话层 | 会话管理、同步 | RPC、NetBIOS |
| 4 | 传输层 | 端到端连接、可靠传输 | TCP、UDP、SCTP |
| 3 | 网络层 | 路由、分片、寻址 | IP、ICMP、ARP |
| 2 | 数据链路层 | 帧、MAC 寻址、差错控制 | Ethernet、PPP、Wi-Fi |
| 1 | 物理层 | 比特流传输 | 电缆、光纤、无线电 |
TCP/IP 4 层模型
实际互联网使用 4 层模型:
| TCP/IP 层 | OSI 对应 | 协议 |
|---|---|---|
| 应用层 | 5-7 | HTTP、FTP、DNS |
| 传输层 | 4 | TCP、UDP |
| 网际层 | 3 | IP、ICMP、ARP |
| 链路层 | 1-2 | Ethernet、Wi-Fi |
10.1.2 Windows 网络组件层次
Winsock DLL(用户态)
ws2_32.dll 是 Windows Sockets 2 的用户态实现:
c
SOCKET s = WSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, ...);
WSAConnect(s, ...);
WSARecv(s, ...);
WSASend(s, ...);
closesocket(s);
ws2_32.dll 把这些调用转换为 IRP 发送到内核。
AFD(内核 Winsock)
afd.sys(Ancillary Function Driver)是 内核态 Winsock:
- 维护 socket 状态
- 处理 select、async 通知
- 实现
bind、connect、accept、send、recv
TCP/IP 协议栈
tcpip.sys 实现 IP、ICMP、IGMP、TCP、UDP、ARP 协议。ReactOS 内部使用 lwIP(lightweight IP)作为协议栈实现:
- ARP:IP → MAC 解析
- IP:分组转发、分片
- ICMP:ping、traceroute
- TCP:可靠连接、流量控制
- UDP:无连接数据报
NDIS
NDIS(Network Driver Interface Specification)位于 NIC 硬件和上层协议之间:
- 抽象 NIC 接口:上层协议不需知道硬件细节
- 多协议共存:TCP/IP、NetBEUI、IPX 同时运行
- 多实例:一个 NIC 多个协议
10.1.3 Winsock 编程模型
阻塞模式
c
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // 创建 socket
bind(s, ...); // 绑定地址
listen(s, 5); // 监听
SOCKET client = accept(s, ...); // 阻塞等待连接
send(client, buf, len, 0); // 发送
recv(client, buf, len, 0); // 阻塞接收
closesocket(s); // 关闭
异步模式
c
SOCKET s = WSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, ...);
WSAAsyncSelect(s, hWnd, WM_SOCKET, FD_READ | FD_WRITE | FD_CLOSE); // 注册窗口消息
WSAConnect(s, ...); // 非阻塞
// 在窗口过程中:
// case WM_SOCKET:
// if (lParam == FD_READ) WSARecv(...);
// if (lParam == FD_CONNECT) ...
重叠 I/O
c
SOCKET s = WSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0,
WSA_FLAG_OVERLAPPED);
WSAOVERLAPPED ovl = {0};
ovl.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
WSASend(s, &wsaBuf, 1, &bytesSent, 0, &ovl, NULL);
// ... 等待
GetOverlappedResult(s, &ovl, &bytesTransferred, TRUE);
10.1.4 NDIS 三类驱动
NDIS 把 NIC 驱动分为三类:
1. Miniport 驱动(NIC 驱动)
直接操作硬件:
- 例:
e1000.sys、dc21x4.sys、rtl8139.sys - 实现:
MiniportInitialize、MiniportSend、MiniportISR - 在 drivers/network/dd/(file:///d:/reactos/drivers/network/dd/) 下
2. IM 驱动(Intermediate,中间层)
夹在协议驱动和 Miniport 之间,做过滤或协议转换:
- 例:NdisIM 中间层驱动(VPN、防火墙、QoS)
- ReactOS 中较少使用
3. Protocol 驱动(协议驱动)
实现网络协议:
- 例:
tcpip.sys、lan.sys(LAN Manager) - 注册回调:
NdisRegisterProtocol - 在 drivers/network/lan/(file:///d:/reactos/drivers/network/lan/) 和 tcpip/(file:///d:/reactos/drivers/network/tcpip/) 下
NDIS 关系
+----------------------+
| Miniport (e1000.sys) |
+----------------------+
^
| NdisM*Xxx (miniport 操作)
|
+----------------------+
| NDIS 库 (ndis.sys) | 中介
+----------------------+
^
| Ndis*Xxx (protocol 操作)
|
+----------------------+
| Protocol (tcpip.sys) |
+----------------------+
10.1.5 用户态 / 内核态接口
用户态 Winsock → 内核 AFD
c
// ws2_32.dll 中的 socket() 实现
SOCKET WSAAPI socket(int af, int type, int protocol) {
// 构造请求
AFD_CREATE_REQUEST req = {af, type, protocol, ...};
// 打开 AFD 设备
HANDLE h = CreateFileW(L"\\Device\\Afd", ...);
// DeviceIoControl 发送请求
DWORD bytes;
DeviceIoControl(h, IOCTL_AFD_CREATE, &req, sizeof(req), &out, sizeof(out), &bytes, NULL);
return (SOCKET)out.Handle;
}
AFD → 协议驱动(TDI 接口)
c
// AFD 内部
NTSTATUS AfdBindSocket(...) {
// 打开 TCP/IP 设备
HANDLE tcpHandle = CreateFileW(L"\\Device\\Tcp", ...);
// 构造 TDI_REQUEST
TDI_REQUEST_KERNEL tdiReq;
// 通过 IRP_MJ_CREATE/INTERNAL_DEVICE_CONTROL 与 TCP/IP 通信
}
协议驱动 → NDIS Protocol API
c
// TCP/IP 内部(lwIP)
NdisSend(&Status, NdisBindingHandle, Packet);
NDIS → Miniport
c
// NIC miniport 处理
NdisMIndicateReceivePacket(MiniportAdapterContext, &Packet, 1);
10.1.6 ReactOS 网络栈状态
ReactOS 的网络栈 基本可用,但部分组件仍在完善:
| 组件 | 状态 | 说明 |
|---|---|---|
| NDIS 库 | 基本完整 | 6.x 兼容 |
| TCP/IP 协议 | 基于 lwIP | 完整 IPv4 |
| Winsock | 基本可用 | ws2_32.dll 多数函数 |
| MSAFD | 简单实现 | 通过 AFD |
| AFD | 较完整 | 支持 select、async |
| LAN Manager | 协议驱动 | 与 TCP/IP 通信 |
| NIC DD | 多型号支持 | dc21x4、e1000 等 |
与 Windows 的差异
- 不支持 IPv6(lwIP 已支持,ReactOS 未启用)
- 不支持 大型 IM 驱动(防火墙、QoS 较少)
- 简化 Winsock 服务提供者(仅 LSP1)
ReactOS 网络架构的深度分析
ReactOS 网络栈的设计体现了对 Windows NT 网络架构的深刻理解与完整复现。从软件工程角度看,这是一个典型的分层架构案例,每一层都有明确的职责边界和接口契约。
设计哲学与权衡
Windows 网络架构的核心设计哲学是"协议独立性"和"硬件抽象化"。NDIS 的引入使得上层协议栈无需关心底层 NIC 硬件的具体实现,而 NIC 厂商也无需了解 TCP/IP 等协议的细节。这种解耦设计带来了巨大的生态系统优势:一个 NIC 驱动可以与多个协议栈共存,一个协议栈可以运行在不同的硬件平台上。
ReactOS 在实现这一架构时面临的关键挑战是:如何在保持二进制兼容性的同时,实现一个干净、可维护的内部结构。Windows 的 NDIS 库经过多次迭代(从 3.1 到 6.x),积累了大量的历史包袱和向后兼容代码。ReactOS 选择实现 NDIS 5.x 兼容版本,这是一个务实的决策------既能支持大多数现有驱动,又避免了过于复杂的 NDIS 6.x 特性(如 RSS、LSO 等)。
NDIS 接口的技术细节
NDIS 库的核心数据结构是 NDIS_PROTOCOL_BLOCK、NDIS_M_DRIVER_BLOCK 和 NDIS_ADAPTER_BLOCK。这三个结构分别代表协议驱动、Miniport 驱动和网络适配器实例。在 ReactOS 的实现中(ndissys.h),这些结构通过双向链表组织:
c
LIST_ENTRY ProtocolListHead; // 所有已注册协议
LIST_ENTRY MiniportListHead; // 所有已注册 Miniport
LIST_ENTRY AdapterListHead; // 所有适配器实例
每个列表都有对应的自旋锁保护(ProtocolListLock、MiniportListLock、AdapterListLock),确保多处理器环境下的并发安全。这种设计反映了 Windows 内核编程的经典模式:全局状态 + 细粒度锁。
TDI 与 Winsock 的关系
TDI(Transport Driver Interface)是 Windows 内核态传输层驱动的标准接口。AFD 通过 TDI 与 TCP/IP 协议栈通信,使用的 IOCTL 包括 IOCTL_TDI_CONNECT、IOCTL_TDI_SEND、IOCTL_TDI_RECEIVE 等。TDI 的设计目标是提供一个统一的传输层抽象,使得上层驱动(如 AFD)无需关心底层是 TCP、UDP 还是其他协议。
在 ReactOS 中,TDI 的实现位于 drivers/network/tdi/ 目录。TDI 本质上是一组标准的 IRP _major function codes 和 IOCTL 定义,而不是一个独立的驱动模块。这种设计使得 TDI 可以灵活地嵌入到各种传输驱动中。
lwIP 集成的技术挑战
ReactOS 选择 lwIP 作为 TCP/IP 协议栈的内部实现,这是一个明智的决策。lwIP 是一个轻量级、模块化的 TCP/IP 栈,最初为嵌入式系统设计,但功能完整且易于移植。然而,将 lwIP 集成到 Windows 内核环境面临几个关键挑战:
-
线程模型差异 :lwIP 设计为单线程模型(NO_SYS=1 或 tcpip_thread),而 Windows 内核是多线程环境。ReactOS 通过全局锁(
LwIPLock)保护 lwIP 数据结构,确保同一时刻只有一个线程访问 lwIP 核心。 -
内存管理 :lwIP 使用自己的内存分配器(
mem.c、memp.c),而 Windows 内核使用ExAllocatePoolWithTag。ReactOS 通过包装层将 lwIP 的内存调用映射到 NT 内核分配器,同时保持内存标签(tag)以便调试。 -
中断处理 :lwIP 的网络接口输入函数(
netif->input)通常在中断上下文中调用,而 Windows 内核的中断处理有严格的 DPC(Deferred Procedure Call)机制。ReactOS 通过工作队列(chew/workqueue.c)将中断处理延迟到 DPC 级别。
与 Windows 网络栈的对比
Windows 网络栈经过 20 多年的演进,包含大量 ReactOS 未实现的特性:
- IPv6 支持:Windows Vista 开始默认启用 IPv6,ReactOS 的 lwIP 虽然包含 IPv6 代码,但未集成到 TDI 接口。
- Winsock LSP(Layered Service Provider):Windows 支持多层 LSP 链,用于实现防火墙、VPN 等功能。ReactOS 仅实现单层 LSP。
- NDIS 6.x 特性:包括 RSS(Receive Side Scaling)、LSO(Large Send Offload)、VMQ(Virtual Machine Queue)等,这些特性在现代网卡中广泛使用。
- WFP(Windows Filtering Platform):Vista 引入的下一代防火墙平台,替代了旧的 TDI 过滤驱动。
ReactOS 的网络栈虽然功能上不如 Windows 完整,但已经能够支持基本的网络操作:TCP/UDP 通信、DNS 解析、HTTP 浏览等。这对于一个开源操作系统来说是一个重要的里程碑。
网络驱动堆叠的工作机制
Windows 网络驱动堆叠的核心是"绑定(Binding)"机制。当系统启动时,NDIS 会枚举所有 NIC 硬件,为每个 NIC 创建适配器实例。然后,协议驱动(如 TCP/IP)通过 NdisOpenAdapter 绑定到适配器。绑定过程建立了协议驱动和 Miniport 驱动之间的关联,使得数据包可以在两者之间流动。
在 ReactOS 中,绑定过程由 protocol.c 中的 ProOpenAdapter 函数处理。绑定成功后,协议驱动获得一个 BindingHandle,后续的数据发送和接收都通过这个句柄进行。这种设计使得多个协议可以同时绑定到同一个 NIC,实现了协议共存。
数据包的发送和接收流程
数据包发送的完整路径(从应用到硬件):
- 应用调用
send()→ws2_32.dll转换为IOCTL_AFD_SEND msafd.dll通过NtDeviceIoControlFile将 IRP 发送到afd.sys- AFD 构造 TDI 请求(
IOCTL_TDI_SEND)发送到tcpip.sys - TCP/IP 调用 lwIP 的
tcp_write或udp_send - lwIP 构造 IP 包,调用
netif->output(通常是etharp_output) etharp_output通过 ARP 解析目标 MAC,调用ethernetif_linkoutputethernetif_linkoutput调用NdisSend将包传递给 NDIS 库- NDIS 库查找适配器绑定的 Miniport 驱动,调用
MiniportSend - Miniport 驱动构造 DMA 描述符,启动硬件发送
数据包接收的路径(从硬件到应用)是发送的逆过程,关键步骤包括:
- NIC 收到帧,触发中断
- Miniport ISR(中断服务例程)读取中断状态寄存器
- Miniport 调用
NdisMIndicateReceivePacket通知 NDIS 库 - NDIS 库遍历适配器绑定的所有协议,调用每个协议的
ProtocolReceivePacket - TCP/IP 的
ProtocolReceivePacket将包传递给 lwIP 的tcpip_input - lwIP 解析 IP 头,根据协议字段分发到 TCP 或 UDP
- TCP/UDP 将数据放入对应 socket 的接收队列
- AFD 通过 TDI 事件通知唤醒等待的
recv()调用 - 数据从内核复制到用户态缓冲区
这个流程涉及多次上下文切换(用户态 ↔ 内核态)、多次锁操作、多次内存复制,是操作系统中最复杂的数据路径之一。
10.1.7 总结
关键要点:
- OSI 7 层模型 与 TCP/IP 4 层模型对应
- 三大组件:Winsock 用户态 → AFD 内核 → 协议栈 → NDIS → NIC
- NDIS 三类驱动:Miniport、IM、Protocol
- lwIP 内核协议栈:ReactOS 使用 lwIP
- API 链路 :
socket→CreateFileW(\\Device\\Afd)→ IOCTL → IRP → 协议驱动
下一节 10.2 将剖析 NDIS 库。
10.1.8 设计哲学问答
Q1:为什么 NDIS 要定义 Miniport / IM / Protocol 三类驱动而不是统一一种?
A :关注点分离 + 生态系统解耦。
三类驱动对应网络栈中三个不同的职责角色:Miniport 只关心"如何把帧发到网线上",Protocol 只关心"如何组装 IP 包",IM 负责"如何在两者之间修改流量"。如果只用一种驱动,网卡厂商需要理解 TCP/IP 协议细节,协议栈作者需要会写 DMA 代码------这是不可能的工程边界。
更关键的是三类驱动的绑定关系是对称的 :Protocol 眼中的"下层"是 IM 或 Miniport,IM 眼中的"下层"也是 IM 或 Miniport。这种对称性使得 IM 可以串联成链------防火墙 IM → QoS IM → 流量监控 IM,每层独立演进。Windows 自带的 NDIS 软总线(ndistapi.sys)和 WAN 微型端口都是 IM 驱动的例子。
Q2:为什么 AFD.sys 要存在而不是让 ws2_32.dll 直接调用 TCP/IP?
A :内核态协议栈 + 安全边界 + 重叠 I/O 原生支持。
有三个原因。首先,TCP/IP 协议栈(tcpip.sys)运行在内核态,直接暴露给用户态 DLL 会破坏内核/用户态安全边界------恶意程序可以通过 Winsock 漏洞获得内核权限。
其次,tcpip.sys 是一个内核驱动,它通过 TDI 设备对象(\Device\Tcp)接受 IRP。而 ws2_32.dll 运行在用户态,无法直接构造和发送内核 IRP。AFD.sys 作为内核代理,将 Winsock 的 Socket 语义(socket、bind、listen)翻译为 IRP 下发给 TDI 设备。
第三,AFD 原生支持重叠 I/O 和 IOCP------它维护每个 socket 的内部状态(Endpoint、ConnectionList、ListenQueue),支持 WSASend 的零拷贝语义。这在用户态 DLL 中是无法高效实现的。
Q3:为什么 lwIP 集成到 Windows 需要全局锁保护?
A :lwIP 的单线程模型与 Windows 多线程环境的冲突。
lwIP 最初为嵌入式系统设计,假设整个协议栈在单个控制流(tcpip_thread)中运行。当多个线程同时调用 lwIP API 时,如果没有锁保护,struct tcp_pcb(TCP 控制块)的状态可能被并发修改,导致死锁或数据损坏。
ReactOS 通过 LwIPLock(一个 EX_PUSH_LOCK)保护所有 lwIP 数据结构的访问。这意味着 lwIP 核心实际上是串行化的------同一时刻只有一个线程可以执行 lwIP 代码。这在多核系统上是一个性能瓶颈,但保证了正确性。
Q4:为什么 NDIS 的 NdisSend 返回成功但包可能根本没发出去?
A :NDIS 的"成功"定义是"包已提交到发送队列"而非"已到达网卡"。
NdisSend 的语义是"包已被接受进入 NDIS 发送队列"。如果 Miniport 的发送环(Send Ring)已满,NdisSend 返回 NDIS_STATUS_RESOURCES,协议栈需要重试。但如果 NdisSend 返回成功,协议栈可以安全地释放数据包缓冲区(假设后续完成路径会处理)。包的实际发送由 Miniport 的 DPC 在后台完成。
这种设计反映了网络栈的分层原则:协议栈不需要关心发送队列是否已满,只需要知道"包是否被接受"。真正的流量控制由 TCP 的拥塞控制算法和 Miniport 的发送环共同完成。
Q5:为什么 Winsock 经历了 Winsock 1.1 → 2.0 → 2.2 的演进而不是一次性设计好?
A :与 Windows 网络架构的渐进演进同步。
Winsock 1.1(1992 年)是跟随 Windows 3.1 的 TCP/IP 供应商(Trumpet Winsock)出现的,API 模仿 BSD Socket 但不完全兼容。Winsock 2.0(1996 年,随 Windows 95 发布)引入了真正的多协议支持------除了 TCP/IP,还能通过 LSP(Layered Service Provider)支持 IPX、ATM 等协议,并添加了 WSAStartup、WSASocket、重叠 I/O 等现代化接口。
Winsock 2.2(1998 年)修正了 2.0 的一些规范问题并添加了 IPv6 前瞻支持。ReactOS 实现的是 Winsock 2.2 兼容 API,这与 Windows 98/2000 的生态兼容。
Q6:为什么 NDIS 需要自己的内存分配器而不是直接用 ExAllocatePool?
A :NDIS 缓冲区需要符合总线主 DMA 的对齐和边界约束。
网卡 DMA 控制器有严格的对齐要求(例如许多 PCI 网卡要求每个描述符 4 字节对齐,数据缓冲区 8 字节对齐)。NDIS 库的 NdisAllocateFromNPagedLookasideList 和 NdisFreeToNPagedLookasideList 使用 lookaside list 优化小数据分配,同时确保分配粒度满足 DMA 对齐要求。
更重要的是,NDIS 的 NDIS_PACKET 结构包含一个"协议栈分配的 MDL"链------这个 MDL 必须满足 NIC Miniport 的 DMA 映射约束。直接使用 ExAllocatePool 可能产生不对齐的缓冲区,导致 DMA 传输失败或性能下降。
Q7:为什么 TCP/IP 协议栈(tcpip.sys)要通过 TDI 与 AFD 通信而不是直接函数调用?
A :TDI 提供了协议无关的传输层抽象。
TDI 定义了一组标准的 IRP IOCTL 接口(IOCTL_TDI_CONNECT、TDI_SEND、TDI_RECEIVE 等),这些接口不依赖于具体的传输协议。如果 Windows 要添加一个新的传输协议(如 DDP/AppleTalk),AFD 不需要修改------它只需把 Socket 操作翻译为对应的 TDI 请求,新协议只需实现相同的 TDI 接口。
此外,TDI 接口使得 AFD 可以同时与多个传输协议栈通信。在 Windows Server 中,你可以通过 AFD 同时使用 TCP/IP 和 IPX/SPX,而 AFD 的代码不需要区分底层是哪种协议。
Q8:为什么 NDIS 的 NdisMIndicateReceivePacket 一次可以指示多个包?
A :优化中断合并(Interrupt Coalescing)+ 批处理效率。
现代千兆网卡在单个中断中可能接收到数十个数据包(特别是启用了 RSS 的多核系统)。如果每收到一个包就触发一次 NdisMIndicateReceivePacket,会产生大量中断开销,严重影响性能。
NdisMIndicateReceivePacket 支持一次指示一个包数组(通过 PPACKET_ENTRY 数组),NDIS 库将这些包打包后一次性递交给协议栈的 ProtocolReceivePackets 回调。这使得协议栈可以在一次调用中处理多个包,减少了函数调用开销和锁竞争。lwIP 在 ethernetif_input 函数中正是利用这一特性批量处理到达的以太网帧。
Q9:为什么 ReactOS 的 MSAFD 是存根实现,而 AFD 反而更完整?
A :架构演进 + 开发优先级。
MSAFD(Microsoft Specific AFD)是 Winsock 2 的 Microsoft 特定扩展,包括命名空间解析器、服务提供者枚举等高级功能。对于一个操作系统来说,最核心的需求是"能建立 TCP 连接",这已经由 AFD.sys 直接实现了------Winsock 应用通过 ws2_32.dll 调用 DeviceIoControl 直接与 AFD.sys 通信,完全绕过 MSAFD。
ReactOS 选择把工程精力集中在 AFD.sys 的正确性和完整性上(包括 select、async、重叠 I/O),而 MSAFD 的高级功能(多提供者链、LSP 安装)则标记为存根。这是务实的工程决策:先确保基础功能工作,再逐步完善高级特性。
Q10:为什么网络数据包路径涉及"多次内存复制"而不是零复制?
A :安全边界 + 内存管理模型 + API 语义约束。
零复制网络(像 Linux 的 sendfile())在 Windows 中也有对应机制(TransmitFile),但基础 Winsock 路径(send/recv)需要多次复制,原因有三:
-
用户态/内核态边界 :应用缓冲区和内核协议栈在不同虚拟地址空间,跨边界复制是不可避免的。
recv()最终需要把数据从tcpip.sys的 mbuf/包缓存复制到用户态buf。 -
MDL 和 DMA 约束:对于 DIRECT_IO socket,AFD 通过 MDL 把用户缓冲区直接映射给网卡 DMA------这实际上避免了从应用缓冲区到协议栈的复制,但数据仍需经过协议栈的重组装缓冲区(TCP 按序号重组)。
-
API 兼容性 :Winsock 的
recv()语义允许部分读取(bytesReceived < len),这要求协议栈维护自己的接收缓冲区来暂存数据,直到应用下次调用recv()取走。
本章代码索引
| 文件 | 内容 |
|---|---|
| ws2_32(file:///d:/reactos/dll/win32/ws2_32/) | 用户态 Winsock DLL |
| msafd(file:///d:/reactos/dll/win32/msafd/) | Microsoft Winsock 实现 |
| afd(file:///d:/reactos/drivers/network/afd/afd/) | 内核 Winsock |
| tcpip(file:///d:/reactos/drivers/network/tcpip/tcpip/) | TCP/IP 协议栈 |
| lan(file:///d:/reactos/drivers/network/lan/lan/) | LAN Manager |
| ndis(file:///d:/reactos/drivers/network/ndis/ndis/) | NDIS 库 |
| network/dd(file:///d:/reactos/drivers/network/dd/) | NIC 设备驱动 |