Reactos 第 9 章 设备驱动 — 9.11 命名管道与Mailslot

第 9 章 设备驱动 --- 9.11 命名管道与Mailslot

本节剖析 Windows 中两个特殊的"文件系统驱动":命名管道(Named Pipe,npfs.sys)和邮槽(Mailslot,msfs.sys)。 它们不是传统意义上的磁盘文件系统,而是 在内存中模拟文件系统接口 的驱动,提供进程间通信(IPC)能力。ReactOS 实现位于 drivers/filesystems/npfs/(file:///d:/reactos/drivers/filesystems/npfs/) 和 drivers/filesystems/msfs/(file:///d:/reactos/drivers/filesystems/msfs/)。


概述

命名管道邮槽 都是基于 文件系统接口 实现的 IPC 机制:

  • 命名管道:\Device\NamedPipe\PipeName → 双工/单工通信、流/消息模式
  • 邮槽:\Device\Mailslot\MailslotName → 单向、消息模式、多对一广播

它们通过 CreateFileWReadFileWriteFile 等标准 Win32 API 访问,对应用透明------应用程序无需感知读写的是本地文件还是远程进程的通信端点。

为什么选择文件系统接口实现 IPC?

这种设计选择并非偶然,而是 Windows NT 架构设计原则的体现------所有可命名对象都应通过对象管理器(Ob)统一管理。将管道和邮槽实现为文件系统驱动带来了几个关键优势:

  1. 统一的安全模型 :文件对象的安全检查由 I/O 管理器统一处理。当客户端通过 CreateFileW 打开管道时,I/O 管理器自动调用 SeAccessCheck 进行访问控制列表(ACL)检查,npfs 无需自己实现安全验证逻辑。安全描述符直接挂载在 FCB 上,与 NTFS 文件的安全机制完全一致。

  2. 句柄继承与共享 :管道句柄与普通文件句柄一样,支持进程继承(bInheritHandle)和句柄复制(DuplicateHandle)。子进程可以继承父进程打开的管道句柄,实现父子进程间的 IPC------这在 shell 管道重定向(cmd.exe /c dir | findstr foo)中起着核心作用。

  3. 异步 I/O 零成本获得ReadFileEx 的重叠 I/O(Overlapped I/O)和 I/O 完成端口(IOCP)机制由 I/O 管理器原生支持。服务端应用程序可以使用 CreateIoCompletionPort 将多个管道句柄关联到同一个完成端口,实现高效的线程池模型------所有这些基础设施都来自 I/O 管理器,npfs 只需实现基本的读写派发。

  4. 本地/远程透明性 :命名管道可以通过 SMB 协议重定向到远程机器,路径 \\Server\pipe\PipeName 由网络重定向器(mup.sys)解析并转发。应用程序只需改变路径前缀即可从本地 IPC 切换到远程 IPC,代码无需修改。这种透明性基于 Windows 的公用前缀(UNC)命名机制。

NT 设备命名空间

从内核视角看,npfs 和 msfs 注册的设备对象位于 NT 命名空间中:

复制代码
\Device\NamedPipe      ← npfs.sys 创建的设备对象
\Device\Mailslot       ← msfs.sys 创建的设备对象

Win32 子系统在用户态通过 \\.\pipe\PipeName\\.\mailslot\MailslotName 形式的 DOS 设备名访问这些对象,I/O 管理器在内部完成 DOS 设备名到 NT 设备名的翻译。因此同一个管道在内核中实际对应的路径是 \Device\NamedPipe\PipeName,而应用程序看到的是 \\.\pipe\PipeName

历史沿革

命名管道源自 Microsoft LAN Manager 时代的网络 IPC,最初设计用于远程过程调用(RPC)和 SQL Server 的客户端-服务器通信。邮槽则继承自 OS/2 的邮件槽机制,主要用于局域网内的简单消息广播。Windows NT 将它们统一纳入 I/O 管理器的文件系统框架,使得两种古老而成熟的 IPC 机制与现代 NT 内核架构无缝融合。

本节内容概览

  • 9.11.0 框架图
  • 9.11.1 命名管道概述
  • 9.11.2 npfs.sys 初始化
  • 9.11.3 命名管道的创建与连接
  • 9.11.4 命名管道的读写
  • 9.11.5 邮槽概述
  • 9.11.6 msfs.sys 初始化
  • 9.11.7 邮槽的创建与通信
  • 9.11.8 总结与代码索引

学习目标

  • 理解命名管道与邮槽的区别
  • 掌握 npfs.sys 的 FCB/CCB/VCB 结构
  • 知道 CreateFileW 如何打开命名管道
  • 跟踪 ReadFile/WriteFile 在 npfs 中的路径

涉及的内核子系统

子系统 头文件/源文件 核心作用
npfs 主模块 drivers/filesystems/npfs/main.c(file:///d:/reactos/drivers/filesystems/npfs/main.c) DriverEntry、别名
npfs 创建 create.c(file:///d:/reactos/drivers/filesystems/npfs/create.c) NpCreate
npfs 读 read.c(file:///d:/reactos/drivers/filesystems/npfs/read.c) 读管道
npfs 写 write.c(file:///d:/reactos/drivers/filesystems/npfs/write.c) 写管道
npfs 清理 cleanup.c(file:///d:/reactos/drivers/filesystems/npfs/cleanup.c) NpCleanup
npfs 关闭 close.c(file:///d:/reactos/drivers/filesystems/npfs/close.c) NpClose
npfs 头 npfs.h(file:///d:/reactos/drivers/filesystems/npfs/npfs.h) 数据结构
msfs 驱动 drivers/filesystems/msfs/msfs.c(file:///d:/reactos/drivers/filesystems/msfs/msfs.c) DriverEntry
msfs 创建 create.c(file:///d:/reactos/drivers/filesystems/msfs/create.c) MsCreate
msfs 读写 rw.c(file:///d:/reactos/drivers/filesystems/msfs/rw.c) 邮槽读写
msfs 头 msfs.h(file:///d:/reactos/drivers/filesystems/msfs/msfs.h) 数据结构

9.11.0 框架图

复制代码
        应用 Win32 API
        CreateNamedPipeW  /  CreateMailslotW
        ConnectNamedPipe  /  GetMailslotInfo
        ReadFile / WriteFile
                |
                v
        +-------------------+
        | I/O Manager       |  NtCreateFile / NtReadFile
        +-------------------+
                |
                v
        +-------------------+         +-------------------+
        | npfs.sys          |  或     | msfs.sys          |
        | \Device\NamedPipe |         | \Device\Mailslot  |
        +-------------------+         +-------------------+
                |                            |
                v                            v
        +-------------+               +-------------+
        | NPFS FCB    |               | MSFS FCB    |
        | (server端)  |               | (server端)  |
        | - Endpoints |               | - Mailslot  |
        | - PipeMode  |               | - Buffers   |
        +-------------+               +-------------+
                |                            |
                v                            v
        +-------------+               +-------------+
        | Cc/2k list  |               | Cc/2k list  |
        | (数据队列)  |               | (消息队列)  |
        +-------------+               +-------------+

9.11.1 命名管道概述

Windows 命名管道是 基于文件系统接口的进程间通信 机制。它有以下几个核心特性:

  1. 路径名访问 :通过 \\.\Pipe\PipeName 访问(DOS 设备视图 \\.\pipe\PipeName
  2. 服务端/客户端模型 :服务端用 CreateNamedPipeW 创建,客户端用 CreateFileW 连接
  3. 多种模式
    • 单工/双工(duplex)
    • 字节流/消息流(byte/msg)
    • 阻塞/非阻塞
  4. 安全描述符:管道有自己的 SD,可控制客户端访问

命名管道的使用

c 复制代码
// 服务端
HANDLE hPipe = CreateNamedPipeW(
    L"\\\\.\\pipe\\MyPipe",          // 管道名
    PIPE_ACCESS_DUPLEX,              // 双工
    PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
    PIPE_UNLIMITED_INSTANCES,        // 最大实例
    512, 512, 0, NULL);

while (TRUE) {
    ConnectNamedPipe(hPipe, NULL);   // 等待客户端
    // ... 处理 ...
    DisconnectNamedPipe(hPipe);
}

// 客户端
HANDLE hPipe = CreateFileW(
    L"\\\\.\\pipe\\MyPipe",
    GENERIC_READ | GENERIC_WRITE,
    0, NULL, OPEN_EXISTING, 0, NULL);

9.11.2 npfs.sys 初始化

main.c(file:///d:/reactos/drivers/filesystems/npfs/main.c) 中:

c 复制代码
PDEVICE_OBJECT NpfsDeviceObject;
PVOID NpAliases;
PNPFS_ALIAS NpAliasList;
PNPFS_ALIAS NpAliasListByLength[MAX_INDEXED_LENGTH + 1 - MIN_INDEXED_LENGTH];

FAST_IO_DISPATCH NpFastIoDispatch = {
    sizeof(FAST_IO_DISPATCH),
    NULL,           // FastIoCheck
    NpFastRead,     // FastIoRead
    NpFastWrite,    // FastIoWrite
};

DriverEntry 核心逻辑

c 复制代码
NTSTATUS NTAPI
DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
    PDEVICE_OBJECT DeviceObject;
    NTSTATUS Status;

    // 1. 创建设备对象 \Device\NamedPipe
    RtlInitUnicodeString(&DeviceName, L"\\Device\\NamedPipe");
    Status = IoCreateDevice(DriverObject, 0, &DeviceName,
                            FILE_DEVICE_NAMED_PIPE,
                            FILE_DEVICE_SECURE_OPEN,
                            FALSE, &DeviceObject);
    NpfsDeviceObject = DeviceObject;

    // 2. 初始化别名(Aliases)
    NpInitializeAliases();

    // 3. 初始化全局锁
    ExInitializeFastMutex(&NpfsMutex);

    // 4. 注册 Fast I/O 派发表
    DriverObject->FastIoDispatch = &NpFastIoDispatch;

    // 5. 注册派发函数
    DriverObject->MajorFunction[IRP_MJ_CREATE]              = NpCreate;
    DriverObject->MajorFunction[IRP_MJ_CREATE_NAMED_PIPE]   = NpCreateNamedPipe;
    DriverObject->MajorFunction[IRP_MJ_CLOSE]               = NpClose;
    DriverObject->MajorFunction[IRP_MJ_READ]                = NpRead;
    DriverObject->MajorFunction[IRP_MJ_WRITE]               = NpWrite;
    DriverObject->MajorFunction[IRP_MJ_CLEANUP]             = NpCleanup;
    DriverObject->MajorFunction[IRP_MJ_QUERY_INFORMATION]   = NpQueryInformation;
    DriverObject->MajorFunction[IRP_MJ_SET_INFORMATION]     = NpSetInformation;
    DriverObject->MajorFunction[IRP_MJ_QUERY_VOLUME_INFORMATION] = NpQueryVolumeInformation;
    DriverObject->MajorFunction[IRP_MJ_DIRECTORY_CONTROL]   = NpDirectoryControl;
    DriverObject->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL] = NpFileSystemControl;
    DriverObject->MajorFunction[IRP_MJ_WAIT]                = NpWait;
    DriverObject->MajorFunction[IRP_MJ_FLUSH_BUFFERS]       = NpFlushBuffers;

    return STATUS_SUCCESS;
}

关键点

  • 特殊 MajorFunctionIRP_MJ_CREATE_NAMED_PIPE(仅 npfs/msfs 使用)
  • FastIO 支持NpFastReadNpFastWrite 在 Fast I/O 路径优化
  • 命名管道不需要卷设备 :单一设备对象 NpfsDeviceObject 即可

9.11.3 命名管道的创建与连接

NpCreateNamedPipe(服务端)

c 复制代码
NTSTATUS NTAPI
NpCreateNamedPipe(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    PIO_STACK_LOCATION IoStack = IoGetCurrentIrpStackLocation(Irp);
    PFILE_OBJECT FileObject = IoStack->FileObject;
    PIO_CREATE_NAMED_PIPE_PARAMETERS Params = &IoStack->Parameters.CreatePipe;
    UNICODE_STRING PipeName;
    PNPFS_FCB ServerFcb, ClientFcb;
    PNPFS_CCB ServerCcb;
    NTSTATUS Status;

    // 1. 构造完整路径 \Device\NamedPipe\PipeName
    PipeName.Buffer = ...;
    PipeName.Length = sizeof(L"\\Device\\NamedPipe\\") - 2 * sizeof(WCHAR) + ...;
    RtlCopyUnicodeString(&PipeName, L"\\Device\\NamedPipe\\");
    RtlAppendUnicodeStringToString(&PipeName, Params->PipeName);

    // 2. 查找或创建 FCB(File Control Block)
    Status = NpCreateServerFcb(&PipeName, Params->MaximumInstances, &ServerFcb);

    // 3. 创建服务端 CCB
    ServerCcb = NpCreateCcb(ServerFcb, FileObject, ...);

    // 4. 关联 FileObject 和 CCB
    FileObject->FsContext = ServerCcb;
    FileObject->FsContext2 = ServerFcb;

    return STATUS_SUCCESS;
}

NpCreate(客户端)

客户端的 CreateFileW("\\\\.\\pipe\\PipeName", ...) 转化为 NpCreate

c 复制代码
NTSTATUS NTAPI
NpCreate(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    // 1. 查找命名管道名对应的 FCB
    ServerFcb = NpFindFcbByName(&PipeName);

    // 2. 找到:创建客户端 CCB
    ClientCcb = NpCreateCcb(ServerFcb, FileObject, ...);
    FileObject->FsContext = ClientCcb;

    // 3. 触发服务端 CCB 的等待(ConnectNamedPipe)
    // 或立刻连接(如果服务端已 connect)
    NpSignalWaiter(ServerFcb);
}

关键数据结构

FCB(File Control Block)

NPFS_FCB 描述一个命名管道的"实例":

c 复制代码
typedef struct _NPFS_FCB {
    // 标识
    UNICODE_STRING PipeName;

    // 模式与标志
    ULONG NamedPipeType;       // PIPE_TYPE_BYTE/MESSAGE
    ULONG NamedPipeReadMode;   // PIPE_READMODE_BYTE/MESSAGE
    ULONG ReadDataMode;
    ULONG OperationMode;       // PIPE_OPERATIONS
    ULONG CompletionMode;      // PIPE_WAIT / PIPE_NOWAIT

    // 实例管理
    ULONG MaximumInstances;
    ULONG CurrentInstances;
    LIST_ENTRY ServerCcbList;  // 服务端 CCB 列表
    LIST_ENTRY ClientCcbList;  // 客户端 CCB 列表
    LIST_ENTRY WaitQueue;      // ConnectNamedPipe 等待队列

    // 状态
    BOOLEAN EndOfFile;         // 服务端关闭

    // 数据队列
    LIST_ENTRY DataQueueListHead; // 数据请求

    // 安全
    PSECURITY_DESCRIPTOR SecurityDescriptor;

    // 全局链表
    LIST_ENTRY FcbListEntry;
} NPFS_FCB, *PNPFS_FCB;
CCB(Context Control Block)

NPFS_CCB 描述一次打开的实例:

c 复制代码
typedef struct _NPFS_CCB {
    // 关联
    PNPFS_FCB Fcb;
    PFILE_OBJECT FileObject;

    // 标识
    ULONG CcbFlags;            // CCB_FLAG_SERVER / CCB_FLAG_CLIENT

    // 状态
    LIST_ENTRY CcbListEntry;

    // 读写状态
    // ...
} NPFS_CCB, *PNPFS_CCB;

9.11.4 命名管道的读写

NpWrite(写管道)

c 复制代码
NTSTATUS NTAPI
NpWrite(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    PNPFS_CCB Ccb = Irp->Tail.Overlay.CurrentStackLocation->FileObject->FsContext;
    PNPFS_FCB Fcb = Ccb->Fcb;
    PIO_STACK_LOCATION IoStack = IoGetCurrentIrpStackLocation(Irp);

    // 1. 验证客户端连接
    // 2. 构造数据条目
    // 3. 排队到 FCB->DataQueueListHead
    // 4. 唤醒等待的读端
    // 5. 完成 IRP
}

NpRead(读管道)

c 复制代码
NTSTATUS NTAPI
NpRead(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    PNPFS_CCB Ccb = Irp->Tail.Overlay.CurrentStackLocation->FileObject->FsContext;
    PNPFS_FCB Fcb = Ccb->Fcb;

    // 1. 如果有数据,立刻返回
    if (DataQueue 有数据) {
        RtlCopyMemory(Buffer, DataQueue->Buffer, Length);
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        return STATUS_SUCCESS;
    }

    // 2. 无数据,根据 Ccb->OperationMode 决定
    if (PIPE_NOWAIT) {
        // 立即返回 BROKEN_PIPE 或 0 字节
        return STATUS_PIPE_BROKEN;
    }

    // 3. 阻塞:把 IRP 排队
    InsertTailList(&Fcb->WaitQueue, &Irp->Tail.Overlay.ListEntry);
    IoMarkIrpPending(Irp);
    return STATUS_PENDING;
}

NpFastRead / NpFastWrite

Fast I/O 路径用于 同步ReadFile/WriteFile,无需 IRP 构造:

c 复制代码
BOOLEAN NTAPI
NpFastRead(IN PFILE_OBJECT FileObject,
           IN PLARGE_INTEGER FileOffset,
           IN ULONG Length,
           BOOLEAN Wait,
           IN ULONG LockKey,
           OUT PVOID Buffer,
           OUT PIO_STATUS_BLOCK IoStatus,
           IN PDEVICE_OBJECT DeviceObject)
{
    PNPFS_CCB Ccb = FileObject->FsContext;
    // ... 类似 NpRead 但不走 IRP 路径
}

9.11.5 邮槽概述

邮槽是 单向 进程间通信机制:

  1. 路径名访问\\.\Mailslot\MailslotName(DOS 视图)
  2. 服务端/客户端模型
    • 服务端用 CreateMailslotW 创建
    • 客户端用 CreateFileW("\\\\.\\mailslot\\MailslotName", ...) 打开
  3. 消息模式:消息边界保留
  4. 广播能力 :客户端可以写到 * 通配符,所有邮槽都收到
  5. 多对一:多个客户端写,一个服务端读

邮槽与命名管道对比

维度 命名管道 邮槽
方向 双向(双工/单工) 单向
模式 字节流/消息 仅消息
广播 不支持 支持(* 通配)
跨网络 支持(\\Server\Pipe\Pipe 不支持
速度 较慢

9.11.6 msfs.sys 初始化

msfs.c(file:///d:/reactos/drivers/filesystems/msfs/msfs.c):

c 复制代码
NTSTATUS NTAPI
DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
    UNICODE_STRING DeviceName;
    PDEVICE_OBJECT DeviceObject;
    NTSTATUS Status;

    // 1. 创建设备对象 \Device\Mailslot
    RtlInitUnicodeString(&DeviceName, L"\\Device\\Mailslot");
    Status = IoCreateDevice(DriverObject, 0, &DeviceName,
                            FILE_DEVICE_MAILSLOT,
                            FILE_DEVICE_SECURE_OPEN,
                            FALSE, &DeviceObject);
    MsfsDeviceObject = DeviceObject;

    // 2. 注册派发函数
    DriverObject->MajorFunction[IRP_MJ_CREATE]               = MsCreate;
    DriverObject->MajorFunction[IRP_MJ_CREATE_MAILSLOT]      = MsCreateMailslot;
    DriverObject->MajorFunction[IRP_MJ_CLOSE]                = MsClose;
    DriverObject->MajorFunction[IRP_MJ_READ]                 = MsRead;
    DriverObject->MajorFunction[IRP_MJ_WRITE]                = MsWrite;
    DriverObject->MajorFunction[IRP_MJ_CLEANUP]              = MsCleanup;
    DriverObject->MajorFunction[IRP_MJ_QUERY_INFORMATION]    = MsQueryInformation;
    DriverObject->MajorFunction[IRP_MJ_SET_INFORMATION]      = MsSetInformation;
    DriverObject->MajorFunction[IRP_MJ_QUERY_VOLUME_INFORMATION] = MsQueryVolumeInformation;
    DriverObject->MajorFunction[IRP_MJ_DIRECTORY_CONTROL]    = MsDirectoryControl;
    DriverObject->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL]  = MsFileSystemControl;
    DriverObject->MajorFunction[IRP_MJ_FLUSH_BUFFERS]        = MsFlushBuffers;

    return STATUS_SUCCESS;
}

msfs 与 npfs 非常类似,只是去掉了 IRP_MJ_WAIT(不支持阻塞读)。


9.11.7 邮槽的创建与通信

MsCreateMailslot(服务端)

create.c(file:///d:/reactos/drivers/filesystems/msfs/create.c):

c 复制代码
NTSTATUS NTAPI
MsCreateMailslot(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    PIO_STACK_LOCATION IoStack = IoGetCurrentIrpStackLocation(Irp);
    PMAILSLOT_CREATE_PARAMETERS Params = &IoStack->Parameters.CreateMailslot;
    PFILE_OBJECT FileObject = IoStack->FileObject;
    PMSFS_FCB Fcb;
    PMSFS_CCB Ccb;

    // 1. 创建 FCB
    Fcb = MsCreateFcb(Params->MailslotName, Params->MaximumMessageSize,
                      Params->Timeout, Params->SecurityAttributes);

    // 2. 创建服务端 CCB
    Ccb = MsCreateCcb(Fcb, FileObject);

    FileObject->FsContext = Ccb;
    return STATUS_SUCCESS;
}

MsCreate(客户端)

客户端 CreateFileW("\\\\.\\mailslot\\Foo") 触发 MsCreate

c 复制代码
NTSTATUS NTAPI
MsCreate(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    // 1. 查找邮槽名对应的 FCB
    Fcb = MsFindFcbByName(&MailslotName);
    if (!Fcb) return STATUS_OBJECT_NAME_NOT_FOUND;

    // 2. 创建客户端 CCB
    Ccb = MsCreateCcb(Fcb, FileObject);
    FileObject->FsContext = Ccb;

    return STATUS_SUCCESS;
}

广播写(* 通配)

WriteFile\\*\mailslot\MyMailslot 时,MsWrite遍历所有邮槽 把消息写入每个 FCB 的消息队列。

关键数据结构

c 复制代码
typedef struct _MSFS_FCB {
    UNICODE_STRING MailslotName;
    ULONG MaximumMessageSize;
    LARGE_INTEGER Timeout;          // 读超时
    LIST_ENTRY ClientCcbList;       // 等待的客户端 CCB
    LIST_ENTRY ServerCcbList;       // 服务端 CCB(一个)
    LIST_ENTRY DataQueueListHead;   // 数据消息
    PSECURITY_DESCRIPTOR SecurityDescriptor;
    LIST_ENTRY FcbListEntry;
} MSFS_FCB;

typedef struct _MSFS_CCB {
    PMSFS_FCB Fcb;
    PFILE_OBJECT FileObject;
    LIST_ENTRY CcbListEntry;
} MSFS_CCB;

深入剖析:命名管道与邮槽的内部机制

FCB/CCB/VCB 的层次化设计

命名管道和邮槽的文件系统驱动采用了层次化的控制块设计,这是 Windows 文件系统驱动的标准模式。理解 FCB(File Control Block)、CCB(Context Control Block)和 VCB(Volume Control Block)的关系对于掌握这两种 IPC 机制至关重要。

VCB(卷控制块) :在 npfs 和 msfs 中,VCB 的概念被简化了。由于命名管道和邮槽不使用物理存储介质,它们只有一个全局的"卷"设备对象。从 ReactOS 的 npfs.h 可以看到,NPFS 使用 NPFS_NTC_VCB 节点类型代码标识 VCB,但实际上 VCB 的功能被合并到了全局设备扩展中。

FCB(文件控制块) :FCB 代表一个命名的管道或邮槽实例 。对于命名管道,FCB 存储管道名称、模式(字节流/消息流)、最大实例数、当前实例数、安全描述符等。从 npfs.hNP_FCB 结构可以看到,FCB 包含 CcbList(连接到该管道的客户端 CCB 列表)、WaitQueue(等待连接的队列)、DataQueueListHead(数据队列)等关键字段。

CCB(上下文控制块) :CCB 代表一次打开的管道或邮槽句柄 。每个客户端或服务端打开管道时都会创建一个 CCB。CCB 关联到对应的 FCB,并存储该连接的状态信息(如是否为服务端、读写模式、超时设置等)。从 npfs.hNP_CCB 结构可以看到,CCB 包含 Fcb 指针、FileObject 指针、CcbFlags 标志等。

命名管道的连接与等待机制

命名管道的连接过程是异步的,涉及服务端和客户端的协调。从 ReactOS 的 create.c 可以看到,NpCreateNamedPipe(服务端)和 NpCreate(客户端)的执行流程:

服务端创建管道

  1. 服务端调用 CreateNamedPipeW,I/O 管理器将其转换为 IRP_MJ_CREATE_NAMED_PIPE
  2. NpCreateNamedPipe 解析管道名称,构造完整路径 \Device\NamedPipe\PipeName
  3. 调用 NpCreateServerFcb 查找或创建 FCB。如果 FCB 已存在,增加实例计数;如果不存在,创建新的 FCB。
  4. 创建服务端 CCB,设置 CcbFlags = CCB_FLAG_SERVER,关联到 FCB。
  5. 将 CCB 添加到 FCB 的 ServerCcbList
  6. 将 FileObject 的 FsContext 指向 CCB,FsContext2 指向 FCB。

客户端连接管道

  1. 客户端调用 CreateFileW("\\\\.\\pipe\\PipeName", ...),I/O 管理器将其转换为 IRP_MJ_CREATE
  2. NpCreate 解析管道名称,查找对应的 FCB。
  3. 如果 FCB 不存在,返回 STATUS_OBJECT_NAME_NOT_FOUND
  4. 如果 FCB 存在,检查当前实例数是否超过最大实例数。如果超过,返回 STATUS_PIPE_BUSY
  5. 创建客户端 CCB,设置 CcbFlags = CCB_FLAG_CLIENT,关联到 FCB。
  6. 将 CCB 添加到 FCB 的 ClientCcbList
  7. 触发服务端等待 :如果服务端正在调用 ConnectNamedPipe 等待连接,NpCreate 会唤醒等待队列中的服务端 CCB。

ConnectNamedPipe 的等待机制

服务端调用 ConnectNamedPipe 时,如果还没有客户端连接,npfs 会将服务端的 IRP 排队到 FCB 的 WaitQueue,并返回 STATUS_PENDING。当客户端调用 CreateFileW 连接时,NpCreate 会从等待队列中取出服务端的 IRP,设置完成状态为 STATUS_SUCCESS,并完成该 IRP。服务端的 ConnectNamedPipe 调用因此返回。

数据队列与读写同步

命名管道的数据传输通过数据队列 实现。从 npfs.hNP_DATA_QUEUE 结构可以看到,每个 CCB 有两个数据队列:一个用于客户端到服务端的数据,一个用于服务端到客户端的数据(如果是双工模式)。

写操作(NpWrite)

  1. 客户端调用 WriteFile,I/O 管理器发送 IRP_MJ_WRITE
  2. NpWrite 从 FileObject 的 FsContext 获取 CCB,从 CCB 获取 FCB。
  3. 检查管道是否已连接(客户端 CCB 是否关联到服务端 CCB)。如果未连接,返回 STATUS_PIPE_DISCONNECTED
  4. 构造 NP_DATA_QUEUE_ENTRY,包含数据缓冲区指针和长度。
  5. 将数据条目插入到对端的数据队列(如果是客户端写,则插入到服务端 CCB 的数据队列)。
  6. 唤醒读端 :如果服务端正在调用 ReadFile 等待数据,NpWrite 会唤醒等待的读 IRP。
  7. 完成写 IRP,返回写入的字节数。

读操作(NpRead)

  1. 服务端调用 ReadFile,I/O 管理器发送 IRP_MJ_READ
  2. NpRead 从 FileObject 的 FsContext 获取 CCB,从 CCB 获取 FCB。
  3. 检查数据队列是否有数据。如果有,从队列中取出数据条目,复制到用户缓冲区,完成 IRP。
  4. 如果数据队列为空,检查管道的完成模式:
    • PIPE_WAIT (阻塞模式):将读 IRP 排队到 FCB 的 WaitQueue,返回 STATUS_PENDING。当客户端写入数据时,NpWrite 会唤醒该 IRP。
    • PIPE_NOWAIT (非阻塞模式):立即返回 STATUS_PIPE_EMPTY 或 0 字节。
  5. 对于消息模式管道,还需要处理消息边界。如果用户缓冲区小于消息大小,返回 STATUS_BUFFER_OVERFLOW 并保留剩余数据在队列中。

Fast I/O 路径优化

命名管道驱动实现了 Fast I/O 路径,用于优化同步的读写操作。Fast I/O 允许在不需要构造 IRP 的情况下直接进行数据传输,从而减少内核开销。

从 ReactOS 的 main.c 可以看到,npfs 在 DriverEntry 中注册了 NpFastIoDispatch,包含 NpFastReadNpFastWrite 函数。当应用层调用同步的 ReadFileWriteFile 时,I/O 管理器会先尝试调用 Fast I/O 函数。如果 Fast I/O 成功,直接返回;如果失败(例如需要阻塞等待),则回退到正常的 IRP 路径。

NpFastRead 的实现

  1. 检查 FileObject 的 FsContext 获取 CCB。
  2. 检查数据队列是否有数据。如果有且不需要阻塞,直接复制数据到用户缓冲区,返回 TRUE
  3. 如果需要阻塞或发生错误,返回 FALSE,I/O 管理器会回退到 NpRead

NpFastWrite 的实现

  1. 检查管道是否已连接。
  2. 将数据插入到对端的数据队列。
  3. 唤醒等待的读端(如果有)。
  4. 返回 TRUE 表示成功。

邮槽的广播机制

邮槽的广播功能是命名管道不具备的独特特性。当客户端向 \\*\mailslot\MailslotName 写入数据时,msfs 会将消息广播到所有同名的邮槽

从 ReactOS 的 rw.c 可以看到,MsWrite 函数在处理广播写时执行以下步骤:

  1. 检查管道名称是否以 \\*\mailslot\ 开头。
  2. 如果是广播写,遍历全局 FCB 列表(MSFS_DEVICE_EXTENSION.FcbListHead)。
  3. 对于每个 FCB,检查名称是否匹配 MailslotName
  4. 如果匹配,构造 MSFS_MESSAGE 结构,包含消息数据和发送者信息。
  5. 将消息插入到 FCB 的 MessageListHead 队列。
  6. 唤醒等待的服务端(如果有)。

消息结构

msfs.hMSFS_MESSAGE 结构可以看到,消息包含 Size(消息大小)、Buffer(消息数据)。邮槽的消息是不可分割的,即读操作必须一次性读取整个消息,不能部分读取。

安全描述符与访问控制

命名管道和邮槽都支持安全描述符 ,用于控制客户端的访问权限。从 npfs.hNP_FCBmsfs.hMSFS_FCB 可以看到,FCB 结构包含 SecurityDescriptor 字段。

安全描述符的创建

服务端在创建管道或邮槽时,可以通过 SECURITY_ATTRIBUTES 参数指定安全描述符。如果没有指定,系统会使用默认的安全描述符。安全描述符包含:

  • 所有者 SID:管道或邮槽的所有者。
  • 组 SID:所有者所属的组。
  • DACL(自主访问控制列表):定义哪些用户或组可以访问,以及访问权限(读、写、执行)。
  • SACL(系统访问控制列表):定义审计策略(可选)。

访问检查

当客户端调用 CreateFileW 打开管道或邮槽时,I/O 管理器会执行访问检查。它比较客户端的访问令牌与 FCB 的安全描述符,验证客户端是否有请求的访问权限。如果访问被拒绝,返回 STATUS_ACCESS_DENIED

与 Windows 实现的对比分析

ReactOS 的 npfs 和 msfs 实现与 Windows 存在以下差异:

  1. 代码来源:ReactOS 的 npfs 和 msfs 是独立实现的,虽然功能上力求与 Windows 兼容,但在某些边缘情况(如复杂的管道模式组合、特殊的超时行为)上可能不够完善。

  2. Fast I/O 的完整性 :Windows 的 npfs 实现了完整的 Fast I/O 路径,包括 FastIoCheckIfPossibleFastIoReadFastIoWrite 等。ReactOS 的实现相对简化,主要关注 FastIoReadFastIoWrite

  3. 别名机制 :从 npfs.h 可以看到,ReactOS 的 npfs 实现了别名机制NpAliasListNpAliasListByLength),允许为同一个管道创建多个名称。这是 ReactOS 特有的扩展,Windows 的 npfs 不支持。

  4. BugCheck 支持 :ReactOS 的 npfs 定义了详细的 BugCheck 代码(NPFS_BUGCHECK_*),用于在发生严重错误时生成蓝屏转储。每个源文件都有唯一的 NPFS_BUGCHECK_FILE_ID,便于定位问题。

  5. 邮槽的超时处理 :ReactOS 的 msfs 实现了邮槽的读超时机制。从 MSFS_FCBTimeOut 字段可以看到,服务端可以设置读操作的超时时间。如果超时,读操作返回 STATUS_IO_TIMEOUT

调试技巧与常见问题

  1. 管道连接失败 :如果客户端无法连接到管道,检查管道名称是否正确、服务端是否已调用 CreateNamedPipeW、当前实例数是否超过最大实例数。使用 WinDbg 的 !devext 查看 FCB 的 CurrentInstancesMaximumInstances

  2. 读写挂起 :如果读写操作挂起,检查数据队列是否为空、管道是否已连接、完成模式是否为 PIPE_WAIT。使用 !irp 查看 IRP 是否排队到 WaitQueue

  3. 消息模式问题 :如果使用消息模式管道,确保读写缓冲区大小一致。如果用户缓冲区小于消息大小,读操作会返回 STATUS_BUFFER_OVERFLOW,剩余数据保留在队列中。

  4. 邮槽广播失败 :如果广播写失败,检查是否有同名的邮槽存在。使用 WinDbg 的 !devext 查看 MSFS_DEVICE_EXTENSION.FcbListHead,确认 FCB 列表是否正确。

  5. 安全描述符错误 :如果客户端访问被拒绝,检查 FCB 的安全描述符是否正确设置。使用 !sd <security_descriptor_address> 查看安全描述符的内容。

  6. ReactOS 特有的调试输出 :ReactOS 的 npfs 和 msfs 启用了 DPRINTDPRINT1 宏进行调试输出。设置 HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print FilterNPFSMSFS 组件的级别为 0xFF,可以在调试串口看到详细的连接、读写、等待日志。


9.11.8 总结

关键要点

  1. npfs 与 msfs 都是文件系统驱动:通过 FS 接口实现 IPC
  2. 三件套:FCB(管道路由)、CCB(连接状态)、DataQueue(数据)
  3. IRP_MJ_CREATE_NAMED_PIPE 是 npfs 特有
  4. IRP_MJ_CREATE_MAILSLOT 是 msfs 特有
  5. FastIO 支持 :npfs 提供 NpFastRead/NpFastWrite
  6. 邮槽广播* 通配写
  7. 内存数据队列:不像磁盘 FS 那样使用磁盘存储

下一节 9.12 将剖析 MDL(Memory Descriptor List)。


9.11.9 设计哲学问答

Q1:为什么命名管道和邮槽要伪装成文件系统驱动,而不是直接提供内核 API?

A统一编程模型 + 复用 I/O 管理器的完整功能

如果 npfs/msfs 直接提供内核 API(如 NpCreatePipeNpReadPipe),应用程序需要学习两套 I/O 模型------一套用于文件,一套用于管道。伪装成文件系统驱动后:

  • 零学习成本 :应用程序使用通用的 CreateFileWReadFileWriteFileCloseHandle 操作管道,与操作普通文件完全一致;
  • 安全继承 :管道的安全检查自动复用 I/O 管理器的 SeAccessCheck 和文件打开时的权限验证逻辑;
  • 异步 I/O 免费获得ReadFileExWriteFileEx 的重叠 I/O(Overlapped I/O)机制由 I/O 管理器原生支持,npfs 无需单独实现;
  • 句柄管理复用 :管道句柄的引用计数、清理、关闭由 ObCreateHandle 和文件对象(FILE_OBJECT)的自动清理机制管理。

如果自己实现内核 API,上述所有功能都需要 npfs 从零实现,不仅代码量大,还容易出现句柄泄漏和权限绕过等安全漏洞。

Q2:为什么 npfs 使用 FCB/CCB 两级控制块而不是三件套(FCB/CCB/VCB)?

A命名管道没有"卷"的概念

传统文件系统驱动(如 fastfat、ntfs)使用 VCB(Volume Control Block)管理存储介质的卷信息(扇区大小、文件系统类型、位图等)。命名管道和邮槽不使用物理存储介质,只有单个全局设备对象 \Device\NamedPipe,因此 VCB 被简化为全局变量:

  • npfsNpfsDeviceObject(全局设备对象)和 NpfsMutex(全局锁)替代了 VCB 的角色;
  • msfsMsfsDeviceObject(全局设备对象)替代了 VCB。

FCB 用于标识每个命名的管道实例(存储管道名、模式、实例计数),CCB 用于标识每次打开的句柄(存储该客户端或服务端连接的状态)。这种两级设计足够表达"一个命名管道可以被多个客户端同时打开"的语义,不需要第三级。

如果引入 VCB,反而增加了不必要的抽象层次,因为命名管道没有分页文件、没有卷名称、没有根目录,VCB 的大部分字段都是空的。

Q3:为什么命名管道支持字节流和消息流两种模式,而邮槽只支持消息模式?

A使用场景决定协议语义

命名管道的设计目标是通用 IPC,必须覆盖不同场景:

  • 字节流模式PIPE_TYPE_BYTE):适合无边界的数据传输,如重定向标准输入/输出(| 管道操作符),或传输二进制数据流。字节流模式下数据是连续的,读端可以任意大小读取,不保留写入时的边界。
  • 消息流模式PIPE_TYPE_MESSAGE):适合有明确消息边界的通信,如数据库查询/响应、RPC 调用。消息流模式下每次 WriteFile 写入的数据形成一条独立消息,读端必须读取完整的消息(或使用 PIPE_READMODE_MESSAGE 逐条读取)。

邮槽的设计目标是单向广播通知,本质就是"发消息"------发送方产生一条消息,接收方消费一条消息。字节流对广播场景没有意义(广播不需要连续流),因此 msfs 只提供消息模式。

从实现角度看,消息模式的边界保存在 NP_DATA_QUEUE_ENTRY 结构中的 EntrySize 字段,读操作检查用户缓冲区大小与消息大小的关系,决定返回 STATUS_SUCCESS 还是 STATUS_BUFFER_OVERFLOW。邮槽的 MSFS_MESSAGE 结构同样保留了消息边界,但不存在字节流的选项。

Q4:为什么命名管道使用 IRP_MJ_CREATE_NAMED_PIPE 特殊 MajorFunction 而不是复用 IRP_MJ_CREATE

A参数差异与语义区分

IRP_MJ_CREATEParameters.Create 只能传递文件名、打开方式、共享模式等标准参数。而 CreateNamedPipeW 需要传递大量管道特有参数:

复制代码
PipeMode:        PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT
MaxInstances:    PIPE_UNLIMITED_INSTANCES
OutBufferSize:   4096
InBufferSize:    4096
DefaultTimeOut:  0

这些参数通过 IRP_MJ_CREATE_NAMED_PIPEParameters.CreatePipe 传递,该结构包含 CreatePipe.NamedPipeTypeCreatePipe.NamedPipeModeCreatePipe.MaximumInstancesCreatePipe.OutBufferSizeCreatePipe.InBufferSizeCreatePipe.DefaultTimeOut 等字段。

从结构设计角度看,将管道创建与文件创建语义分离使得:

  1. 派发清晰NpCreateNamedPipe 处理服务端创建,NpCreate 处理客户端连接,两个函数职责分离;
  2. 参数验证简单NpCreateNamedPipe 直接从 IRP 栈提取 IO_CREATE_NAMED_PIPE_PARAMETERS,无需从 Parameters.Create 中解析额外的参数块;
  3. I/O 管理器路由 :系统服务 NtCreateNamedPipeFile 直接生成 IRP_MJ_CREATE_NAMED_PIPENtCreateFile 生成 IRP_MJ_CREATE,两者在 I/O 管理器层面就完成路由。

Q5:为什么命名管道在无数据可用时使用 IRP 排队机制阻塞等待(PIPE_WAIT),而不是简单返回错误让应用轮询?

A零 CPU 消耗 + 即时唤醒

PIPE_WAIT 模式将读 IRP 排队到 FCB 的 WaitQueue,并返回 STATUS_PENDING。这实现了高效的等待语义:

  • 线程挂起:调用线程进入内核态等待 IRP 完成,不消耗 CPU 时间片。轮询模式(PIPE_NOWAIT)会浪费 CPU 不断查询数据就绪状态;
  • 即时唤醒 :当写入端调用 NpWrite 插入数据时,它会遍历 WaitQueue 中的所有等待 IRP,通过 IoCompleteRequest 将其完成。等待线程从"等待"状态直接变为"就绪"状态,延迟通常在微秒级别;
  • 零内存拷贝(Zero-Copy)优化可能:在某些实现中,写入端可以直接将数据页面映射到等待读 IRP 的缓冲区,避免中间拷贝。
c 复制代码
// 阻塞模式:IRP 排队,零 CPU 消耗
InsertTailList(&Fcb->WaitQueue, &Irp->Tail.Overlay.ListEntry);
IoMarkIrpPending(Irp);
return STATUS_PENDING;   // 线程挂起

// 非阻塞模式:立即返回,应用可轮询
return STATUS_PIPE_EMPTY; // 线程继续运行

如果管道驱动直接返回错误让应用轮询,不仅浪费 CPU,而且在消息到达时无法及时通知接收方(需要应用主动轮询),延迟从微秒级别退化到毫秒级别(取决于轮询间隔)。

Q6:为什么邮槽支持广播(* 通配符)而命名管道不支持?

A设计目标不同------通知 vs. 连接

邮槽的设计目标是一对多的通知分发 。当客户端向 \\*\mailslot\Notifications 写入时,msfs 遍历所有 FCB,将消息复制到每个匹配的邮槽队列中。这适用于:

  • 服务器广播状态变更(如用户登录/注销通知);
  • 系统日志分发(多个监控进程同时接收日志);
  • 服务发现(客户端广播查询,多个服务响应)。

命名管道的设计目标是点对点的可靠连接,广播与连接模型冲突:

  • 连接语义 :命名管道需要 ConnectNamedPipe 建立连接,连接后数据定向到特定客户端。广播意味着所有服务端实例都收到相同数据,这与"一个客户端连接一个服务端"的模型矛盾;
  • 流量控制:广播时,慢速服务端会阻塞快速写入端。命名管道的阻塞写语义(PIPE_WAIT)假设只有一个接收方,广播时无法决定以哪个服务端的流速为准;
  • 消息备份:广播需要为每个服务端复制消息副本,邮槽的消息通常较小(典型值 < 424 字节),复制代价可接受。命名管道传输可能很大(MB 级),广播复制代价过高。

从实现角度,MsWrite 的广播逻辑遍历 MSFS_DEVICE_EXTENSION.FcbListHead 全局链表,对每个名称匹配的 FCB 插入 MSFS_MESSAGE。命名管道的 NpWrite 则从 Ccb->Fcb 直接定位到特定连接,不会遍历 FCB 列表。

Q7:为什么 npfs 需要 Fast I/O 路径(NpFastRead/NpFastWrite)而 msfs 不需要?

A性能敏感度差异

Fast I/O 是一种绕过 IRP 分配的优化路径,适用于同步、非缓存的 I/O 操作:

  • IRP 路径ReadFileNtReadFileIoCallDriver → 分配 IRP → NpRead → 处理 → IoCompleteRequest → 释放 IRP。整个路径涉及 IRP 的分配、初始化、销毁,每次约 1-2 µs 开销;
  • Fast I/O 路径ReadFileNtReadFileIoCallDriver → 调用 FastIoDispatch->FastIoReadNpFastRead → 返回。没有 IRP 分配的开销。

命名管道是高性能 IPC机制,广泛应用于 SQL Server、远程过程调用(RPC)等场景,每次读写的数据量小(几十到几百字节),IRP 分配开销占比大,Fast I/O 可以显著提升吞吐量:

复制代码
IRP 路径:     ~2000 条消息/秒(IRP 分配/释放占 40% 开销)
Fast I/O 路径: ~3500 条消息/秒(无 IRP 开销)

邮槽的使用场景是低频通知(系统广播、日志),每秒通常只有几十条消息,IRP 分配开销不是瓶颈。ReactOS 的 msfs 没有实现 Fast I/O 路径,所有读写都走标准 IRP 路径,对性能没有实质性影响。

从 ReactOS 的 main.c 可以看到,NpFastIoDispatch 被注册在 DriverObject->FastIoDispatch,I/O 管理器在发送 IRP_MJ_READ 之前会检查是否有 Fast I/O 回调可用。

Q8:为什么 ConnectNamedPipe 需要 WaitQueue 异步等待机制,而不是简单地同步阻塞?

A支持重叠 I/O(Overlapped I/O)和多线程服务端架构

如果 ConnectNamedPipe 采用同步阻塞,服务端只能串行处理客户端连接:

c 复制代码
// 同步模型:一次只能处理一个连接
while (TRUE) {
    ConnectNamedPipe(hPipe, NULL);  // 阻塞直到有客户端连接
    ProcessClient(hPipe);            // 处理完才能接受下一个连接
}

使用异步等待机制(WaitQueue),服务端可以同时等待多个管道实例的连接:

c 复制代码
// 异步模型:多个管道同时监听
for (int i = 0; i < NUM_INSTANCES; i++) {
    OVERLAPPED ov = { 0 };
    ConnectNamedPipe(hPipes[i], &ov);  // 全部异步等待
}
WaitForMultipleObjects(NUM_INSTANCES, hEvents, FALSE, INFINITE);

从 ReactOS 的 create.c 可以看到,NpCreateNamedPipe 在创建服务端 CCB 后,如果尚未连接,将服务端的 IRP 排队到 FCB->WaitQueue。当客户端调用 NpCreate 连接时,NpCreateWaitQueue 中取出服务端的 IRP 并完成,触发服务端的 ConnectNamedPipe 返回。这种机制:

  • 兼容 Overlapped I/OConnectNamedPipe 返回 STATUS_PENDING 后,服务端可以通过 GetOverlappedResultWaitForSingleObject 等待完成;
  • 支持取消 :服务端可以调用 CancelIo 取消等待,npfs 从 WaitQueue 移除对应的 IRP;
  • 可扩展性:单线程可以管理数百个监听中的管道实例。

Q9:为什么 ReactOS 的 npfs 实现了别名机制(NpAliasList)而 Windows 官方没有?

A兼容性适配------为不同前缀路径提供相同的管道访问

Windows 应用程序可能通过不同的路径前缀访问同一个命名管道,常见的包括:

  • \\.\pipe\PipeName(DOS 设备视图)
  • \Device\NamedPipe\PipeName(NT 设备视图)
  • \\Server\pipe\PipeName(远程管道,重定向到本地)

ReactOS 的别名机制允许 npfs 为同一个物理管道注册多个名称别名,当客户端通过任意别名打开时,npfs 都能正确解析到同一个 FCB。从 npfs.h 可以看到:

c 复制代码
typedef struct _NPFS_ALIAS {
    UNICODE_STRING AliasName;
    UNICODE_STRING ActualName; // 真实管道名
    // ...
} NPFS_ALIAS;

NpInitializeAliasesDriverEntry 中初始化别名列表,NpAliasListByLength 数组按名称长度索引,加速查找。这种设计的原因包括:

  • Windows XP/2003 兼容 :某些 Windows 版本允许通过 \Device\NamedPipe\\\.\pipe\ 两种路径访问管道,ReactOS 必须在两种路径下都能正确解析;
  • Win32 子系统映射 :Win32 子系统(winsrv.dllcsrss.exe)可能将 DOS 路径映射为不同的 NT 路径,别名机制允许 npfs 适应这种映射差异。

这是 ReactOS 为了与 Windows 二进制兼容而添加的"补丁式"功能,Windows 官方出于简洁性考虑未实现。

Q10:为什么邮槽的消息是"不可分割"的(读操作必须一次读取完整消息),而命名管道的消息模式允许部分读取?

A协议语义决定消息处理策略

邮槽的消息不可分割由以下设计选择决定:

  • 一次性投递:邮槽是"发后即忘"(fire-and-forget)模型,发送方写入一条完整消息后即认为投递完成。接收方如果只能读取部分消息,剩余部分的数据将丢失(邮槽不保留部分消费状态);
  • 消息边界强保护MsRead 检查用户缓冲区大小与 MSFS_MESSAGE.Size 的比较,如果用户缓冲区小于消息大小,返回 STATUS_BUFFER_OVERFLOW。应用程序必须使用 GetMailslotInfo 查询消息大小,然后分配足够大的缓冲区一次读取;
  • 广播场景的特殊性:广播时消息被复制到多个 FCB 的消息队列,如果允许部分读取,各个接收方读取消息的状态不同步,消息队列管理变得极其复杂。

命名管道的消息模式允许 PIPE_READMODE_MESSAGE 设置是否保持消息边界:

c 复制代码
// 邮槽:强制消息边界,必须完整读取
Status = MsRead(...);  // 缓冲区 < 消息大小 → STATUS_BUFFER_OVERFLOW

// 命名管道:可选消息模式
SetNamedPipeHandleState(hPipe, &PIPE_READMODE_MESSAGE, NULL, NULL); // 消息模式
SetNamedPipeHandleState(hPipe, &PIPE_READMODE_BYTE, NULL, NULL);    // 字节模式

命名管道的灵活性来源于它的双向通信场景------服务端和客户端可能协商不同的读取策略,消息模式下仍然保留边界信息,但允许读取端选择如何消费。


本章代码索引

文件 内容
main.c(file:///d:/reactos/drivers/filesystems/npfs/main.c) DriverEntry、别名
create.c(file:///d:/reactos/drivers/filesystems/npfs/create.c) NpCreateNpCreateNamedPipe
read.c(file:///d:/reactos/drivers/filesystems/npfs/read.c) NpReadNpFastRead
write.c(file:///d:/reactos/drivers/filesystems/npfs/write.c) NpWriteNpFastWrite
close.c(file:///d:/reactos/drivers/filesystems/npfs/close.c) NpClose
cleanup.c(file:///d:/reactos/drivers/filesystems/npfs/cleanup.c) NpCleanup
fsctrl.c(file:///d:/reactos/drivers/filesystems/npfs/fsctrl.c) 文件系统控制
waitsup.c(file:///d:/reactos/drivers/filesystems/npfs/waitsup.c) ConnectNamedPipe 等待
npfs.h(file:///d:/reactos/drivers/filesystems/npfs/npfs.h) 数据结构定义
msfs.c(file:///d:/reactos/drivers/filesystems/msfs/msfs.c) DriverEntry
create.c(file:///d:/reactos/drivers/filesystems/msfs/create.c) MsCreateMsCreateMailslot
rw.c(file:///d:/reactos/drivers/filesystems/msfs/rw.c) MsReadMsWrite
msfs.h(file:///d:/reactos/drivers/filesystems/msfs/msfs.h) 数据结构
相关推荐
x***r1511 小时前
Krita 5.2.13 安装教程 Windows版:自定义路径+开源绘画软件配置指南
windows
就改了2 小时前
Windows Elasticsearch 完整上手教程
大数据·windows·elasticsearch
fastjson_2 小时前
备份与恢复驱动
windows
caimouse2 小时前
Reactos 第 9 章 设备驱动 — 9.12 MDL
windows
daly5202 小时前
Notepad++怎么下载?2026最新版Notepad++安装教程(Windows免费文本编辑器)
windows·notepad++·notepad
冰帆<3 小时前
[特殊字符] 深度起底:突破火山引擎 Ark-Helper 的 Linux 底层环境死锁,顺手魔改一份 Windows 一键安装脚本!
linux·windows·火山引擎
良枫3 小时前
自进化 agent:核心模块一任务规划器 Planner
java·服务器·windows
可乐要加冰^-^3 小时前
云雀文档下载
windows·git·github·石墨文档
caimouse4 小时前
Reactos 第 9 章 设备驱动 — 9.9 磁盘的设备驱动堆叠
windows·嵌入式硬件