Reactos 第 10 章 网络操作 — 10.1 概述

第 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 链),注册 ProtocolReceiveProtocolSend 回调。当下层 Miniport 收到一个以太网帧时,NDIS 库将帧递交给协议栈的 ProtocolReceive,协议栈解析 IP 头、TCP 头,最终将应用数据交给 AFD。当应用程序发送数据时,协议栈构造 IP 报头、TCP 报头,通过 NDIS 的 NdisSend 下发给 Miniport。

关键设计:NDIS 库的核心作用

ndis.sys(NDIS 库)不仅仅是一个接口规范,它是一个实际运行的框架代码,提供以下关键服务:

  1. 协议绑定管理 :NDIS 维护一个全局的"适配器绑定列表"(AdapterList),记录每个 Miniport 的能力(最大帧长度、支持的媒体类型)和其上绑定的 Protocol 驱动。NdisOpenAdapter 在这个列表上查找匹配的 Miniport,NdisCloseAdapter 解除绑定关系。

  2. 发送/接收队列 :NDIS 库为每个 Miniport 管理一个发送队列(Send Queue)和一个接收队列(Receive Queue)。协议栈调用 NdisSend 提交数据包,NDIS 将包放入发送队列,Miniport 的 MiniportSend 回调从队列中取走数据包。接收方向类似,Miniport 的 MiniportHandleInterrupt 将收到的帧放入接收队列,然后通过 NdisIndicateReceive 通知协议栈取走数据。

  3. 资源共享与序列化 :Miniport 驱动可能在多个 CPU 核心上被并发调用,但 Miniport 内部通常不是线程安全的。NDIS 库提供 NDIS_SPIN_LOCK (基于 KeAcquireSpinLock)和 NdisMSynchronizeWithInterrupt(在 DIRQL 执行回调)来帮助 Miniport 实现资源保护。

Winsock 的两种传输提供者

