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. 句柄转换类 :ObReferenceObjectByHandle、ObReferenceObjectByPointer ------ 用于从"用户态可见的句柄"或"已知对象指针"获取"带引用的对象指针"
  2. 名称查找类 :ObpLookupEntryDirectory、ObpLookupObjectName ------ 用于在命名空间中查找对象
  3. 对象打开类 :ObOpenObjectByName、ObReferenceObjectByName ------ 用于按名称打开/引用对象
  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:\Windows 和 c:\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. 现在需要重新从 \ 开始查找

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

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,必须先经过 A 和 B
  • 如果用户对 A 或 B 没有 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. 必须确保 AccessMode 是 KernelMode:用户态不应调用此函数
  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
含义 内核态引用次数 用户态句柄数
操作 ObReferenceObjectByHandle、ObDereferenceObject ObpCreateHandle、ObpCloseHandle
归零影响 触发对象销毁 触发 CloseProcedure

4.5.9 为什么要这样设计

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

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

  1. 性能优化:一个目录内的查找是简单操作,不需要路径解析的复杂逻辑
  2. 代码复用 :ObpInsertEntryDirectory 和 ObpDeleteEntryDirectory 都可以复用此函数
  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 同时 Reference 和 Dereference 同一对象,没有原子操作可能导致:

  1. 计数丢失:两个操作都读到旧值,后写入的丢失
  2. 对象过早释放:计数在不应该归零时归零
  3. BSOD :PointerCount 变负触发 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 节详细讨论
相关推荐
AI小白Lin2 小时前
我的 Agent 迁移"成功"了:每道门都在册,没有一道能跑
架构·llm·agent
meiying88382 小时前
模板建站 vs 定制开发:从技术债、TCO 与可维护性算一笔工程账
架构·saas·建站
真上帝的左手3 小时前
27. 数据产品-数据中台架构规划
数据仓库·架构·数据分析·数据中台
杭州领祺科技3 小时前
零碳园区绿电直连并网验收:688 号文 + 27 号令双合规下 7 产品 4 区架构工程正解
架构·储能·电力
小蜗牛狂飙记3 小时前
Windows 双击 Jar 包无反应,不用写 bat 脚本!Jarfix 一键修复(以 Struts2 扫描工具为例)
windows·struts·jar
hhb_6184 小时前
AIAgent高可用架构与自愈实战
架构
Thneonl4 小时前
让 LLM 值第一班岗:只读的活放手交,变更的活一条别给
后端·架构
ao-weilai4 小时前
MySQL数据库:C语言连接MySQL教程
c语言·数据库·mysql
坐看风云变幻4 小时前
Betaflight 开发环境搭建保姆级教程(Ubuntu / Windows+WSL / Docker 三方案 + 官方网址大全)
windows·ubuntu·docker·无人机·飞控·betaflight
阿标在干嘛4 小时前
缓存架构的“场景适配“:我们为什么在部分服务中采用了本地缓存?
缓存·架构