Reactos 第 4 章 对象管理 — 4.5 几个常用的内核函数

第 4 章 对象管理 --- 4.5 几个常用的内核函数

本节是第 4 章的"工具箱"------把对象管理器中最常用的 7 个函数逐一拆解。每个函数都是阅读 ReactOS 源码时频繁遇到的关键 API:句柄转换(ObReferenceObjectByHandle / ObReferenceObjectByPointer)、名称查找(ObpLookupEntryDirectory / ObpLookupObjectName)、对象打开(ObOpenObjectByName / ObReferenceObjectByName)、引用释放(ObDereferenceObject)。掌握这些函数是理解整个对象管理器的"钥匙"。

4.5.0 框架图

复制代码
┌──────────────────────────────────────────────────────────────────────────────────┐
│                              7 个常用内核函数总览                                   │
└──────────────────────────────────────────────────────────────────────────────────┘
                                       │
        ┌──────────────────────────────┼──────────────────────────────┐
        │                              │                              │
        ▼                              ▼                              ▼
┌──────────────────┐         ┌──────────────────┐         ┌──────────────────┐
│   句柄转换类      │         │   名称查找类      │         │   引用管理类      │
│                  │         │                  │         │                  │
│ • ObReference    │         │ • ObpLookup      │         │ • ObDereference  │
│   ObjectByHandle │         │   EntryDirectory │         │   Object         │
│   (4.5.1)        │         │   (4.5.3)        │         │   (4.5.7)        │
│                  │         │                  │         │                  │
│ • ObReference    │         │ • ObpLookup      │         │                  │
│   ObjectByPointer│         │   ObjectName     │         │                  │
│   (4.5.2)        │         │   (4.5.4)        │         │                  │
└──────────────────┘         │                  │         └──────────────────┘
                              │ • ObOpen         │
                              │   ObjectByName   │
                              │   (4.5.5)        │
                              │                  │
                              │ • ObReference    │
                              │   ObjectByName   │
                              │   (4.5.6)        │
                              └──────────────────┘

┌──────────────────────────────────────────────────────────────────────────────────┐
│                           函数关系图                                              │
│                                                                                  │
│  ObReferenceObjectByHandle ───▶ 用于:句柄 → 对象指针 + 权限验证                    │
│         │                                                                      │
│         │ 内部调用 ExMapHandleToPointer                                          │
│         │   ├─ ExpLookupHandleTableEntry                                        │
│         │   └─ ExpLockHandleTableEntry                                          │
│         │                                                                      │
│         ▼                                                                      │
│  ObpLookupEntryDirectory ───▶ 用于:目录 → 名称查找(Hash 表)                     │
│         │                                                                      │
│         │ 内部:ObpInsertEntryDirectory / ObpDeleteEntryDirectory                │
│         │                                                                      │
│         ▼                                                                      │
│  ObpLookupObjectName ───▶ 用于:完整路径解析(符号链接 + ParseProcedure)           │
│         │                                                                      │
│         │ 内部调用 ObpLookupEntryDirectory                                       │
│         │                                                                      │
│         ▼                                                                      │
│  ObOpenObjectByName ───▶ 用于:打开已存在对象                                     │
│         │                                                                      │
│         │ 内部调用 ObpLookupObjectName + ObpCreateHandle                         │
│         │                                                                      │
│         ▼                                                                      │
│  ObReferenceObjectByName ───▶ 用于:内核态按名称引用对象(不创建句柄)              │
│         │                                                                      │
│         │ 内部调用 ObpLookupObjectName + ObReferenceObjectByPointer             │
│         │                                                                      │
│         ▼                                                                      │
│  ObDereferenceObject ───▶ 用于:递减引用计数                                       │
│         │                                                                      │
│         │ 当计数为 0 时调用 ObpDeleteObject + Type->DeleteProcedure              │
└──────────────────────────────────────────────────────────────────────────────────┘

4.5.0.1 设计意图

核心问题

对象管理器是 Windows 内核的"基础设施层"------它为所有子系统(PS、IO、MM、SE、CM、EX)提供统一的对象管理服务。在所有这些子系统的代码中,有 7 个函数出现的频率远高于其他函数:

  1. 句柄转换类ObReferenceObjectByHandleObReferenceObjectByPointer ------ 用于从"用户态可见的句柄"或"已知对象指针"获取"带引用的对象指针"
  2. 名称查找类ObpLookupEntryDirectoryObpLookupObjectName ------ 用于在命名空间中查找对象
  3. 对象打开类ObOpenObjectByNameObReferenceObjectByName ------ 用于按名称打开/引用对象
  4. 引用管理类ObDereferenceObject ------ 用于释放引用

设计哲学

这 7 个函数的设计体现了对象管理器的几个核心原则:

  1. Lookup 模式Lookup* 系列函数负责名称解析;Reference* 系列函数负责引用计数;Open* 系列函数负责创建句柄。三者分工明确。
  2. By-Name vs By-Handle vs By-Pointer:内核提供三种获取对象的方式,分别对应"按名称查找"、"从用户态句柄获取"、"从内核态指针获取"。每种方式有不同的安全语义。
  3. P-前缀的内部函数Obp* 函数是"对象管理器内部使用"的函数,通常不导出给驱动程序。Ob* 函数(无 P)是"导出函数",可以被驱动调用。

本节定位

本节将逐个拆解这 7 个函数,每个函数都从「函数签名 → 核心流程 → 关键设计决策 → 常见使用场景」四个角度展开。读完本节后,读者应当能够:

  • 在 ReactOS 源码中快速识别这 7 个函数
  • 理解每个函数的安全语义
  • 编写正确的对象引用/释放代码
  • 诊断常见的对象管理错误(如引用计数泄漏)

4.5.1 ObReferenceObjectByHandle()

4.5.1.1 函数签名与参数解析

c 复制代码
NTSTATUS
NTAPI
ObReferenceObjectByHandle(IN HANDLE Handle,
                          IN ACCESS_MASK DesiredAccess,
                          IN POBJECT_TYPE ObjectType OPTIONAL,
                          IN KPROCESSOR_MODE AccessMode,
                          OUT PVOID *Object,
                          OUT POBJECT_HANDLE_INFORMATION HandleInformation OPTIONAL);

参数详解

参数 含义
Handle 用户态/内核态传入的句柄值
DesiredAccess 调用方请求的访问权限(用于权限验证)
ObjectType 期望的对象类型(可选,若指定则进行类型检查)
AccessMode 调用方的处理器模式(UserMode / KernelMode)
Object 输出:对象体指针
HandleInformation 输出:句柄信息(属性 + 实际权限,可选)

返回值

  • STATUS_SUCCESS:成功
  • STATUS_INVALID_HANDLE:句柄值非法
  • STATUS_ACCESS_DENIED:权限不足
  • STATUS_OBJECT_TYPE_MISMATCH:类型不匹配