Windows 的 Winsock 架构实际上存在两个层次:基础 Winsock ProviderNamespace Provider 。基础提供者(MSAFD)实现实际的 Socket 语义,通过 AFD.sys 与内核交互。Namespace 提供者(如 dnscache)则负责名称解析(gethostbynamegetaddrinfo)和协议无关的 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 socketsendrecv
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 通知
  • 实现 bindconnectacceptsendrecv

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.sysdc21x4.sysrtl8139.sys
  • 实现:MiniportInitializeMiniportSendMiniportISR
  • drivers/network/dd/(file:///d:/reactos/drivers/network/dd/) 下

2. IM 驱动(Intermediate,中间层)

夹在协议驱动和 Miniport 之间,做过滤或协议转换:

  • 例:NdisIM 中间层驱动(VPN、防火墙、QoS)
  • ReactOS 中较少使用

3. Protocol 驱动(协议驱动)

实现网络协议:

  • 例:tcpip.syslan.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_BLOCKNDIS_M_DRIVER_BLOCKNDIS_ADAPTER_BLOCK。这三个结构分别代表协议驱动、Miniport 驱动和网络适配器实例。在 ReactOS 的实现中(ndissys.h),这些结构通过双向链表组织:

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

每个列表都有对应的自旋锁保护(ProtocolListLockMiniportListLockAdapterListLock),确保多处理器环境下的并发安全。这种设计反映了 Windows 内核编程的经典模式:全局状态 + 细粒度锁。

TDI 与 Winsock 的关系

TDI(Transport Driver Interface)是 Windows 内核态传输层驱动的标准接口。AFD 通过 TDI 与 TCP/IP 协议栈通信,使用的 IOCTL 包括 IOCTL_TDI_CONNECTIOCTL_TDI_SENDIOCTL_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 内核环境面临几个关键挑战:

  1. 线程模型差异 :lwIP 设计为单线程模型(NO_SYS=1 或 tcpip_thread),而 Windows 内核是多线程环境。ReactOS 通过全局锁(LwIPLock)保护 lwIP 数据结构,确保同一时刻只有一个线程访问 lwIP 核心。

  2. 内存管理 :lwIP 使用自己的内存分配器(mem.cmemp.c),而 Windows 内核使用 ExAllocatePoolWithTag。ReactOS 通过包装层将 lwIP 的内存调用映射到 NT 内核分配器,同时保持内存标签(tag)以便调试。

  3. 中断处理 :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,实现了协议共存。

数据包的发送和接收流程

数据包发送的完整路径(从应用到硬件):

  1. 应用调用 send()ws2_32.dll 转换为 IOCTL_AFD_SEND
  2. msafd.dll 通过 NtDeviceIoControlFile 将 IRP 发送到 afd.sys
  3. AFD 构造 TDI 请求(IOCTL_TDI_SEND)发送到 tcpip.sys
  4. TCP/IP 调用 lwIP 的 tcp_writeudp_send
  5. lwIP 构造 IP 包,调用 netif->output(通常是 etharp_output
  6. etharp_output 通过 ARP 解析目标 MAC,调用 ethernetif_linkoutput
  7. ethernetif_linkoutput 调用 NdisSend 将包传递给 NDIS 库
  8. NDIS 库查找适配器绑定的 Miniport 驱动,调用 MiniportSend
  9. Miniport 驱动构造 DMA 描述符,启动硬件发送

数据包接收的路径(从硬件到应用)是发送的逆过程,关键步骤包括:

  1. NIC 收到帧,触发中断
  2. Miniport ISR(中断服务例程)读取中断状态寄存器
  3. Miniport 调用 NdisMIndicateReceivePacket 通知 NDIS 库
  4. NDIS 库遍历适配器绑定的所有协议,调用每个协议的 ProtocolReceivePacket
  5. TCP/IP 的 ProtocolReceivePacket 将包传递给 lwIP 的 tcpip_input
  6. lwIP 解析 IP 头,根据协议字段分发到 TCP 或 UDP
  7. TCP/UDP 将数据放入对应 socket 的接收队列
  8. AFD 通过 TDI 事件通知唤醒等待的 recv() 调用
  9. 数据从内核复制到用户态缓冲区

这个流程涉及多次上下文切换(用户态 ↔ 内核态)、多次锁操作、多次内存复制,是操作系统中最复杂的数据路径之一。


10.1.7 总结

关键要点

  1. OSI 7 层模型TCP/IP 4 层模型对应
  2. 三大组件:Winsock 用户态 → AFD 内核 → 协议栈 → NDIS → NIC
  3. NDIS 三类驱动:Miniport、IM、Protocol
  4. lwIP 内核协议栈:ReactOS 使用 lwIP
  5. API 链路socketCreateFileW(\\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 语义(socketbindlisten)翻译为 IRP 下发给 TDI 设备。

第三,AFD 原生支持重叠 I/O 和 IOCP------它维护每个 socket 的内部状态(EndpointConnectionListListenQueue),支持 WSASend 的零拷贝语义。这在用户态 DLL 中是无法高效实现的。

Q3:为什么 lwIP 集成到 Windows 需要全局锁保护?

AlwIP 的单线程模型与 Windows 多线程环境的冲突

lwIP 最初为嵌入式系统设计,假设整个协议栈在单个控制流(tcpip_thread)中运行。当多个线程同时调用 lwIP API 时,如果没有锁保护,struct tcp_pcb(TCP 控制块)的状态可能被并发修改,导致死锁或数据损坏。

ReactOS 通过 LwIPLock(一个 EX_PUSH_LOCK)保护所有 lwIP 数据结构的访问。这意味着 lwIP 核心实际上是串行化的------同一时刻只有一个线程可以执行 lwIP 代码。这在多核系统上是一个性能瓶颈,但保证了正确性。

Q4:为什么 NDIS 的 NdisSend 返回成功但包可能根本没发出去?

ANDIS 的"成功"定义是"包已提交到发送队列"而非"已到达网卡"

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 等协议,并添加了 WSAStartupWSASocket、重叠 I/O 等现代化接口。

Winsock 2.2(1998 年)修正了 2.0 的一些规范问题并添加了 IPv6 前瞻支持。ReactOS 实现的是 Winsock 2.2 兼容 API,这与 Windows 98/2000 的生态兼容。

Q6:为什么 NDIS 需要自己的内存分配器而不是直接用 ExAllocatePool

ANDIS 缓冲区需要符合总线主 DMA 的对齐和边界约束

网卡 DMA 控制器有严格的对齐要求(例如许多 PCI 网卡要求每个描述符 4 字节对齐,数据缓冲区 8 字节对齐)。NDIS 库的 NdisAllocateFromNPagedLookasideListNdisFreeToNPagedLookasideList 使用 lookaside list 优化小数据分配,同时确保分配粒度满足 DMA 对齐要求。

更重要的是,NDIS 的 NDIS_PACKET 结构包含一个"协议栈分配的 MDL"链------这个 MDL 必须满足 NIC Miniport 的 DMA 映射约束。直接使用 ExAllocatePool 可能产生不对齐的缓冲区,导致 DMA 传输失败或性能下降。

Q7:为什么 TCP/IP 协议栈(tcpip.sys)要通过 TDI 与 AFD 通信而不是直接函数调用?

ATDI 提供了协议无关的传输层抽象

TDI 定义了一组标准的 IRP IOCTL 接口(IOCTL_TDI_CONNECTTDI_SENDTDI_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)需要多次复制,原因有三:

  1. 用户态/内核态边界 :应用缓冲区和内核协议栈在不同虚拟地址空间,跨边界复制是不可避免的。recv() 最终需要把数据从 tcpip.sys 的 mbuf/包缓存复制到用户态 buf

  2. MDL 和 DMA 约束:对于 DIRECT_IO socket,AFD 通过 MDL 把用户缓冲区直接映射给网卡 DMA------这实际上避免了从应用缓冲区到协议栈的复制,但数据仍需经过协议栈的重组装缓冲区(TCP 按序号重组)。

  3. 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 设备驱动
相关推荐
CingSyuan1 小时前
服务器现场排障:在 Windows 下使用 Linux reader 直接查看 Linux 系统 U 盘中的日志文件与文件结构
linux·运维·服务器·网络·windows
芳草萋萋鹦鹉洲哦1 小时前
【mqtt】emqx broker安装测试详细教程(附windows版本emqx broker下载地址)
windows·mqtt·broker·emqx
sukalot1 小时前
windows显示驱动开发-CCD DDI的其它技术
windows·驱动开发
不吃鱼的羊1 小时前
DaVinci配置NVM模块
前端·javascript·网络
艾莉丝努力练剑1 小时前
【Linux网络】多路转接select
java·linux·运维·服务器·网络·tcp/ip
Cx330❀1 小时前
【Linux网络】从零定制应用层协议:黏包问题、全双工缓冲区与 Jsoncpp 序列化深度解析
linux·运维·服务器·开发语言·网络·c++·人工智能
祭曦念1 小时前
【共创季稿事节】鸿蒙原生 ArkTS 布局实践:List + onReachStart/End 分页加载完全指南
windows·list·harmonyos
艾莉丝努力练剑1 小时前
【Linux网络】五种IO模型与非阻塞IO
linux·运维·服务器·开发语言·网络·tcp/ip
VidDown1 小时前
视频协议传输全解析:从 HTTP/HTTPS 到 HLS/DASH 的完整旅程
javascript·网络·http·https·编辑器·音视频·视频编解码