第 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→ 单向、消息模式、多对一广播
它们通过 CreateFileW、ReadFile、WriteFile 等标准 Win32 API 访问,对应用透明------应用程序无需感知读写的是本地文件还是远程进程的通信端点。
为什么选择文件系统接口实现 IPC?
这种设计选择并非偶然,而是 Windows NT 架构设计原则的体现------所有可命名对象都应通过对象管理器(Ob)统一管理。将管道和邮槽实现为文件系统驱动带来了几个关键优势:
-
统一的安全模型 :文件对象的安全检查由 I/O 管理器统一处理。当客户端通过
CreateFileW打开管道时,I/O 管理器自动调用SeAccessCheck进行访问控制列表(ACL)检查,npfs 无需自己实现安全验证逻辑。安全描述符直接挂载在 FCB 上,与 NTFS 文件的安全机制完全一致。 -
句柄继承与共享 :管道句柄与普通文件句柄一样,支持进程继承(
bInheritHandle)和句柄复制(DuplicateHandle)。子进程可以继承父进程打开的管道句柄,实现父子进程间的 IPC------这在 shell 管道重定向(cmd.exe /c dir | findstr foo)中起着核心作用。 -
异步 I/O 零成本获得 :
ReadFileEx的重叠 I/O(Overlapped I/O)和 I/O 完成端口(IOCP)机制由 I/O 管理器原生支持。服务端应用程序可以使用CreateIoCompletionPort将多个管道句柄关联到同一个完成端口,实现高效的线程池模型------所有这些基础设施都来自 I/O 管理器,npfs 只需实现基本的读写派发。 -
本地/远程透明性 :命名管道可以通过 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 命名管道是 基于文件系统接口的进程间通信 机制。它有以下几个核心特性:
- 路径名访问 :通过
\\.\Pipe\PipeName访问(DOS 设备视图\\.\pipe\PipeName) - 服务端/客户端模型 :服务端用
CreateNamedPipeW创建,客户端用CreateFileW连接 - 多种模式 :
- 单工/双工(duplex)
- 字节流/消息流(byte/msg)
- 阻塞/非阻塞
- 安全描述符:管道有自己的 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;
}
关键点
- 特殊 MajorFunction :
IRP_MJ_CREATE_NAMED_PIPE(仅 npfs/msfs 使用) - FastIO 支持 :
NpFastRead、NpFastWrite在 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 邮槽概述
邮槽是 单向 进程间通信机制:
- 路径名访问 :
\\.\Mailslot\MailslotName(DOS 视图) - 服务端/客户端模型 :
- 服务端用
CreateMailslotW创建 - 客户端用
CreateFileW("\\\\.\\mailslot\\MailslotName", ...)打开
- 服务端用
- 消息模式:消息边界保留
- 广播能力 :客户端可以写到
*通配符,所有邮槽都收到 - 多对一:多个客户端写,一个服务端读
邮槽与命名管道对比
| 维度 | 命名管道 | 邮槽 |
|---|---|---|
| 方向 | 双向(双工/单工) | 单向 |
| 模式 | 字节流/消息 | 仅消息 |
| 广播 | 不支持 | 支持(* 通配) |
| 跨网络 | 支持(\\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.h 的 NP_FCB 结构可以看到,FCB 包含 CcbList(连接到该管道的客户端 CCB 列表)、WaitQueue(等待连接的队列)、DataQueueListHead(数据队列)等关键字段。
CCB(上下文控制块) :CCB 代表一次打开的管道或邮槽句柄 。每个客户端或服务端打开管道时都会创建一个 CCB。CCB 关联到对应的 FCB,并存储该连接的状态信息(如是否为服务端、读写模式、超时设置等)。从 npfs.h 的 NP_CCB 结构可以看到,CCB 包含 Fcb 指针、FileObject 指针、CcbFlags 标志等。
命名管道的连接与等待机制
命名管道的连接过程是异步的,涉及服务端和客户端的协调。从 ReactOS 的 create.c 可以看到,NpCreateNamedPipe(服务端)和 NpCreate(客户端)的执行流程:
服务端创建管道:
- 服务端调用
CreateNamedPipeW,I/O 管理器将其转换为IRP_MJ_CREATE_NAMED_PIPE。 NpCreateNamedPipe解析管道名称,构造完整路径\Device\NamedPipe\PipeName。- 调用
NpCreateServerFcb查找或创建 FCB。如果 FCB 已存在,增加实例计数;如果不存在,创建新的 FCB。 - 创建服务端 CCB,设置
CcbFlags = CCB_FLAG_SERVER,关联到 FCB。 - 将 CCB 添加到 FCB 的
ServerCcbList。 - 将 FileObject 的
FsContext指向 CCB,FsContext2指向 FCB。
客户端连接管道:
- 客户端调用
CreateFileW("\\\\.\\pipe\\PipeName", ...),I/O 管理器将其转换为IRP_MJ_CREATE。 NpCreate解析管道名称,查找对应的 FCB。- 如果 FCB 不存在,返回
STATUS_OBJECT_NAME_NOT_FOUND。 - 如果 FCB 存在,检查当前实例数是否超过最大实例数。如果超过,返回
STATUS_PIPE_BUSY。 - 创建客户端 CCB,设置
CcbFlags = CCB_FLAG_CLIENT,关联到 FCB。 - 将 CCB 添加到 FCB 的
ClientCcbList。 - 触发服务端等待 :如果服务端正在调用
ConnectNamedPipe等待连接,NpCreate会唤醒等待队列中的服务端 CCB。
ConnectNamedPipe 的等待机制 :
服务端调用 ConnectNamedPipe 时,如果还没有客户端连接,npfs 会将服务端的 IRP 排队到 FCB 的 WaitQueue,并返回 STATUS_PENDING。当客户端调用 CreateFileW 连接时,NpCreate 会从等待队列中取出服务端的 IRP,设置完成状态为 STATUS_SUCCESS,并完成该 IRP。服务端的 ConnectNamedPipe 调用因此返回。
数据队列与读写同步
命名管道的数据传输通过数据队列 实现。从 npfs.h 的 NP_DATA_QUEUE 结构可以看到,每个 CCB 有两个数据队列:一个用于客户端到服务端的数据,一个用于服务端到客户端的数据(如果是双工模式)。
写操作(NpWrite):
- 客户端调用
WriteFile,I/O 管理器发送IRP_MJ_WRITE。 NpWrite从 FileObject 的FsContext获取 CCB,从 CCB 获取 FCB。- 检查管道是否已连接(客户端 CCB 是否关联到服务端 CCB)。如果未连接,返回
STATUS_PIPE_DISCONNECTED。 - 构造
NP_DATA_QUEUE_ENTRY,包含数据缓冲区指针和长度。 - 将数据条目插入到对端的数据队列(如果是客户端写,则插入到服务端 CCB 的数据队列)。
- 唤醒读端 :如果服务端正在调用
ReadFile等待数据,NpWrite会唤醒等待的读 IRP。 - 完成写 IRP,返回写入的字节数。
读操作(NpRead):
- 服务端调用
ReadFile,I/O 管理器发送IRP_MJ_READ。 NpRead从 FileObject 的FsContext获取 CCB,从 CCB 获取 FCB。- 检查数据队列是否有数据。如果有,从队列中取出数据条目,复制到用户缓冲区,完成 IRP。
- 如果数据队列为空,检查管道的完成模式:
- PIPE_WAIT (阻塞模式):将读 IRP 排队到 FCB 的
WaitQueue,返回STATUS_PENDING。当客户端写入数据时,NpWrite会唤醒该 IRP。 - PIPE_NOWAIT (非阻塞模式):立即返回
STATUS_PIPE_EMPTY或 0 字节。
- PIPE_WAIT (阻塞模式):将读 IRP 排队到 FCB 的
- 对于消息模式管道,还需要处理消息边界。如果用户缓冲区小于消息大小,返回
STATUS_BUFFER_OVERFLOW并保留剩余数据在队列中。
Fast I/O 路径优化
命名管道驱动实现了 Fast I/O 路径,用于优化同步的读写操作。Fast I/O 允许在不需要构造 IRP 的情况下直接进行数据传输,从而减少内核开销。
从 ReactOS 的 main.c 可以看到,npfs 在 DriverEntry 中注册了 NpFastIoDispatch,包含 NpFastRead 和 NpFastWrite 函数。当应用层调用同步的 ReadFile 或 WriteFile 时,I/O 管理器会先尝试调用 Fast I/O 函数。如果 Fast I/O 成功,直接返回;如果失败(例如需要阻塞等待),则回退到正常的 IRP 路径。
NpFastRead 的实现:
- 检查 FileObject 的
FsContext获取 CCB。 - 检查数据队列是否有数据。如果有且不需要阻塞,直接复制数据到用户缓冲区,返回
TRUE。 - 如果需要阻塞或发生错误,返回
FALSE,I/O 管理器会回退到NpRead。
NpFastWrite 的实现:
- 检查管道是否已连接。
- 将数据插入到对端的数据队列。
- 唤醒等待的读端(如果有)。
- 返回
TRUE表示成功。
邮槽的广播机制
邮槽的广播功能是命名管道不具备的独特特性。当客户端向 \\*\mailslot\MailslotName 写入数据时,msfs 会将消息广播到所有同名的邮槽。
从 ReactOS 的 rw.c 可以看到,MsWrite 函数在处理广播写时执行以下步骤:
- 检查管道名称是否以
\\*\mailslot\开头。 - 如果是广播写,遍历全局 FCB 列表(
MSFS_DEVICE_EXTENSION.FcbListHead)。 - 对于每个 FCB,检查名称是否匹配
MailslotName。 - 如果匹配,构造
MSFS_MESSAGE结构,包含消息数据和发送者信息。 - 将消息插入到 FCB 的
MessageListHead队列。 - 唤醒等待的服务端(如果有)。
消息结构 :
从 msfs.h 的 MSFS_MESSAGE 结构可以看到,消息包含 Size(消息大小)、Buffer(消息数据)。邮槽的消息是不可分割的,即读操作必须一次性读取整个消息,不能部分读取。
安全描述符与访问控制
命名管道和邮槽都支持安全描述符 ,用于控制客户端的访问权限。从 npfs.h 的 NP_FCB 和 msfs.h 的 MSFS_FCB 可以看到,FCB 结构包含 SecurityDescriptor 字段。
安全描述符的创建 :
服务端在创建管道或邮槽时,可以通过 SECURITY_ATTRIBUTES 参数指定安全描述符。如果没有指定,系统会使用默认的安全描述符。安全描述符包含:
- 所有者 SID:管道或邮槽的所有者。
- 组 SID:所有者所属的组。
- DACL(自主访问控制列表):定义哪些用户或组可以访问,以及访问权限(读、写、执行)。
- SACL(系统访问控制列表):定义审计策略(可选)。
访问检查 :
当客户端调用 CreateFileW 打开管道或邮槽时,I/O 管理器会执行访问检查。它比较客户端的访问令牌与 FCB 的安全描述符,验证客户端是否有请求的访问权限。如果访问被拒绝,返回 STATUS_ACCESS_DENIED。
与 Windows 实现的对比分析
ReactOS 的 npfs 和 msfs 实现与 Windows 存在以下差异:
-
代码来源:ReactOS 的 npfs 和 msfs 是独立实现的,虽然功能上力求与 Windows 兼容,但在某些边缘情况(如复杂的管道模式组合、特殊的超时行为)上可能不够完善。
-
Fast I/O 的完整性 :Windows 的 npfs 实现了完整的 Fast I/O 路径,包括
FastIoCheckIfPossible、FastIoRead、FastIoWrite等。ReactOS 的实现相对简化,主要关注FastIoRead和FastIoWrite。 -
别名机制 :从
npfs.h可以看到,ReactOS 的 npfs 实现了别名机制 (NpAliasList、NpAliasListByLength),允许为同一个管道创建多个名称。这是 ReactOS 特有的扩展,Windows 的 npfs 不支持。 -
BugCheck 支持 :ReactOS 的 npfs 定义了详细的 BugCheck 代码(
NPFS_BUGCHECK_*),用于在发生严重错误时生成蓝屏转储。每个源文件都有唯一的NPFS_BUGCHECK_FILE_ID,便于定位问题。 -
邮槽的超时处理 :ReactOS 的 msfs 实现了邮槽的读超时机制。从
MSFS_FCB的TimeOut字段可以看到,服务端可以设置读操作的超时时间。如果超时,读操作返回STATUS_IO_TIMEOUT。
调试技巧与常见问题
-
管道连接失败 :如果客户端无法连接到管道,检查管道名称是否正确、服务端是否已调用
CreateNamedPipeW、当前实例数是否超过最大实例数。使用 WinDbg 的!devext查看 FCB 的CurrentInstances和MaximumInstances。 -
读写挂起 :如果读写操作挂起,检查数据队列是否为空、管道是否已连接、完成模式是否为
PIPE_WAIT。使用!irp查看 IRP 是否排队到WaitQueue。 -
消息模式问题 :如果使用消息模式管道,确保读写缓冲区大小一致。如果用户缓冲区小于消息大小,读操作会返回
STATUS_BUFFER_OVERFLOW,剩余数据保留在队列中。 -
邮槽广播失败 :如果广播写失败,检查是否有同名的邮槽存在。使用 WinDbg 的
!devext查看MSFS_DEVICE_EXTENSION.FcbListHead,确认 FCB 列表是否正确。 -
安全描述符错误 :如果客户端访问被拒绝,检查 FCB 的安全描述符是否正确设置。使用
!sd <security_descriptor_address>查看安全描述符的内容。 -
ReactOS 特有的调试输出 :ReactOS 的 npfs 和 msfs 启用了
DPRINT和DPRINT1宏进行调试输出。设置HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter中NPFS和MSFS组件的级别为 0xFF,可以在调试串口看到详细的连接、读写、等待日志。
9.11.8 总结
关键要点:
- npfs 与 msfs 都是文件系统驱动:通过 FS 接口实现 IPC
- 三件套:FCB(管道路由)、CCB(连接状态)、DataQueue(数据)
- IRP_MJ_CREATE_NAMED_PIPE 是 npfs 特有
- IRP_MJ_CREATE_MAILSLOT 是 msfs 特有
- FastIO 支持 :npfs 提供
NpFastRead/NpFastWrite - 邮槽广播 :
*通配写 - 内存数据队列:不像磁盘 FS 那样使用磁盘存储
下一节 9.12 将剖析 MDL(Memory Descriptor List)。
9.11.9 设计哲学问答
Q1:为什么命名管道和邮槽要伪装成文件系统驱动,而不是直接提供内核 API?
A :统一编程模型 + 复用 I/O 管理器的完整功能。
如果 npfs/msfs 直接提供内核 API(如 NpCreatePipe、NpReadPipe),应用程序需要学习两套 I/O 模型------一套用于文件,一套用于管道。伪装成文件系统驱动后:
- 零学习成本 :应用程序使用通用的
CreateFileW、ReadFile、WriteFile、CloseHandle操作管道,与操作普通文件完全一致; - 安全继承 :管道的安全检查自动复用 I/O 管理器的
SeAccessCheck和文件打开时的权限验证逻辑; - 异步 I/O 免费获得 :
ReadFileEx和WriteFileEx的重叠 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 被简化为全局变量:
- npfs :
NpfsDeviceObject(全局设备对象)和NpfsMutex(全局锁)替代了 VCB 的角色; - msfs :
MsfsDeviceObject(全局设备对象)替代了 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_CREATE 的 Parameters.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_PIPE 的 Parameters.CreatePipe 传递,该结构包含 CreatePipe.NamedPipeType、CreatePipe.NamedPipeMode、CreatePipe.MaximumInstances、CreatePipe.OutBufferSize、CreatePipe.InBufferSize、CreatePipe.DefaultTimeOut 等字段。
从结构设计角度看,将管道创建与文件创建语义分离使得:
- 派发清晰 :
NpCreateNamedPipe处理服务端创建,NpCreate处理客户端连接,两个函数职责分离; - 参数验证简单 :
NpCreateNamedPipe直接从 IRP 栈提取IO_CREATE_NAMED_PIPE_PARAMETERS,无需从Parameters.Create中解析额外的参数块; - I/O 管理器路由 :系统服务
NtCreateNamedPipeFile直接生成IRP_MJ_CREATE_NAMED_PIPE,NtCreateFile生成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 路径 :
ReadFile→NtReadFile→IoCallDriver→ 分配 IRP →NpRead→ 处理 →IoCompleteRequest→ 释放 IRP。整个路径涉及 IRP 的分配、初始化、销毁,每次约 1-2 µs 开销; - Fast I/O 路径 :
ReadFile→NtReadFile→IoCallDriver→ 调用FastIoDispatch->FastIoRead→NpFastRead→ 返回。没有 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 连接时,NpCreate 从 WaitQueue 中取出服务端的 IRP 并完成,触发服务端的 ConnectNamedPipe 返回。这种机制:
- 兼容 Overlapped I/O :
ConnectNamedPipe返回STATUS_PENDING后,服务端可以通过GetOverlappedResult或WaitForSingleObject等待完成; - 支持取消 :服务端可以调用
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;
NpInitializeAliases 在 DriverEntry 中初始化别名列表,NpAliasListByLength 数组按名称长度索引,加速查找。这种设计的原因包括:
- Windows XP/2003 兼容 :某些 Windows 版本允许通过
\Device\NamedPipe\和\\.\pipe\两种路径访问管道,ReactOS 必须在两种路径下都能正确解析; - Win32 子系统映射 :Win32 子系统(
winsrv.dll或csrss.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) | NpCreate、NpCreateNamedPipe |
| read.c(file:///d:/reactos/drivers/filesystems/npfs/read.c) | NpRead、NpFastRead |
| write.c(file:///d:/reactos/drivers/filesystems/npfs/write.c) | NpWrite、NpFastWrite |
| 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) | MsCreate、MsCreateMailslot |
| rw.c(file:///d:/reactos/drivers/filesystems/msfs/rw.c) | MsRead、MsWrite |
| msfs.h(file:///d:/reactos/drivers/filesystems/msfs/msfs.h) | 数据结构 |