源码位置:ntoskrnl/ob/obref.c(file:///d:/reactos/ntoskrnl/ob/obref.c)(约 450 行)

4.5.1.2 句柄定位与锁定

ObReferenceObjectByHandle 的第一步是定位并锁定句柄表项:

c 复制代码
PHANDLE_TABLE_ENTRY
NTAPI
ExpLookupHandleTableEntry(IN PHANDLE_TABLE HandleTable,
                          IN HANDLE Handle)
{
    ULONG_PTR TableCode = HandleTable->TableCode;
    ULONG_PTR LowIndex = (ULONG_PTR)Handle & 0xFFF;  // 提取低 12 位
    ULONG_PTR MidIndex = ((ULONG_PTR)Handle >> 12) & 0x1FF;  // 中 9 位
    ULONG_PTR HighIndex = ((ULONG_PTR)Handle >> 21) & 0x1FF;  // 高 9 位
    // ... 详细查找逻辑
}

句柄值分解(详见 4.3.0 节框架图):

  • 低 2 位(TagBits):标志位,0 表示普通句柄
  • 低 12 位:在叶子页表中的索引(512 个 Entry)
  • 中 9 位:在中间页表中的索引
  • 高 9 位:在根页表中的索引
  • 最高位(仅 64 位):内核句柄标志

锁定 通过 ExpLockHandleTableEntry(无锁原子操作)实现:

c 复制代码
VOID ExpLockHandleTableEntry(PHANDLE_TABLE_ENTRY Entry)
{
    PVOID OldValue, NewValue;
    do {
        OldValue = Entry->Object;
        NewValue = OldValue | EXHANDLE_TABLE_ENTRY_LOCK_BIT;
    } while (InterlockedCompareExchangePointer(&Entry->Object, NewValue, OldValue) != OldValue);
}

4.5.1.3 权限验证流程

权限验证包含三层检查:

c 复制代码
/* 1. 句柄值合法性 */
if (HandleToLong(Handle) < 0) {
    /* 处理伪句柄(-1, -2)*/
    if (Handle == NtCurrentProcess()) { /* ... */ }
    else if (Handle == NtCurrentThread()) { /* ... */ }
    else if (KernelHandle) { /* 内核句柄 */ }
    else return STATUS_INVALID_HANDLE;
}

/* 2. Entry 状态验证 */
if (Entry->Object == NULL) {
    ExUnlockHandleTableEntry(...);
    return STATUS_INVALID_HANDLE;
}

/* 3. 权限验证 */
if (AccessMode != KernelMode) {
    if ((Entry->GrantedAccess & DesiredAccess) != DesiredAccess) {
        ExUnlockHandleTableEntry(...);
        return STATUS_ACCESS_DENIED;
    }
}

/* 4. 类型验证(如果指定了 ObjectType)*/
if (ObjectType && ObjectHeader->Type != ObjectType) {
    ExUnlockHandleTableEntry(...);
    return STATUS_OBJECT_TYPE_MISMATCH;
}

关键设计决策

  • 内核态权限绕过AccessMode == KernelMode 时跳过权限检查
  • 类型检查可选:调用方可以指定期望类型,让函数自动验证
  • 失败时不递增引用计数:所有检查通过后才递增

4.5.1.4 引用计数递增

权限验证通过后,递增对象的引用计数:

c 复制代码
InterlockedIncrementSizeT(&ObjectHeader->PointerCount);
*Object = &ObjectHeader->Body;
if (HandleInformation) {
    HandleInformation->GrantedAccess = Entry->GrantedAccess;
    HandleInformation->HandleAttributes = Entry->ObAttributes & OBJ_HANDLE_ATTRIBUTES;
}
ExUnlockHandleTableEntry(...);
return STATUS_SUCCESS;

为什么使用 InterlockedIncrement

  • 多核并发:可能多个 CPU 同时引用同一对象
  • 避免竞态InterlockedIncrement 是原子操作
  • 内存屏障:保证指针和引用计数的可见性

4.5.1.5 错误路径分析

错误码 触发条件 实际影响
STATUS_INVALID_HANDLE 句柄值未在 NextHandleNeedingPool 范围内 句柄表无此 Entry
STATUS_ACCESS_DENIED GrantedAccess 缺少 DesiredAccess 所需位 权限不足
STATUS_OBJECT_TYPE_MISMATCH ObjectHeader->Type != ObjectType 类型错误
STATUS_KERNEL_HANDLE_NOT_EXPOSED 用户态试图访问未导出的内核句柄 内核句柄隔离
STATUS_HANDLE_REVOKED 句柄已被撤销(极少使用) 句柄失效

关键点:所有错误路径都正确地:

  • 释放锁定的 Entry
  • 离开临界区
  • 不递增引用计数

4.5.1.6 关键设计决策

  1. 伪句柄的处理NtCurrentProcess()(-1)和 NtCurrentThread()(-2)是特殊值,直接返回当前对象指针
  2. 内核句柄标志 :64 位系统上,最高位置 1 表示内核句柄,仅允许 KernelMode 访问
  3. 类型检查的可选性:让调用方决定是否验证类型------某些场景下需要接受多种类型
  4. 权限检查的严格性:用户态严格检查,内核态宽松检查
  5. 引用计数的原子性 :使用 InterlockedIncrement 保证并发安全

4.5.1.7 常见使用场景

c 复制代码
/* 场景 1:从用户态句柄获取进程对象 */
NTSTATUS Status = ObReferenceObjectByHandle(
    ProcessHandle,           // HANDLE
    PROCESS_VM_READ,         // 请求的权限
    PsProcessType,           // 期望类型
    UserMode,                // 调用方模式
    &Process,                // 输出对象指针
    NULL                     // 不需要句柄信息
);
if (NT_SUCCESS(Status)) {
    /* 使用 Process 访问进程内存 */
    KeStackAttachProcess(&Process->Pcb, &ApcState);
    /* ... */
    KeUnstackDetachProcess(&ApcState);
    ObDereferenceObject(Process);  // 释放引用
}
c 复制代码
/* 场景 2:打开事件对象 */
NTSTATUS Status = ObReferenceObjectByHandle(
    EventHandle,
    EVENT_MODIFY_STATE,      // 请求修改权限
    ExEventObjectType,
    UserMode,
    &Event,
    NULL
);
if (NT_SUCCESS(Status)) {
    KeSetEvent((PKEVENT)Event, 0, FALSE);
    ObDereferenceObject(Event);
}

4.5.1.8 注意事项

  1. 必须配对 ObDereferenceObject:每次成功引用都对应一次释放
  2. 不要在 DPC/ISR 中调用:会触发页面错误
  3. 不要在内核 APC 中调用:可能死锁
  4. 不要递归调用:可能导致引用计数泄漏

4.5.2 ObReferenceObjectByPointer()

4.5.2.1 函数签名与参数解析

c 复制代码
NTSTATUS
NTAPI
ObReferenceObjectByPointer(IN PVOID Object,
                          IN ACCESS_MASK DesiredAccess,
                          IN POBJECT_TYPE ObjectType,
                          IN KPROCESSOR_MODE AccessMode);

ObReferenceObjectByHandle 的区别

维度 ObReferenceObjectByHandle ObReferenceObjectByPointer
输入 HANDLE(用户态可见) 指针(内核态已知)
查找 需查句柄表 不需查找
权限检查 检查句柄的 GrantedAccess 不检查(内核态已知)
类型检查 强制 强制
使用场景 跨边界(用户态→内核态) 内核内部

源码位置:ntoskrnl/ob/obref.c:100(file:///d:/reactos/ntoskrnl/ob/obref.c#L100)

4.5.2.2 实现细节

c 复制代码
NTSTATUS
NTAPI
ObReferenceObjectByPointer(IN PVOID Object,
                          IN ACCESS_MASK DesiredAccess,
                          IN POBJECT_TYPE ObjectType,
                          IN KPROCESSOR_MODE AccessMode)
{
    POBJECT_HEADER ObjectHeader = OBJECT_TO_OBJECT_HEADER(Object);

    /* 类型检查(强类型) */
    if (ObjectHeader->Type != ObjectType) {
        return STATUS_OBJECT_TYPE_MISMATCH;
    }

    /* 递增引用计数 */
    InterlockedIncrementSizeT(&ObjectHeader->PointerCount);

    return STATUS_SUCCESS;
}

注意 :与 ObReferenceObjectByHandle 不同,ObReferenceObjectByPointer

  • 不返回对象指针(因为输入就是对象指针)
  • 不进行权限检查(因为内核态已经知道对象)
  • 仍然进行类型检查(防止类型错误)

4.5.2.3 使用场景

c 复制代码
/* 场景 1:在驱动中引用已知对象 */
PEPROCESS Process = IoGetCurrentProcess();  // 已知是当前进程
ObReferenceObjectByPointer(Process, 0, PsProcessType, KernelMode);
/* 使用 Process */
ObDereferenceObject(Process);
c 复制代码
/* 场景 2:在对象创建路径中引用父对象 */
NTSTATUS PspCreateProcess(PEPROCESS ParentProcess, /* ... */)
{
    ObReferenceObjectByPointer(ParentProcess, 0, PsProcessType, KernelMode);
    /* ... */
}

4.5.2.4 为什么不需要权限检查?

ObReferenceObjectByPointer 不进行权限检查的原因是:

  1. 调用方是内核代码:已经被信任
  2. 指针是已知的:不存在"伪造"的可能
  3. 简化代码:避免每次都进行无意义的权限检查

但这并不意味着任何对象都可以引用:

  • 类型检查仍然强制:防止意外的类型错误
  • KPROCESSOR_MODE 参数:在某些特殊情况下可能影响行为

4.5.2.5 注意事项

  1. 必须保证对象有效 :调用方必须确保 Object 指向真实的对象(未释放)
  2. 类型必须精确匹配PsProcessType 不能用于 PsThreadType
  3. 必须配对 ObDereferenceObject :与 ObReferenceObjectByHandle 相同
  4. 不要在持有自旋锁时调用:会触发死锁(引用计数是 PagedPool 中的指针)

4.5.3 ObpLookupEntryDirectory()

4.5.3.1 函数签名与参数解析

ObpLookupEntryDirectory 是 4.5.4 节 ObpLookupObjectName 的核心内部函数,负责在一个具体的 OBJECT_DIRECTORY 中按名称查找或创建条目。

c 复制代码
NTSTATUS
NTAPI
ObpLookupEntryDirectory(IN POBJECT_DIRECTORY Directory,
                        IN PUNICODE_STRING Name,
                        IN ULONG Attributes,
                        IN BOOLEAN Insert,
                        IN POBP_LOOKUP_CONTEXT Context,
                        OUT POBJECT_HEADER *FoundObject);

参数详解

参数 含义
Directory 目标对象目录
Name 要查找的名称(UNICODE_STRING)
Attributes 标志位(决定大小写敏感性)
Insert 是否在未找到时插入新条目
Context 查找上下文(用于跨目录跟踪)
FoundObject 输出:找到的对象头

源码位置:ntoskrnl/ob/obdir.c:235(file:///d:/reactos/ntoskrnl/ob/obdir.c#L235)

4.5.3.2 Hash 表查找机制

OBJECT_DIRECTORY 内部使用 37 桶 Hash 表(4.1.5 节(file:///d:/reactos/doc/第4章_对象管理_4.1.md#415)):

c 复制代码
typedef struct _OBJECT_DIRECTORY {
    struct _OBJECT_DIRECTORY_ENTRY *HashBuckets[NUMBER_HASH_BUCKETS];  // 37 桶
    // ... 其他字段
} OBJECT_DIRECTORY, *POBJECT_DIRECTORY;

Hash 函数(简化版):

c 复制代码
ULONG ObpHashName(PUNICODE_STRING Name)
{
    WCHAR Char;
    ULONG Hash = 0;
    PWCHAR Buffer = Name->Buffer;
    ULONG Length = Name->Length / sizeof(WCHAR);

    while (Length--) {
        Char = RtlUpcaseUnicodeChar(Buffer[Length]);
        Hash = (Hash << 1) | (Hash >> 31);  // 循环左移
        Hash += Char;
    }
    return Hash % NUMBER_HASH_BUCKETS;
}

关键设计决策

  • 37 桶是质数:减少 Hash 碰撞
  • 循环左移 + 累加:分布更均匀
  • 大写归一化:大小写不敏感

4.5.3.3 大小写不敏感比较

Attributes & OBJ_CASE_INSENSITIVE 决定比较方式:

c 复制代码
BOOLEAN ObpCompareNames(PUNICODE_STRING Name1, PUNICODE_STRING Name2, BOOLEAN CaseInsensitive)
{
    if (Name1->Length != Name2->Length) return FALSE;

    if (CaseInsensitive) {
        return RtlCompareUnicodeString(Name1, Name2, TRUE) == 0;
    } else {
        return RtlCompareUnicodeString(Name1, Name2, FALSE) == 0;
    }
}

大小写不敏感的典型场景

  • 文件对象(C:\Windowsc:\windows 相同)
  • 设备对象
  • 注册表键

大小写敏感的典型场景

  • 进程对象(区分大小写)
  • 互斥体
  • 事件

4.5.3.4 查找上下文缓存

OBP_LOOKUP_CONTEXT 缓存查找结果以加速「先查后创建」的操作:

c 复制代码
typedef struct _OBP_LOOKUP_CONTEXT {
    POBJECT_DIRECTORY Directory;       // 当前目录
    POBJECT_DIRECTORY_ENTRY Entry;     // 当前条目
    ULONG HashIndex;                  // 当前位置的 Hash 索引
    ULONG LockState;                  // 锁状态
} OBP_LOOKUP_CONTEXT, *POBP_LOOKUP_CONTEXT;

为什么需要缓存?

考虑以下场景:

  1. 调用 ObOpenObjectByName 查找 \Device\HarddiskVolume1\foo
  2. ObpLookupEntryDirectory\Device 目录中找到 HarddiskVolume1
  3. HarddiskVolume1 是一个符号链接
  4. 解析符号链接后,路径变成 \Device\HarddiskVolume1\foo
  5. 现在需要重新从 \ 开始查找

如果不缓存,每次都会重新查找 \DeviceHarddiskVolume1。缓存可以让第二次查找"接续"第一次的结果。

4.5.3.5 创建新条目流程

Insert = TRUE 且未找到时:

c 复制代码
if (Insert && *FoundObject == NULL) {
    /* 分配 OBJECT_DIRECTORY_ENTRY */
    NewEntry = ExAllocatePoolWithTag(PagedPool, sizeof(OBJECT_DIRECTORY_ENTRY), 'EteN');
    
    /* 复制名称(UNICODE_STRING) */
    NewEntry->Name.Length = Name->Length;
    NewEntry->Name.MaximumLength = Name->Length + sizeof(WCHAR);
    NewEntry->Name.Buffer = ExAllocatePoolWithTag(PagedPool, NewEntry->Name.MaximumLength, 'NmeN');
    RtlCopyMemory(NewEntry->Name.Buffer, Name->Buffer, Name->Length);
    
    /* 设置对象头(稍后由 ObInsertObject 填充) */
    NewEntry->ObjectHeader = NULL;  // 占位
    
    /* 插入到 Hash 桶 */
    HashIndex = ObpHashName(Name);
    NewEntry->ChainLink = Directory->HashBuckets[HashIndex];
    Directory->HashBuckets[HashIndex] = NewEntry;
    
    /* 保存到 Context(供后续填充) */
    Context->Entry = NewEntry;
    Context->HashIndex = HashIndex;
}

关键设计决策

  • 先占位后填充 :Entry 的 ObjectHeader 字段在创建时为 NULL,ObInsertObject 后续填充
  • 名称独立分配:Entry 内部只存指向名称的指针,名称单独分配
  • 链头插入:新 Entry 插入到 Hash 桶的头部(O(1) 操作)

4.5.3.6 关键设计决策

  1. 37 桶 Hash 表:质数减少碰撞
  2. 链头插入:LIFO 行为,最近创建的 Entry 在最前面
  3. 查找上下文缓存:避免重复查找
  4. 插入和查找分离:先查找,必要时再插入

4.5.3.7 注意事项

  1. 目录锁 :调用方必须持有 Directory 的锁
  2. 名称生命周期:插入时分配的名称缓冲必须长期有效(直到对象删除)
  3. Entry 分配失败ExAllocatePoolWithTag 失败时返回 STATUS_INSUFFICIENT_RESOURCES

4.5.4 ObpLookupObjectName()

4.5.4.1 函数签名与参数解析

c 复制代码
NTSTATUS
NTAPI
ObpLookupObjectName(IN HANDLE RootHandle OPTIONAL,
                    IN OUT PUNICODE_STRING ObjectName,
                    IN ULONG Attributes,
                    IN POBJECT_TYPE ObjectType,
                    IN KPROCESSOR_MODE AccessMode,
                    IN OUT PVOID ParseContext,
                    IN PSECURITY_QUALITY_OF_SERVICE SecurityQos OPTIONAL,
                    IN PVOID InsertObject OPTIONAL,
                    IN OUT PACCESS_STATE AccessState,
                    OUT POBP_LOOKUP_CONTEXT LookupContext,
                    OUT PVOID *FoundObject);

参数详解

参数 含义
RootHandle 根目录句柄(NULL 表示从 \ 根目录开始)
ObjectName 完整路径(如 \Device\HarddiskVolume1\foo
Attributes 标志位
ObjectType 期望的对象类型(可选)
AccessMode 处理器模式
ParseContext 解析上下文(由 ParseProcedure 设置)
SecurityQos 安全 QoS(可选)
InsertObject 要插入的新对象(创建时使用)
AccessState 访问状态(用于权限累积)
LookupContext 查找上下文(用于跨目录跟踪)
FoundObject 输出:找到的对象

源码位置:ntoskrnl/ob/obname.c:446(file:///d:/reactos/ntoskrnl/ob/obname.c#L446)

4.5.4.2 路径解析流程

完整的路径解析流程如下:

c 复制代码
/* 初始化 */
ObpInitializeLookupContext(LookupContext);
*FoundObject = NULL;

/* 1. 解析根目录 */
if (RootHandle) {
    /* 从句柄获取根目录 */
    ObReferenceObjectByHandle(RootHandle, 0, ObpDirectoryObjectType, AccessMode, &Directory, NULL);
} else {
    /* 使用全局根目录 */
    Directory = ObpRootDirectoryObject;
}

/* 2. 提取第一段路径 */
ComponentName = /* ObjectName 的第一段 */;
RemainingName = /* ObjectName 的剩余部分 */;

/* 3. 循环解析 */
while (TRUE) {
    /* 查找当前目录中的 ComponentName */
    Status = ObpLookupEntryDirectory(Directory, &ComponentName, Attributes, Insert, Context, &Object);
    
    if (Object) {
        if (RemainingName.Length == 0) {
            /* 找到目标 */
            *FoundObject = Object;
            return STATUS_SUCCESS;
        }
        
        /* 还有剩余路径,根据对象类型处理 */
        if (Object->Type == ObpDirectoryObjectType) {
            /* 目录对象:继续向下 */
            Directory = (POBJECT_DIRECTORY)Object;
        } else if (Object->Type->TypeInfo.ParseProcedure) {
            /* 有 ParseProcedure:调用它解析剩余路径 */
            Status = Object->Type->TypeInfo.ParseProcedure(
                Object,
                Object->Type,
                AccessState,
                AccessMode,
                Attributes,
                &ObjectName,
                &RemainingName,
                ParseContext,
                SecurityQos,
                FoundObject);
            
            if (Status == STATUS_REPARSE) {
                /* 符号链接:使用新的路径重试 */
                ObjectName = ReparseTarget;
                goto loop_start;
            }
            return Status;
        } else {
            /* 无 ParseProcedure:路径冲突 */
            return STATUS_OBJECT_PATH_INVALID;
        }
    } else {
        /* 未找到:如果是 Insert 模式且 InsertObject 有效,则插入 */
        if (Insert && InsertObject) {
            Status = ObpInsertEntryDirectory(Directory, InsertObject, &ComponentName, Context);
            *FoundObject = InsertObject;
            return STATUS_SUCCESS;
        }
        return STATUS_OBJECT_NAME_NOT_FOUND;
    }
    
    /* 提取下一段 */
    ComponentName = RemainingName 的下一段;
    RemainingName = RemainingName 的剩余部分;
}

4.5.4.3 符号链接重解析

符号链接对象会触发 STATUS_REPARSE

c 复制代码
/* 符号链接对象的 ParseProcedure 是 ObpParseSymbolicLink */
NTSTATUS
NTAPI
ObpParseSymbolicLink(IN PVOID SymbolicLinkObject,
                      /* ... */,
                      PUNICODE_STRING RemainingName,
                      /* ... */,
                      PVOID *FoundObject)
{
    PSYMBOLIC_LINK_OBJECT SymLink = (PSYMBOLIC_LINK_OBJECT)SymbolicLinkObject;
    UNICODE_STRING NewName;
    
    /* 构建新路径:SymLink->LinkTarget + RemainingName */
    NewName.Length = SymLink->LinkTarget.Length + RemainingName->Length;
    NewName.Buffer = ExAllocatePoolWithTag(PagedPool, NewName.Length, 'eSmL');
    RtlCopyUnicodeString(&NewName, &SymLink->LinkTarget);
    RtlAppendUnicodeStringToString(&NewName, RemainingName);
    
    /* 设置重解析标志 */
    *FoundObject = NULL;
    return STATUS_REPARSE;  // 表示路径需要重新解析
}

重解析机制

  • MaxReparse = 30:限制重解析次数,防止循环
  • 每次重解析从 \ 根目录重新开始
  • 维护一个 ReparseBuffer 缓存

4.5.4.4 ParseProcedure 调用

每种类型可以定义自己的 ParseProcedure

类型 ParseProcedure 作用
Directory 默认 继续向下解析
SymbolicLink ObpParseSymbolicLink 重定向到链接目标
Device IopParseDevice 解析为设备对象
File IopParseFile 打开文件对象
Section MiSectionParseProcedure 映射内存段

典型 ParseProcedure 流程

c 复制代码
NTSTATUS IopParseDevice(IN PVOID DeviceObject,
                       /* ... */,
                       PUNICODE_STRING RemainingName,
                       /* ... */,
                       PVOID *FoundObject)
{
    PDEVICE_OBJECT Device = (PDEVICE_OBJECT)DeviceObject;
    
    /* 1. 查找附加到设备的文件系统 */
    if (Device->DeviceType == FILE_DEVICE_DISK) {
        Vpb = Device->Vpb;
        if (Vpb && Vpb->DeviceObject) {
            /* 通过 VPB 找到文件系统 */
            *FoundObject = Vpb->DeviceObject;
            return STATUS_REPARSE;  // 让上层的 IopParseFile 继续
        }
    }
    
    /* 2. 直接打开设备 */
    Status = IopOpenDevice(Device, AccessMode, DesiredAccess, ...);
    
    /* 3. 返回文件对象 */
    *FoundObject = NewFileObject;
    return STATUS_SUCCESS;
}

4.5.4.5 循环解析与重解析限制

c 复制代码
ULONG MaxReparse = 30;  // 最大重解析次数

/* 主循环 */
while (MaxReparse--) {
    /* ... 解析路径 ... */
    
    if (Status == STATUS_REPARSE) {
        /* 重置路径并重试 */
        continue;
    }
    
    break;
}

if (MaxReparse == 0) {
    return STATUS_OBJECT_NAME_INVALID;  // 可能是循环引用
}

循环解析的常见原因

  • 符号链接指向自身(A → A
  • 符号链接形成环(A → B → A
  • 配置错误

为什么限制为 30?

  • 性能:如果无限重试,会消耗大量 CPU
  • 安全:防止恶意构造的循环引用
  • 可观察性:返回错误码让管理员发现配置问题

4.5.4.6 权限检查时机

ObpLookupObjectName 在以下时机进行权限检查:

时刻 检查内容 函数
根目录解析 是否有 Traverse 权限 ObpCheckObjectAccess
每段路径 是否有 Traverse 权限 ObpCheckObjectAccess
目标对象 是否有完整访问权限 ObCheckObjectAccess

为什么需要 Traverse 权限?

考虑路径 \A\B\C

  • 要访问 C,必须先经过 AB
  • 如果用户对 AB 没有 Traverse 权限,应该被拒绝
  • 这种检查防止用户通过"跳板"访问未授权的对象

4.5.4.7 关键设计决策

  1. 逐段解析 :与 Unix 的 path_lookup 思路一致
  2. 符号链接重解析:在主循环中处理,而非递归
  3. ParseProcedure 多态:每种类型自定义解析逻辑
  4. 重解析限制:30 次上限防止循环
  5. Traverse 权限:按段检查,防止未授权访问

4.5.4.8 注意事项

  1. 目录锁顺序:必须按"从根到叶"获取目录锁
  2. APC 级别:不能在 APC_LEVEL 调用
  3. 分页:调用期间不能持有自旋锁

4.5.5 ObOpenObjectByName()

4.5.5.1 函数签名与参数解析

c 复制代码
NTSTATUS
NTAPI
ObOpenObjectByName(IN POBJECT_ATTRIBUTES ObjectAttributes,
                   IN POBJECT_TYPE ObjectType,
                   IN KPROCESSOR_MODE AccessMode,
                   IN ACCESS_MASK DesiredAccess,
                   IN PACCESS_STATE PassedAccessState OPTIONAL,
                   OUT PHANDLE Handle);

参数详解

参数 含义
ObjectAttributes 对象属性(含路径)
ObjectType 期望的对象类型
AccessMode 处理器模式
DesiredAccess 请求的访问权限
PassedAccessState 访问状态(可选)
Handle 输出:句柄

源码位置:ntoskrnl/ob/obhandle.c:2532(file:///d:/reactos/ntoskrnl/ob/obhandle.c#L2532)

4.5.5.2 与 ObReferenceObjectByName 的区别

维度 ObOpenObjectByName ObReferenceObjectByName
输出 句柄(HANDLE) 指针(PVOID)
句柄表 需要 不需要
配额 扣除句柄配额 扣除对象配额
使用场景 用户态可见的句柄 仅内核可见的引用

4.5.5.3 名称查找与对象打开

ObOpenObjectByName 的实现:

c 复制代码
NTSTATUS
NTAPI
ObOpenObjectByName(IN POBJECT_ATTRIBUTES ObjectAttributes,
                  /* ... */)
{
    PVOID Object;
    NTSTATUS Status;
    PACCESS_STATE AccessState = NULL;
    
    /* 1. 准备访问状态(如果未提供) */
    if (!PassedAccessState) {
        AccessState = ExAllocatePoolWithTag(NonPagedPool, sizeof(ACCESS_STATE), 'sAcA');
        Status = SeCreateAccessState(AccessState, /* ... */, DesiredAccess, &ObjectType->TypeInfo.GenericMapping);
    } else {
        AccessState = PassedAccessState;
    }
    
    /* 2. 查找对象(详见 4.5.4 节) */
    Status = ObpLookupObjectName(
        ObjectAttributes->RootDirectory,
        ObjectAttributes->ObjectName,
        ObjectAttributes->Attributes,
        ObjectType,
        AccessMode,
        NULL,  // ParseContext
        NULL,  // SecurityQos
        NULL,  // InsertObject (NULL = 只打开)
        AccessState,
        &LookupContext,
        &Object);
    
    if (!NT_SUCCESS(Status)) {
        goto cleanup;
    }
    
    /* 3. 创建句柄 */
    Status = ObpCreateHandle(
        PsGetCurrentProcess(),
        Object,
        AccessState,
        ObjectType,
        AccessMode,
        0,  // ObjectPointerBias
        Handle);
    
cleanup:
    if (Object) ObDereferenceObject(Object);  // 释放 Lookup 增加的引用
    if (AccessStateCreatedHere) SeDeleteAccessState(AccessState);
    return Status;
}

4.5.5.4 安全描述符处理

ObOpenObjectByName 在打开时进行完整的权限检查:

  1. 对象类型检查:必须是请求的类型
  2. 安全描述符检查:调用方是否有 DesiredAccess 权限
  3. 审计日志:对敏感对象记录打开事件

4.5.5.5 句柄分配

ObOpenObjectByName 调用 ObpCreateHandle(详见 4.3.4 节):

c 复制代码
Status = ObpCreateHandle(
    Process,
    Object,
    AccessState,
    ObjectType,
    AccessMode,
    0,
    Handle);

句柄的特殊属性

  • OBJ_INHERIT:可被子进程继承
  • OBJ_KERNEL_HANDLE:内核句柄(仅 64 位)

4.5.5.6 关键设计决策

  1. 查找 → 引用 → 创建句柄:三阶段流程
  2. 释放查找的引用:Lookup 增加的引用在句柄创建后释放
  3. 统一的错误处理:任何一步失败都正确清理

4.5.5.7 使用场景

c 复制代码
/* 场景 1:打开命名互斥体 */
UNICODE_STRING Name = RTL_CONSTANT_STRING(L"\\BaseNamedObjects\\MyMutex");
OBJECT_ATTRIBUTES ObjectAttributes;
HANDLE MutexHandle;

InitializeObjectAttributes(&ObjectAttributes, &Name, OBJ_CASE_INSENSITIVE, NULL, NULL);

Status = ObOpenObjectByName(
    &ObjectAttributes,
    ExMutantObjectType,
    KernelMode,
    MUTANT_ALL_ACCESS,
    NULL,
    &MutexHandle);

4.5.6 ObReferenceObjectByName()

4.5.6.1 函数签名与参数解析

c 复制代码
NTSTATUS
NTAPI
ObReferenceObjectByName(IN PUNICODE_STRING ObjectName,
                        IN ULONG Attributes,
                        IN PACCESS_STATE AccessState OPTIONAL,
                        IN ACCESS_MASK DesiredAccess,
                        IN POBJECT_TYPE ObjectType,
                        IN KPROCESSOR_MODE AccessMode,
                        IN PVOID ParseContext OPTIONAL,
                        OUT PVOID *Object);

ObOpenObjectByName 的区别

  • 输出是对象指针 而非句柄
  • 不调用 ObpCreateHandle
  • 不扣除句柄配额

源码位置:ntoskrnl/ob/obhandle.c(file:///d:/reactos/ntoskrnl/ob/obhandle.c)

4.5.6.2 使用场景

c 复制代码
/* 场景 1:内核代码引用系统对象 */
UNICODE_STRING Name = RTL_CONSTANT_STRING(L"\\Device\\HarddiskVolume1");
PDEVICE_OBJECT Device;

Status = ObReferenceObjectByName(
    &Name,
    OBJ_CASE_INSENSITIVE,
    NULL,
    FILE_READ_ATTRIBUTES,
    IoDeviceObjectType,
    KernelMode,
    NULL,
    &Device);

if (NT_SUCCESS(Status)) {
    /* 使用 Device */
    ObDereferenceObject(Device);
}

4.5.6.3 关键设计决策

  1. 仅内核使用ObReferenceObjectByName 是内部 API,通常不导出
  2. 不创建句柄:避免句柄表的负担
  3. 安全语义与 ObOpenObjectByName 相同:完整的安全检查

4.5.6.4 注意事项

  1. 必须确保 AccessModeKernelMode:用户态不应调用此函数
  2. 必须配对 ObDereferenceObject
  3. 不能用于打开新对象:仅用于引用已存在的对象

4.5.7 ObDereferenceObject()

4.5.7.1 函数签名与参数解析

c 复制代码
VOID
NTAPI
ObDereferenceObject(IN PVOID Object);

参数

  • Object:对象体指针(注意:是指向 OBJECT_BODY 的指针,不是 OBJECT_HEADER

返回值:无(Void)

源码位置:ntoskrnl/ob/obref.c:250(file:///d:/reactos/ntoskrnl/ob/obref.c#L250)

4.5.7.2 引用计数递减

ObDereferenceObject 的核心逻辑:

c 复制代码
VOID
NTAPI
ObDereferenceObject(IN PVOID Object)
{
    POBJECT_HEADER ObjectHeader = OBJECT_TO_OBJECT_HEADER(Object);
    LONG_PTR NewCount;

    /* 原子递减引用计数 */
    NewCount = InterlockedDecrementSizeT(&ObjectHeader->PointerCount);

    /* 如果引用计数归零,触发对象销毁 */
    if (NewCount == 0) {
        ObpDeleteObject(Object);
    }
}

4.5.7.3 对象销毁触发

PointerCount 归零时,ObpDeleteObject 被调用:

c 复制代码
VOID
NTAPI
ObpDeleteObject(IN PVOID Object)
{
    POBJECT_HEADER ObjectHeader = OBJECT_TO_OBJECT_HEADER(Object);
    POBJECT_TYPE ObjectType = ObjectHeader->Type;
    
    /* 1. 从命名空间中移除 */
    if (ObjectHeader->NameInfoOffset) {
        ObpRemoveObject(ObjectHeader);
    }
    
    /* 2. 调用类型专属的 DeleteProcedure */
    if (ObjectType->TypeInfo.DeleteProcedure) {
        ObjectType->TypeInfo.DeleteProcedure(Object);
    }
    
    /* 3. 释放安全描述符 */
    if (ObjectHeader->SecurityDescriptor) {
        ObpDeassignSecurity(ObjectHeader);
    }
    
    /* 4. 释放 QuotaInfo */
    if (ObjectHeader->QuotaInfoOffset) {
        /* ... */
    }
    
    /* 5. 释放对象池内存 */
    ObpDeallocateObject(ObjectHeader);
}

4.5.7.4 DeleteProcedure 调用

DeleteProcedure 是类型专属的清理函数:

类型 DeleteProcedure 清理内容
Process PspDeleteProcess 释放 EPROCESS、PID、句柄表
Thread PspDeleteThread 释放 ETHREAD、堆栈、APC
File IopDeleteFile 关闭 IRP、释放 FCB
Event NULL (无 Body 资源)
Section MiSectionDelete 取消映射、写回数据

4.5.7.5 关键设计决策

  1. 原子递减InterlockedDecrement 保证并发安全
  2. 延迟删除PointerCount 归零后才真正删除
  3. 类型专属清理DeleteProcedure 抽象出类型差异
  4. 命名空间解链:在删除前从命名空间移除,防止悬挂引用

4.5.7.6 引用计数泄漏的检测

常见的引用计数错误:

错误 症状 检测方法
忘记 Dereference 对象永远不释放 !handle + !object 查看对象数
过度 Dereference 引用计数变负,触发 BSOD BugCheck 0x18D
不匹配的 Dereference 对象被意外释放 !analyze -v

4.5.7.7 注意事项

  1. 必须配对引用:每次 Reference 必须有一次 Dereference
  2. 不能在持有自旋锁时调用:触发分页
  3. 不能在 DPC 级别调用
  4. 不能 Dereference NULL(虽然代码会处理)

4.5.7.8 与 ObDereferenceObjectDefer 的关系

Windows 有 ObDereferenceObjectDefer 函数:

  • 在引用计数归零时不立即删除
  • 而是放入延迟删除队列
  • ObReapObject 异步清理

ReactOS 中没有 ObDereferenceObjectDefer(或实现不同),但 NT 5.2+ 之后引入这个 API 处理"自旋锁→删除"死锁问题。


4.5.8 概念解释

4.5.8.1 HANDLE_VALUE 编码

Windows 句柄的位级结构:

  • 32 位系统:TagBits(2) + LowIndex(10) + MidIndex(10) + HighIndex(10)
  • 64 位系统:TagBits(2) + LowIndex(10) + MidIndex(10) + HighIndex(10) + Reserved(31) + KernelFlag(1)

关键点

  • 句柄值是连续的(大量空洞)
  • 句柄是 PID(不同维度)

4.5.8.2 OBJECT_DIRECTORY

OBJECT_DIRECTORY 是命名空间的核心数据结构:

  • 37 桶 Hash 表
  • 链头插入(LIFO)
  • 锁保护

4.5.8.3 OBP_LOOKUP_CONTEXT

OBP_LOOKUP_CONTEXT 缓存查找结果:

  • 当前目录
  • 当前 Entry
  • Hash 索引
  • 锁状态

作用:加速"先查后创建"的操作,避免重复查找。

4.5.8.4 ParseProcedure

OB_PARSE_METHOD 是类型的 ParseProcedure 函数指针:

  • 多态:每种类型自定义解析逻辑
  • 链式:一个对象的 Parse 可以返回另一个对象
  • 符号链接:重定向路径

4.5.8.5 STATUS_REPARSE

STATUS_REPARSE 是 NTSTATUS 的特殊值:

  • 不表示错误
  • 表示"路径需要重新解析"
  • 由符号链接的 ParseProcedure 返回

4.5.8.6 PointerCount vs HandleCount

维度 PointerCount HandleCount
含义 内核态引用次数 用户态句柄数
操作 ObReferenceObjectByHandleObDereferenceObject ObpCreateHandleObpCloseHandle
归零影响 触发对象销毁 触发 CloseProcedure

4.5.9 为什么要这样设计

4.5.9.1 问题 1:为什么需要 ObpLookupEntryDirectory 作为独立函数?

将"目录内的名称查找"从 ObpLookupObjectName 中分离出来,原因:

  1. 性能优化:一个目录内的查找是简单操作,不需要路径解析的复杂逻辑
  2. 代码复用ObpInsertEntryDirectoryObpDeleteEntryDirectory 都可以复用此函数
  3. 清晰的语义边界:「在目录中查找」和「解析完整路径」是两个不同层次的操作

4.5.9.2 问题 2:为什么 ObpLookupObjectName 不处理符号链接重解析?

ObpLookupObjectName 确实 处理符号链接,但通过 STATUS_REPARSE 机制:

  1. 避免递归调用:递归会导致栈溢出风险
  2. 循环检测:循环重试比递归更容易检测
  3. 可重入性:循环重试可以正确释放已获取的资源

4.5.9.3 问题 3:为什么 ObReferenceObjectByHandle 必须验证权限?

权限验证是安全的关键防线:

  1. 防止越权访问:用户态不能访问它没有权限的对象
  2. 防止权限提升:即使用户态拿到高权限句柄,也只能用它被授予的权限
  3. 审计需求:验证失败时记录审计日志

但内核态可以绕过------这是设计权衡:内核是可信的,不应该被自己的权限检查阻止。

4.5.9.4 问题 4:为什么 ObDereferenceObject 简单到只需要递减计数?

ObDereferenceObject 故意保持简单:

  • 复杂的逻辑(命名空间移除、安全描述符释放)放到 ObpDeleteObject
  • PointerCount 归零时,复杂逻辑只执行一次
  • 引用计数频繁变动时,复杂逻辑不执行

性能优化 :在对象引用频繁的场景下(如文件系统),只递增/递减计数非常快;只有最后一次 Dereference 才触发清理。

4.5.9.5 问题 5:为什么需要 Obp 内部的私有函数?

Obp* 函数是「Private」的,不导出给驱动:

  1. API 稳定性:导出函数的接口更稳定,内部函数可以自由修改
  2. 安全:避免驱动直接访问内部结构
  3. 关注点分离:内部函数由对象管理器自己使用,不暴露给外部

内部函数经常被驱动依赖 ------这导致 ReactOS 实际上导出 了部分 Obp* 函数。


4.5.10 增强子节:引用计数的并发安全

4.5.10.1 设计意图

核心问题 :对象引用计数在多核多线程环境下需要严格的并发安全。如果两个 CPU 同时 ReferenceDereference 同一对象,没有原子操作可能导致:

  1. 计数丢失:两个操作都读到旧值,后写入的丢失
  2. 对象过早释放:计数在不应该归零时归零
  3. BSODPointerCount 变负触发 BugCheck 0x18D

设计哲学 :「单变量原子操作 」------只用 InterlockedIncrement/InterlockedDecrement 这两个原子原语,配合「释放即不再访问」的纪律。

4.5.10.2 概念解释

  • 原子操作(Atomic Operation):CPU 提供的不可中断的操作
  • 内存屏障(Memory Barrier):保证指令执行顺序的 CPU 指令
  • 引用计数(Reference Count):记录对象被引用次数的计数器
  • ABA 问题:并发环境下可能出现的「值未变但语义已变」问题

4.5.10.3 原子操作原语

x86 提供以下原子原语:

原语 作用
LOCK XADD 原子加法(InterlockedIncrement 内部使用)
LOCK CMPXCHG 原子比较并交换(InterlockedCompareExchange 内部使用)
LOCK XCHG 原子交换(InterlockedExchange 内部使用)

Windows 内核包装了这些原语:

  • InterlockedIncrement
  • InterlockedDecrement
  • InterlockedCompareExchangePointer
  • InterlockedExchangePointer

4.5.10.4 引用计数的「纪律」

引用计数的正确使用需要严格的纪律:

c 复制代码
/* 1. Reference */
ObReferenceObject(Object);  // PointerCount 从 1 → 2
/* 在此期间,对象保证有效 */
/* 2. 使用对象 */
Object->Field1 = ...;  // 安全
/* 3. Dereference */
ObDereferenceObject(Object);  // PointerCount 从 2 → 1
/* 此后,不能再访问 Object */

核心纪律

  • 不能在没有引用的情况下访问对象
  • 不能多次释放同一个引用
  • 不能在持有引用时假设对象不变(其它代码可能修改)

4.5.10.5 引用计数与锁的交互

引用计数与锁的交互是一个微妙的问题:

c 复制代码
/* 错误:持有锁时 Dereference */
KeAcquireSpinLock(&Lock, &OldIrql);
Object = ...;
KeReleaseSpinLock(&Lock, OldIrql);
ObDereferenceObject(Object);  // 可能触发分页,等待,BSOD!

/* 正确:先 Dereference 再释放锁 */
KeAcquireSpinLock(&Lock, &OldIrql);
Object = ...;
ObReferenceObject(Object);  // 递增引用计数
KeReleaseSpinLock(&Lock, OldIrql);
ObDereferenceObject(Object);  // 现在安全

规则

  • 不能在持有自旋锁时 Dereference(可能触发分页)
  • 不能在 DPC/ISR 级别 Dereference
  • 不能跨 APC 边界传递对象指针

4.5.10.6 为什么要这样设计

问题 :为什么不让 ObDereferenceObject 自动决定何时延迟删除?

答案 :这正是 ObDereferenceObjectDefer 的作用------在引用计数归零时放入延迟删除队列,由专门线程在合适时机删除。

但在 ReactOS/NT 5.0 时代没有这个 API。内核开发者必须自觉地遵守引用计数纪律。

反例 :如果让 ObDereferenceObject 自动延迟删除:

  • 删除时机不可预测
  • 调试困难
  • 性能不可控

结论:引用计数的纪律是内核编程的基本功,没有捷径。


4.5.11 增强子节:对象生命周期管理

4.5.11.1 设计意图

核心问题:对象的生命周期涉及多个组件(Header、Info 块、Body、命名空间、句柄表、配额)。任何一处遗漏都可能导致资源泄漏或悬挂引用。

设计哲学 :「一进一退 」原则------每次 Reference 必须配对 Dereference,每次 Open 必须配对 Close

4.5.11.2 对象生命周期的五个阶段

复制代码
[创建]
  ObCreateObject → 分配内存
  <初始化>
  ObInsertObject → 插入命名空间 + 创建句柄
  PointerCount = 1 (Body 引用) + 1 (句柄引用)
       ↓
[使用]
  ObReferenceObjectByHandle → PointerCount += 1
  使用对象
  ObDereferenceObject → PointerCount -= 1
       ↓
[关闭]
  NtClose → 句柄引用 -= 1
  当 HandleCount = 0 → 调用 CloseProcedure
  当 PointerCount = 0 → 调用 DeleteProcedure → 释放内存
       ↓
[已删除]
  内存返回池

4.5.11.3 概念解释

  • 生命周期(Lifecycle):对象从创建到销毁的完整过程
  • 悬挂引用(Dangling Reference):对象已被释放但仍有引用
  • 泄漏(Leak):对象被引用但不再使用,无法释放
  • TypeList 监控:通过类型对象跟踪所有该类型的对象

4.5.11.4 对象生命周期的安全保证

内核提供以下机制保证对象生命周期的安全:

  1. 引用计数:保证对象在使用期间不被释放
  2. 类型化对象 :通过 Type 字段进行类型检查
  3. 命名空间原子性:插入/移除命名空间是原子的
  4. 延迟删除:避免在持锁时删除对象

4.5.11.5 引用计数与命名空间的协同

c 复制代码
/* 场景:进程 A 打开进程 B 的句柄 */
1. 进程 B 被创建 → PointerCount = 1
2. 进程 A 调用 OpenProcess → 查找 \ObjectTypes\Process 中 B 的 Entry
3. ObReferenceObjectByHandle(进程B, ...) → PointerCount = 2
4. 创建句柄 → HandleCount = 1
5. 进程 A 使用句柄

/* 进程 A 关闭句柄 */
6. NtClose → 句柄 Entry 移除
7. HandleCount 递减 → CloseProcedure 可能被调用
8. PointerCount 递减(之前增加的引用) → PointerCount = 1
9. 句柄已关闭

/* 进程 B 退出 */
10. 进程 B 的 PspExitProcess → PointerCount 递减 → PointerCount = 0
11. DeleteProcedure 调用 → 释放进程 B 的资源

关键点

  • 句柄的关闭不立即删除对象(如果还有内核态引用)
  • 引用计数归零才真正删除
  • 关闭句柄触发 CloseProcedure(如 IopCloseFile

4.5.11.6 为什么要这样设计

问题 :为什么不让 NtClose 立即删除对象?

答案

  • 内核态代码可能仍持有对象指针
  • 立即删除会导致悬挂引用
  • 引用计数保证对象在使用期间有效

问题 :为什么 ObDereferenceObject 不做更多安全检查?

答案

  • 性能考虑:每次 Reference/Dereference 都执行
  • 复杂度分散:安全检查由调用方负责
  • 调试友好:错误更容易定位

4.5.12 小结(增强版)

4.5.12.1 7 个函数的核心要点

函数 核心职责 关键参数 典型调用方
ObReferenceObjectByHandle 句柄 → 对象指针 + 引用 Handle, DesiredAccess, ObjectType 几乎所有接受 HANDLE 的系统调用
ObReferenceObjectByPointer 已知指针 → 引用 Object, ObjectType 驱动内部、对象创建路径
ObpLookupEntryDirectory 目录内名称查找/创建 Directory, Name, Insert ObpLookupObjectName
ObpLookupObjectName 完整路径解析 ObjectName, Attributes ObOpenObjectByName
ObOpenObjectByName 按名称打开对象 ObjectAttributes, ObjectType 各种 NtOpen* 系统调用
ObReferenceObjectByName 按名称引用对象 ObjectName, ObjectType 内核内部
ObDereferenceObject 释放引用 Object 所有 ObReference* 的配对调用

4.5.12.2 设计原则总结

  1. 三类对象访问方式:By-Handle / By-Pointer / By-Name
  2. 三类 API 风格:Lookup / Reference / Open
  3. 引用计数纪律:一进一退,不多不少
  4. 失败回滚:每一步都可以干净地回滚
  5. 并发安全:原子操作 + 内存屏障

4.5.12.3 常见陷阱

  1. 引用计数泄漏 :忘记调用 ObDereferenceObject
  2. 悬挂引用:对象被释放后仍访问
  3. 不匹配的类型 :用 PsProcessType 检查 ETHREAD
  4. 在自旋锁中 Dereference:触发分页,BSOD
  5. 跨线程传递对象指针:可能在线程结束时对象已被释放

4.5.12.4 后续学习路径

  • 第 5 章:进程与线程管理(最复杂的使用场景)
  • 第 6 章:I/O 管理器(次复杂的使用场景)
  • 第 7 章:配置管理器(注册表访问)
  • WRK:Windows Research Kernel(完整 NT 5.2 源码)

4.5.12.5 调试技巧

  • !obtrace:追踪对象创建
  • !handle:查看进程的句柄表
  • !object:查看对象类型
  • !referencetable:查看对象的引用计数
  • Verifier:启用对象引用计数检查

4.5.12.6 本节回顾

本节是 4.1~4.4 节的"工具箱"------把对象管理的核心 API 详细拆解。读者应当能够:

  • 识别这 7 个函数在源码中的使用
  • 编写正确的对象引用/释放代码
  • 诊断引用计数错误
  • 理解对象管理器的整体 API 设计

至此,第 4 章对象管理的全部核心内容已完成。读者已经具备阅读 ReactOS 中任何涉及对象管理的代码的能力。


附录:4.5 节函数交叉引用

引用方 被引用方 关系
NtCreateEvent ObCreateObject 4.5.1 节中讨论的句柄转换常用于分配
ObInsertObject ObpChargeQuotaForObject 4.4.4 节详细讨论
ObInsertObject ObpLookupObjectName 4.5.4 节详细讨论
ObInsertObject ObpCreateHandle 4.3.4 节详细讨论
ObpLookupObjectName ObpLookupEntryDirectory 4.5.3 节详细讨论
ObOpenObjectByName ObpLookupObjectName 4.5.4 节
ObReferenceObjectByName ObpLookupObjectName 4.5.4 节
ObReferenceObjectByHandle ObpCreateHandle 4.3.4 节(反向)
ObDereferenceObject ObpDeleteObject 4.5.7 节详细讨论
ObpDeleteObject Type->DeleteProcedure 4.2.4 节详细讨论
相关推荐
x138702859571 小时前
c语言排雷游戏(基础版9*9)
c语言·算法·游戏
折哥的程序人生 · 物流技术专研1 小时前
Java 23 种设计模式:从踩坑到精通 | 原型模式 —— 克隆对象,深拷贝与浅拷贝的坑你踩过吗?
java·设计模式·架构·原型模式·单一职责原则
装不满的克莱因瓶2 小时前
基于 OpenResty 扩展开发实现动态服务注册与发现能力
java·开发语言·架构·openresty
caimouse2 小时前
Reactos 第 4 章 对象管理 — 4.3 句柄和句柄表(Handle & Handle Table)
c语言·windows·架构
Selina K3 小时前
C中日历时间转换
c语言·开发语言
故渊at3 小时前
第二板块:Android 四大组件标准化学理 | 第六篇:四大组件架构总论与 Manifest 规范
android·架构·zygote·manifest·四大组件
李燚3 小时前
erlang_migrate 架构拆解:behaviour 驱动的多数据库迁移引擎
数据库·postgresql·架构·erlang·migrate·behaviour·erlang_migrate
Chase_______3 小时前
【Java基础 | 15】集合框架(中):Set、HashSet、TreeSet 与哈希表
java·windows·散列表
caimouse3 小时前
Windows NT 内核架构(主通用模型)流 NT 5.x/10+
windows·架构