Reactos 第 4 章 对象管理 — 4.2 对象类型(Object Type)

第 4 章 对象管理 --- 4.2 对象类型(Object Type)

4.2.0 框架图

复制代码
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                                 对象管理器 (Object Manager)                             │
└─────────────────────────────────────────────────────────────────────────────────────────┘
                                            │
                                            ▼
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│                          ObpTypeObjectType  [「类型对象」的类型]                       │
│  ┌───────────────────────────────────────────────────────────────────────────────────┐  │
│  │  OBJECT_TYPE                                                                      │  │
│  │  ┌──────────────────────────────────────────────────────────────────────────────┐  │  │
│  │  │  Name                : L"Type"                                               │  │  │
│  │  │  Index               : 1                                                     │  │  │
│  │  │  Key                 : 'epyT' (ASCII 四字节标签)                             │  │  │
│  │  │  TypeInfo.PoolType   : NonPagedPool                                          │  │  │
│  │  │  TypeInfo.MaintainTypeList : TRUE                                            │  │  │
│  │  │  DeleteProcedure     : ObpDeleteObjectType                                   │  │  │
│  │  │  Mutex + 4 ObjectLocks                                                       │  │  │
│  │  │  TotalNumberOfObjects : N (已注册类型数量)                                   │  │  │
│  │  └──────────────────────────────────────────────────────────────────────────────┘  │  │
│  │                                                                                     │  │
│  │  ┌── TypeList (LIST_ENTRY) ───► POBJECT_HEADER_CREATOR_INFO → POBJECT_HEADER       │  │
│  └──┼──────────────────────────────────────────────────────────────────────────────────┘  │
│     │                                                                                      │
│     │  ┌─────────────────────────┐    ┌─────────────────────────┐    ┌───────────────┐   │
│     └─►│   PsProcessType         │───►│   IoFileObjectType      │───►│ ExEventObject │.. │
│        │   OBJECT_TYPE           │    │   OBJECT_TYPE           │    │   ......      │   │
│        │   Name: L"Process"      │    │   Name: L"File"         │    │               │   │
│        │   DeleteProc: PspDelete │    │   CloseProc: IopCloseFile│   │               │   │
│        └─────────────────────────┘    └─────────────────────────┘    └───────────────┘   │
└────────────────────────────────────────────────────────────────────────────────────────────┘
                                          │
                        ┌─────────────────┴──────────────────┐
                        ▼                                    ▼
┌─────────────────────────────┐           ┌─────────────────────────────┐
│  对象实例 1 (EPROCESS)      │           │  对象实例 2 (FILE_OBJECT)    │
│  OBJECT_HEADER               │           │  OBJECT_HEADER               │
│  ├─ PointerCount             │           │  ├─ PointerCount             │
│  ├─ HandleCount              │           │  ├─ HandleCount              │
│  ├─ Type ────────────────────┼──────────►│  ├─ Type ───► IoFileObjectType│
│  ├─ NameInfo / HandleInfo    │           │  ├─ NameInfo / HandleInfo    │
│  └─ Body (EPROCESS)          │           │  └─ Body (FILE_OBJECT)       │
└─────────────────────────────┘           └─────────────────────────────┘
                                          │
                ┌─────────────────────────┼───────────────────────────┐
                ▼                         ▼                           ▼
┌──────────────────────────┐   ┌──────────────────────────┐   ┌──────────────────────┐
│   NtCreateProcess        │   │     NtCreateFile         │   │    NtCreateEvent     │
│   NtOpenProcess          │   │     NtOpenFile           │   │    NtOpenEvent       │
└──────────────────────────┘   └──────────────────────────┘   └──────────────────────┘

说明 :对象类型本身也是一种对象,所有类型对象的类型为 ObpTypeObjectType,构成一个「类型自举」结构。

类型在系统初始化阶段通过 ObCreateObjectType 注册,并通过 TypeList 链表挂在 ObpTypeObjectType

上进行遍历与查询。用户态通过 NtQueryObject(ObjectTypesInformation) 可以枚举全部类型。

框架图深度解读

这幅图展示了对象类型系统的核心架构,有三个层次的关系需要理解:

第一层:类型的类型(Type of Types)

图中最顶层的 ObpTypeObjectType 是整个系统的基石。它的 Name 字段是 L"Type",意味着"这是一个描述类型的类型"。这种设计被称为自举(Bootstrapping)------系统需要一个起点来定义类型本身。

在 ReactOS 启动时,ObInitSystem 首先创建 ObpTypeObjectType,然后用它来创建其他类型。这类似于面向对象语言中 Object 类是所有类的基类。

第二层:类型对象层(Type Objects)

PsProcessTypeIoFileObjectTypeExEventObjectType 等都是 OBJECT_TYPE 实例。它们通过 TypeList 链表挂在 ObpTypeObjectTypeTypeList 字段上。

每个类型对象包含:

  • 该类型的元数据(Name、Index、Key)
  • 该类型的行为定义(8 个 Procedure 回调)
  • 该类型的统计信息(TotalNumberOfObjects、HighWaterNumberOfObjects)
  • 该类型的同步机制(Mutex、ObjectLocks4

第三层:对象实例层(Object Instances)

最底层的 EPROCESSFILE_OBJECT 等是具体的对象实例。每个实例的 OBJECT_HEADER.Type 字段指向它所属的类型对象。

这种三层结构带来的好处是:

  1. 代码复用:所有对象的创建、引用、销毁逻辑都在对象管理器中统一实现
  2. 行为定制:每种类型通过回调函数定制自己的行为
  3. 类型安全 :通过 Type->Key 字段可以验证句柄类型是否正确
  4. 统一管理 :系统可以通过 ObpTypeObjectType->TypeList 枚举所有类型和对象

命名空间映射

图中没有直接展示的是,每个类型对象都被插入到 \ObjectTypes 目录下。例如:

  • \ObjectTypes\ProcessPsProcessType
  • \ObjectTypes\FileIoFileObjectType
  • \ObjectTypes\EventExEventObjectType

这允许用户态程序通过 NtOpenObjectByName 打开类型对象,查询该类型的统计信息。

4.2.0.1 设计意图

核心问题

Windows 是一个基于对象的操作系统,对象管理器需要统一管理内核中形形色色的对象(进程、线程、事件、文件、注册表键、设备等)。这些对象在创建方式生命周期权限模型打开/关闭语义等待能力等方面差异极大。如何用一套统一的机制来描述并驱动这些差异,是对象管理器必须解决的根本问题。

设计哲学

对象管理器采用「元对象(Meta-Object) 」的设计哲学:为每种对象创建一个「对象类型(Object Type) 」来充当模板行为表。内核中所有对象实例都指向其类型对象,由类型对象提供:

  1. 对象体大小 ------ 对象创建时分配多大的内存
  2. 配额费用 ------ 创建时在进程配额中扣取多少分页/非分页池
  3. 访问权限映射表 ------ GENERIC_READ/WRITE/EXECUTE/ALL 映射到该类型的具体权限
  4. 有效访问掩码 & 非法属性 ------ 限定该类型允许的属性和访问
  5. 过程回调表 ------ 定义打开/关闭/删除/解析/查询名称/安全操作/等待时的钩子函数

这种设计使得对象管理器的核心代码保持类型无关(Type-Agnostic)ObCreateObjectObReferenceObjectByHandleObCloseHandle 等通用代码通过调用类型上的 Procedure,把类型相关的行为下放给各子系统(PS、IO、MM、CM、EX、LPC)去实现。

本节定位

本节将从 ReactOS 源码出发,深入解析 OBJECT_TYPE 结构、对象类型的创建与初始化、各类 Procedure 回调机制、以及同步对象与等待机制的整合。它是理解「对象为什么拥有行为」的关键桥梁。


4.2.1 什么是对象类型

类与实例的类比

把对象类型理解为面向对象语言中的 Class(类) ,把内核对象理解为Instance(实例)

  • OBJECT_TYPE ≈ Class 描述:定义了对象体的大小、分配位置、配额、权限映射、虚方法表(Procedure 表)。
  • 内核对象(如 EPROCESSFILE_OBJECT) ≈ Instance :由对象管理器通过 ObCreateObject 分配,每个对象头中的 Type 字段指向其类型对象。
  • 同一类型的所有实例共享相同行为 :所有进程对象的删除都走 PspDeleteProcess,所有文件对象的关闭都走 IopCloseFile

对象类型定义了对象的「行为契约」

c 复制代码
/* 每个对象头都保存指向其类型的指针 */
typedef struct _OBJECT_HEADER {
    LONG_PTR PointerCount;
    union { LONG_PTR HandleCount; volatile PVOID NextToFree; };
    POBJECT_TYPE Type;              /* ← 类型指针:决定此对象的行为 */
    UCHAR NameInfoOffset;
    UCHAR HandleInfoOffset;
    UCHAR QuotaInfoOffset;
    UCHAR Flags;
    union {
        POBJECT_CREATE_INFORMATION ObjectCreateInfo;
        PVOID QuotaBlockCharged;
    };
    PSECURITY_DESCRIPTOR SecurityDescriptor;
    QUAD Body;                     /* ← 对象体起始 */
} OBJECT_HEADER, *POBJECT_HEADER;

ntoskrnl/include/ndk/obtypes.h:485-505(file:///d:/reactos/sdk/include/ndk/obtypes.h#L485-L505)

系统内置类型数量概览

在 ReactOS 初始化阶段,各子系统通过 ObCreateObjectType 注册了约 20~30 种核心对象类型。部分典型类型如下:

所属子系统 典型类型
ntoskrnl/ob Type, Directory, SymbolicLink
ntoskrnl/ps Process, Thread, Job
ntoskrnl/io/iomgr File, Device, Driver, Adapter, Controller, IoCompletion
ntoskrnl/ex Event, Semaphore, Mutant, Timer, EventPair, Profile, Callback
ntoskrnl/mm Section
ntoskrnl/config Key(注册表)
ntoskrnl/lpc Port, WaitablePort
ntoskrnl/se Token(访问令牌)
ntoskrnl/dbgk DebugObject
ntoskrnl/wmi WmiGuid

其中 Token、Desktop、WindowStation、Key、Section、SymbolicLink 等类型对用户态不可直接创建 (没有对应的 NtCreateXxx),只能通过特定 API 或命名空间解析间接获得句柄。


4.2.2 OBJECT_TYPE 结构详解

完整结构定义

OBJECT_TYPE 定义在 sdk/include/ndk/obtypes.h:379-393(file:///d:/reactos/sdk/include/ndk/obtypes.h#L379-L393):

c 复制代码
typedef struct _OBJECT_TYPE {
    ERESOURCE Mutex;                    /* 类型对象自身的互斥资源锁 */
    LIST_ENTRY TypeList;                /* 挂入 ObpTypeObjectType->TypeList 的链表 */
    UNICODE_STRING Name;                /* 类型名称,如 L"Process"、L"File"、L"Event" */
    PVOID DefaultObject;                /* 默认的可等待对象 (KEVENT 指针或偏移) */
    ULONG Index;                        /* 类型索引 (1-based, Index-1 作为 ObpObjectTypes[] 下标) */
    ULONG TotalNumberOfObjects;         /* 当前存活对象数量 */
    ULONG TotalNumberOfHandles;         /* 当前句柄数量 */
    ULONG HighWaterNumberOfObjects;     /* 对象数量历史峰值 (高水位) */
    ULONG HighWaterNumberOfHandles;     /* 句柄数量历史峰值 (高水位) */
    OBJECT_TYPE_INITIALIZER TypeInfo;   /* 类型初始化器 (过程回调 + 各种属性) */
    ULONG Key;                          /* 四字节标签 (如 'File' 'Proc' 'Even'),用于句柄表校验 */
    ERESOURCE ObjectLocks[4];           /* 用于分散锁,避免对象实例争抢 */
} OBJECT_TYPE;

OBJECT_TYPE_INITIALIZER 结构

OBJECT_TYPE_INITIALIZER 是调用 ObCreateObjectType 时传入的核心参数,定义在

sdk/include/ndk/obtypes.h:352-374(file:///d:/reactos/sdk/include/ndk/obtypes.h#L352-L374):

c 复制代码
typedef struct _OBJECT_TYPE_INITIALIZER {
    USHORT Length;                              /* 本结构大小 (sizeof) */
    BOOLEAN UseDefaultObject;                   /* 是否使用默认 KEVENT 作为可等待对象 */
    BOOLEAN CaseInsensitive;                    /* 名字比较是否大小写不敏感 (如文件) */
    ULONG InvalidAttributes;                    /* 不允许的 OBJECT_ATTRIBUTES 标志位组合 */
    GENERIC_MAPPING GenericMapping;             /* GENERIC_xxx → 具体权限的映射表 */
    ULONG ValidAccessMask;                      /* 该类型有效的访问掩码 */
    BOOLEAN SecurityRequired;                   /* 该类型对象是否必须有安全描述符 */
    BOOLEAN MaintainHandleCount;                /* 是否维护 Per-Process 句柄计数 */
    BOOLEAN MaintainTypeList;                   /* 是否维护 CreatorInfo->TypeList */
    POOL_TYPE PoolType;                         /* 对象实例分配于 NonPagedPool 还是 PagedPool */
    ULONG DefaultPagedPoolCharge;               /* 默认分页池配额 */
    ULONG DefaultNonPagedPoolCharge;            /* 默认非分页池配额 */
    OB_DUMP_METHOD DumpProcedure;               /* 调试 Dump 回调 */
    OB_OPEN_METHOD OpenProcedure;               /* 句柄被打开/复制/继承时回调 */
    OB_CLOSE_METHOD CloseProcedure;             /* 句柄递减到 0 时回调 */
    OB_DELETE_METHOD DeleteProcedure;           /* PointerCount 为 0 时释放对象体 */
    OB_PARSE_METHOD ParseProcedure;             /* 命名空间解析时回调 (如 Device->File, Section 映射) */
    OB_SECURITY_METHOD SecurityProcedure;       /* 查询/设置安全描述符时回调 */
    OB_QUERYNAME_METHOD QueryNameProcedure;     /* NtQueryObject(NameInformation) 时回调 */
    OB_OKAYTOCLOSE_METHOD OkayToCloseProcedure; /* 允许关闭前的确认 (通常 NULL) */
} OBJECT_TYPE_INITIALIZER, *POBJECT_TYPE_INITIALIZER;

各字段深入说明

字段 说明
Name 类型的可读名称。通过 NtQueryObject(ObjectTypesInformation) 返回给用户态。系统内部使用 ObpLookupEntryDirectory\ObjectTypes 目录下按名字查找。
Index 类型索引,范围 [1, ObpObjectTypes[] 大小 - 1]。用于快速在全局 ObpObjectTypes 数组中定位类型对象。ObpTypeObjectType 自身的 Index 为 1。
Key 四字节标签,由类型名前 4 字符转换为 ASCII 得到(如 File→'eliF')。用于句柄表中的句柄类型校验,防止把错误类型的句柄传入系统调用。
PoolType 对象实例默认分配的池类型。同步对象(Event/Mutant/Semaphore/Timer)、Process/Thread 必须用 NonPagedPool;Directory/SymbolicLink/File 可用 PagedPool
DefaultPaged/NonPagedPoolCharge 创建一个对象时从当前进程配额扣除的内存量。在 ObCreateObjectType 内部会把 OBJECT_HEADER 及附加信息(NameInfo/HandleInfo)的尺寸自动追加到此字段。
InvalidAttributes 该类型对象不允许 传入的 OBJ_* 标志位,例如 Process 类型禁止 `OBJ_PERMANENT
GenericMapping 权限映射表。RtlMapGenericMask 用它把 GENERIC_READ/WRITE/EXECUTE/ALL 替换为该类型具体的权限位。例如 File 类型的 GENERIC_READ 会展开为 `FILE_READ_DATA
ValidAccessMask 该类型定义的全部有效权限位。传入 DesiredAccess 若含有未在该掩码中定义的位会被 ObCheckObjectAccess 拒绝。
MaintainHandleCount 是否维护每个对象的「进程级别句柄计数」(即 OBJECT_HEADER_HANDLE_INFO)。文件类型开启此开关,以便 NtQueryInformationFile(FilePositionInformation) 能判断文件是否被同一进程多次打开。
MaintainTypeList 是否将所有此类型的已创建对象通过 OBJECT_HEADER_CREATOR_INFO::TypeList 挂入类型对象的 TypeList。用于调试与泄漏检测。受全局标志 FLG_MAINTAIN_OBJECT_TYPELIST 覆盖。
DefaultObject UseDefaultObject = TRUE 时,对象被作为同步对象使用,KeWaitForSingleObject 会调用 ObWaitForSingleObject,它通过 DefaultObject 取得 DISPATCHER_HEADER。对 File 类型,这里硬编码为 FILE_OBJECT.Event 的偏移量而不是全局事件指针。
Mutex / ObjectLocks[4] 类型自身的锁与一组分布式锁,用于修改类型内部统计、插入 TypeList 等操作。
字段设计的精妙之处

OBJECT_TYPE 结构的设计体现了多种系统设计原则:

1. 锁的分层设计

MutexObjectLocks[4] 构成了两级锁体系:

  • Mutex:保护类型对象本身的元数据(Name、Index、统计信息)
  • ObjectLocks[4]:分散锁,用于保护 TypeList 的插入/删除操作

这种设计减少了锁竞争------多个线程可以同时向不同类型添加对象,而不会阻塞彼此。

2. Key 字段的双重用途

Key 字段有两个作用:

  • 句柄类型验证:当通过句柄访问对象时,句柄表条目会记录类型的 Key,用于快速验证句柄类型是否匹配
  • 调试辅助:Key 由类型名的前 4 个字符组成(如 'Proc'、'File'),便于调试时快速识别对象类型

3. Index 的快速查找机制

Index 字段是 1-based 的,Index - 1 直接作为全局数组 ObpObjectTypes[] 的下标。这种设计允许 O(1) 时间复杂度查找类型对象,而无需遍历 TypeList。

4. DefaultObject 的灵活性

DefaultObject 可以是:

  • 全局事件指针:如 Event、Semaphore 类型
  • 偏移量 :如 File 类型,存储 FILE_OBJECT.Event 相对于对象头的偏移

这种灵活性使得不同类型可以以不同方式支持等待操作。

TotalNumberOfObjects / HighWater 统计

  • TotalNumberOfObjects:此类型当前仍存活的对象数。在 ObCreateObject 递增,在 ObpDeleteObject 递减。
  • TotalNumberOfHandles:此类型当前仍打开的句柄数。在句柄创建/复制时递增,句柄关闭时递减。
  • HighWaterNumberOfObjects / HighWaterNumberOfHandles:记录历史最高峰值,永不回退,供性能诊断和驱动泄露检测使用。

这与「Pool Quota」是两个正交的统计维度:Pool Charge 反映的是「一个对象占了多少池内存」,而 TotalNumberOfObjects 反映的是「创建/销毁的对象数量曲线」。


4.2.3 对象类型的初始化:ObInitSystem

对象类型的初始化分为两个阶段:

  1. Phase 0 (ObInitSystem 首次调用):创建最基本的三个类型 ------ ObpTypeObjectTypeObpDirectoryObjectTypeObpSymbolicLinkObjectType。此时系统尚不能创建 \ObjectTypes 目录。
  2. Phase 1 (后续阶段):创建根目录 \\KernelObjects\ObjectTypes,然后把已注册的所有类型通过 ObpInsertEntryDirectory 插入到 \ObjectTypes 目录中,以便用户态可按名枚举。

ObInitSystem 主流程

核心代码位于 ntoskrnl/ob/obinit.c:203-303(file:///d:/reactos/ntoskrnl/ob/obinit.c#L203-L303):

c 复制代码
/* Phase 0 ------ 创建 Type、Directory、SymbolicLink 三种基础类型 */

/* 1) ObpTypeObjectType: 所有对象类型的类型 */
RtlZeroMemory(&ObjectTypeInitializer, sizeof(ObjectTypeInitializer));
RtlInitUnicodeString(&Name, L"Type");
ObjectTypeInitializer.Length = sizeof(ObjectTypeInitializer);
ObjectTypeInitializer.ValidAccessMask = OBJECT_TYPE_ALL_ACCESS;
ObjectTypeInitializer.UseDefaultObject = TRUE;
ObjectTypeInitializer.MaintainTypeList = TRUE;
ObjectTypeInitializer.PoolType = NonPagedPool;
ObjectTypeInitializer.GenericMapping = ObpTypeMapping;
ObjectTypeInitializer.DefaultNonPagedPoolCharge = sizeof(OBJECT_TYPE);
ObjectTypeInitializer.InvalidAttributes = OBJ_OPENLINK;
ObjectTypeInitializer.DeleteProcedure = ObpDeleteObjectType;
ObCreateObjectType(&Name, &ObjectTypeInitializer, NULL, &ObpTypeObjectType);

/* 2) ObpDirectoryObjectType */
RtlInitUnicodeString(&Name, L"Directory");
ObjectTypeInitializer.PoolType = PagedPool;
ObjectTypeInitializer.ValidAccessMask = DIRECTORY_ALL_ACCESS;
ObjectTypeInitializer.CaseInsensitive = TRUE;
ObjectTypeInitializer.MaintainTypeList = FALSE;
ObjectTypeInitializer.GenericMapping = ObpDirectoryMapping;
ObjectTypeInitializer.DeleteProcedure = NULL;
ObjectTypeInitializer.DefaultNonPagedPoolCharge = sizeof(OBJECT_DIRECTORY);
ObCreateObjectType(&Name, &ObjectTypeInitializer, NULL, &ObpDirectoryObjectType);
ObpDirectoryObjectType->TypeInfo.ValidAccessMask &= ~SYNCHRONIZE;

/* 3) ObpSymbolicLinkObjectType */
RtlInitUnicodeString(&Name, L"SymbolicLink");
ObjectTypeInitializer.DefaultNonPagedPoolCharge = sizeof(OBJECT_SYMBOLIC_LINK);
ObjectTypeInitializer.GenericMapping = ObpSymbolicLinkMapping;
ObjectTypeInitializer.ValidAccessMask = SYMBOLIC_LINK_ALL_ACCESS;
ObjectTypeInitializer.ParseProcedure = ObpParseSymbolicLink;
ObjectTypeInitializer.DeleteProcedure = ObpDeleteSymbolicLink;
ObCreateObjectType(&Name, &ObjectTypeInitializer, NULL, &ObpSymbolicLinkObjectType);
ObpSymbolicLinkObjectType->TypeInfo.ValidAccessMask &= ~SYNCHRONIZE;

其他子系统的注册示例

Process/Thread/Job ------ ntoskrnl/ps/psmgr.c:405-438(file:///d:/reactos/ntoskrnl/ps/psmgr.c#L405-L438)

c 复制代码
RtlZeroMemory(&ObjectTypeInitializer, sizeof(ObjectTypeInitializer));
ObjectTypeInitializer.Length = sizeof(ObjectTypeInitializer);
ObjectTypeInitializer.InvalidAttributes = OBJ_PERMANENT | OBJ_EXCLUSIVE | OBJ_OPENIF;
ObjectTypeInitializer.PoolType = NonPagedPool;
ObjectTypeInitializer.SecurityRequired = TRUE;

RtlInitUnicodeString(&Name, L"Process");
ObjectTypeInitializer.DefaultNonPagedPoolCharge = sizeof(EPROCESS);
ObjectTypeInitializer.GenericMapping = PspProcessMapping;
ObjectTypeInitializer.ValidAccessMask = PROCESS_ALL_ACCESS;
ObjectTypeInitializer.DeleteProcedure = PspDeleteProcess;
ObCreateObjectType(&Name, &ObjectTypeInitializer, NULL, &PsProcessType);

Event ------ ntoskrnl/ex/event.c:39-57(file:///d:/reactos/ntoskrnl/ex/event.c#L39-L57)

c 复制代码
RtlZeroMemory(&ObjectTypeInitializer, sizeof(ObjectTypeInitializer));
RtlInitUnicodeString(&Name, L"Event");
ObjectTypeInitializer.Length = sizeof(ObjectTypeInitializer);
ObjectTypeInitializer.DefaultNonPagedPoolCharge = sizeof(KEVENT);
ObjectTypeInitializer.GenericMapping = ExpEventMapping;
ObjectTypeInitializer.PoolType = NonPagedPool;
ObjectTypeInitializer.ValidAccessMask = EVENT_ALL_ACCESS;
ObjectTypeInitializer.InvalidAttributes = OBJ_OPENLINK;
ObCreateObjectType(&Name, &ObjectTypeInitializer, NULL, &ExEventObjectType);

File ------ ntoskrnl/io/iomgr/iomgr.c:306-322(file:///d:/reactos/ntoskrnl/io/iomgr/iomgr.c#L306-L322)

c 复制代码
RtlInitUnicodeString(&Name, L"File");
ObjectTypeInitializer.DefaultNonPagedPoolCharge = sizeof(FILE_OBJECT);
ObjectTypeInitializer.InvalidAttributes |= OBJ_EXCLUSIVE;
ObjectTypeInitializer.MaintainHandleCount = TRUE;
ObjectTypeInitializer.ValidAccessMask = FILE_ALL_ACCESS;
ObjectTypeInitializer.GenericMapping = IopFileMapping;
ObjectTypeInitializer.CloseProcedure = IopCloseFile;
ObjectTypeInitializer.DeleteProcedure = IopDeleteFile;
ObjectTypeInitializer.SecurityProcedure = IopGetSetSecurityObject;
ObjectTypeInitializer.QueryNameProcedure = IopQueryName;
ObjectTypeInitializer.ParseProcedure = IopParseFile;
ObjectTypeInitializer.UseDefaultObject = FALSE;
ObCreateObjectType(&Name, &ObjectTypeInitializer, NULL, &IoFileObjectType);

ObCreateObjectType 做了什么

ObCreateObjectType 的核心实现位于 ntoskrnl/ob/oblife.c:1136-1395(file:///d:/reactos/ntoskrnl/ob/oblife.c#L1136-L1395)。主要步骤:

  1. 参数校验 ------ 检查 LengthInvalidAttributesPoolType 的合法性。特别地,如果 MaintainHandleCount 为 TRUE,则必须提供 OpenProcedureCloseProcedure
  2. 名称查重 ------ 如果 \ObjectTypes 目录已存在,则在此目录中查找重名类型(ObpLookupEntryDirectory)。
  3. 分配类型对象 ------ 调用 ObpAllocateObject(NULL, &ObjectName, ObpTypeObjectType, sizeof(OBJECT_TYPE), ...),用类型对象自身的类型(ObpTypeObjectType)分配一个 OBJECT_TYPE 对象体。
  4. 设置自举 (Bootstrap)------ 如果 ObpTypeObjectType == NULL (即当前正在创建的就是 ObpTypeObjectType 自身),则把它设为自己的 Header->Type,并把 Key 硬编码为 TAG_OBJECT_TYPE('epyT')TotalNumberOfObjects = 1
  5. Key 计算 ------ 其他类型则把名称前 4 字符转 ANSI 并作为 4 字节 ULONG(如 'Proc''File''Even')。
  6. TypeInfo 复制 ------ 把传入的 OBJECT_TYPE_INITIALIZER 复制到 LocalObjectType->TypeInfo,然后调整 PoolType、把 Header 大小追加到 PoolCharge、若未指定 SecurityProcedure 则使用默认的 SeDefaultObjectMethod
  7. DefaultObject 决定
    • UseDefaultObject = TRUE:默认可等待对象设为全局 ObpDefaultObject(一个永远处于 Signaled 状态的 Notification Event),并把 ValidAccessMask |= SYNCHRONIZE
    • 若类型名为 L"File":硬编码 DefaultObject = UlongToPtr(FIELD_OFFSET(FILE_OBJECT, Event))。这样 KeWaitForSingleObject 能直接从 FILE_OBJECT 内部的事件等待, UseDefaultObject 保持 FALSE。
    • 若类型名为 L"WaitablePort":硬编码 DefaultObject = UlongToPtr(FIELD_OFFSET(LPCP_PORT_OBJECT, WaitEvent))
    • 否则 DefaultObject = NULL(不可等待)。
  8. 锁初始化 ------ ExInitializeResourceLite 初始化 Mutex 及 4 个 ObjectLocks
  9. 挂入全局 TypeList ------ 通过 OBJECT_HEADER_CREATOR_INFO::TypeList 把这个类型挂入 ObpTypeObjectType->TypeList 链表。
  10. 分配 Index ------ Index = ObpTypeObjectType->TotalNumberOfObjects,并在 ObpObjectTypes[Index-1] 中注册,以便快速定位。
  11. 插入 \ObjectTypes 目录 ------ 若目录存在则按名字插入,使对象类型对用户态可见。

这个过程有一个非常优美的自举 性质:ObpTypeObjectType 是「类型对象的类型」,它自己的类型字段也指向自己Header->Type = ObpTypeObjectType),构成了一个自引用的不动点(Fixed-Point)。


4.2.4 类型回调机制(Procedure Callbacks)

回调签名定义

以下回调类型均定义在 sdk/include/ndk/obtypes.h:185-256(file:///d:/reactos/sdk/include/ndk/obtypes.h#L185-L256):

回调 签名
OB_OPEN_METHOD NTSTATUS (NTAPI *)(OB_OPEN_REASON Reason, PEPROCESS Process, PVOID ObjectBody, ACCESS_MASK GrantedAccess, ULONG HandleCount)
OB_CLOSE_METHOD VOID (NTAPI *)(PEPROCESS Process, PVOID Object, ACCESS_MASK GrantedAccess, ULONG ProcessHandleCount, ULONG SystemHandleCount)
OB_DELETE_METHOD VOID (NTAPI *)(PVOID Object)
OB_PARSE_METHOD NTSTATUS (NTAPI *)(PVOID ParseObject, PVOID ObjectType, PACCESS_STATE AccessState, KPROCESSOR_MODE AccessMode, ULONG Attributes, PUNICODE_STRING CompleteName, PUNICODE_STRING RemainingName, PVOID Context, PSECURITY_QUALITY_OF_SERVICE SecurityQos, PVOID *Object)
OB_SECURITY_METHOD NTSTATUS (NTAPI *)(PVOID Object, SECURITY_OPERATION_CODE OperationType, PSECURITY_INFORMATION SecurityInformation, PSECURITY_DESCRIPTOR SecurityDescriptor, PULONG CapturedLength, PSECURITY_DESCRIPTOR *ObjectSecurityDescriptor, POOL_TYPE PoolType, PGENERIC_MAPPING GenericMapping)
OB_QUERYNAME_METHOD NTSTATUS (NTAPI *)(PVOID Object, BOOLEAN HasObjectName, POBJECT_NAME_INFORMATION ObjectNameInfo, ULONG Length, PULONG ReturnLength, KPROCESSOR_MODE AccessMode)
OB_OKAYTOCLOSE_METHOD BOOLEAN (NTAPI *)(PEPROCESS Process, PVOID Object, HANDLE Handle, KPROCESSOR_MODE AccessMode)
OB_DUMP_METHOD VOID (NTAPI *)(PVOID Object, POB_DUMP_CONTROL Control)

回调触发时机总览

下表总结了每个回调的触发时机、调用者与用途:

回调 何时调用 调用方 (典型位置) 用途
OpenProcedure 每次句柄被创建/复制/继承(包括 NtCreateXxxNtOpenXxxNtDuplicateObject、句柄继承) ObpIncrementHandleCount / ObCreateObject 之后 让对象类型在「一个句柄被交出」前做一次审计或调整;Process 类型用它做引用计数或配额检查。
CloseProcedure 句柄递减到 0 时(NtClose 使某对象在该进程中的句柄数归零) ObpDecrementHandleCount File 类型用于触发 IRP_MJ_CLEANUP,释放文件锁、取消重叠 I/O。
DeleteProcedure 对象头 PointerCount 从 1 → 0,真正要释放对象体时 ObpDeleteObject 释放对象体内部引用(如 EPROCESS 的 Token、ImageSectionObject)。
ParseProcedure 名字解析路径上遇到该类型对象作为目录项时(\??\C:\foo.txt 解析 Device 后剩余 C:\foo.txt 交给 File 解析;Section 对象用于内存映射文件解析) ObpLookupObjectName 最复杂回调:由「父对象」把剩余路径解析委托给子系统。Device/IopParseDevice、File/IopParseFile、Section/MiSectionParseProcedure、SymbolicLink/ObpParseSymbolicLink 是典型示例。
SecurityProcedure NtQuerySecurityObject / NtSetSecurityObject、创建时分配 SD ObGetObjectSecurityObSetSecurityObject 若为默认 SeDefaultObjectMethod,使用保存在 OBJECT_HEADER::SecurityDescriptor 的 SD。Key 类型(注册表)重写此回调以读写 hive 内保存的 SD。
QueryNameProcedure NtQueryObject(ObjectNameInformation) ObQueryNameInfo 由文件对象实现,返回完整的 \Device\HarddiskVolume1\foo.txt 路径(因为 FILE_OBJECT 没有 NameInfo 结构)。其他类型通常使用默认实现(返回 NameInfo 中的名称)。
OkayToCloseProcedure NtClose 之前 ObpCloseHandle 极少使用。若返回 FALSE,则拒绝本次 NtClose(常用于调试或特殊保护对象)。
DumpProcedure 调试器 !object 扩展命令 调试支持 输出此对象的调试信息。

典型类型的回调表

类型 OpenProcedure CloseProcedure DeleteProcedure ParseProcedure SecurityProcedure QueryNameProcedure
Process --- --- PspDeleteProcess --- 默认 ---
Thread --- --- PspDeleteThread --- 默认 ---
File --- IopCloseFile IopDeleteFile IopParseFile IopGetSetSecurityObject IopQueryName
Device --- --- --- IopParseDevice IopGetSetSecurityObject ---
Section --- --- --- MiSectionParseProcedure 默认 ---
Key(注册表) --- --- --- --- CmpKeySecurityProcedure ---
SymbolicLink --- --- ObpDeleteSymbolicLink ObpParseSymbolicLink 默认 ---
Event / Semaphore / Mutant / Timer --- --- --- --- 默认 ---
Token --- --- SepDeleteToken --- 默认 ---
IoCompletion (IOCP) --- --- IopDeleteIoCompletion --- 默认 ---

上表说明:同步对象类型是「最纯」的对象类型 ------ 它们只是把 KEVENT/KSEMAPHORE/KMUTANT/KTIMER 包一层 OBJECT_HEADER,既不需要 open/close 钩子,也不需要命名解析,只依赖默认的 SeDefaultObjectMethod 做权限检查。

回调机制的设计原理

回调机制是对象管理器实现「类型无关核心 + 类型特定行为」的关键技术。它体现了以下设计原则:

1. 开闭原则(Open/Closed Principle)

对象管理器的核心代码(ObCreateObjectObCloseHandle 等)对扩展开放,对修改关闭。当需要添加新的对象类型时,只需注册新的回调函数,而无需修改对象管理器的核心逻辑。

2. 策略模式(Strategy Pattern)

每种回调函数代表一种策略:

  • 删除策略DeleteProcedure 定义了如何清理对象体
  • 解析策略ParseProcedure 定义了如何解析路径
  • 安全策略SecurityProcedure 定义了如何管理安全描述符

3. 职责分离

回调机制实现了对象管理器与子系统的职责分离:

  • 对象管理器:负责对象的通用生命周期管理(分配、引用计数、句柄管理)
  • 子系统:负责对象的类型特定行为(文件关闭时触发 IRP、进程删除时清理地址空间)
回调链的执行顺序

在对象的生命周期中,回调函数按特定顺序被调用:

复制代码
创建阶段:
1. ObCreateObject → 分配内存(无回调)
2. ObInsertObject → 可能触发 ParseProcedure(路径解析)

使用阶段:
3. ObpCreateHandle → 触发 OpenProcedure(句柄创建)
4. NtDuplicateObject → 触发 OpenProcedure(句柄复制)

销毁阶段:
5. ObCloseHandle → 触发 OkayToCloseProcedure(可选)
6. ObpDecrementHandleCount → 触发 CloseProcedure(句柄数归零)
7. ObDereferenceObject → 触发 DeleteProcedure(引用计数归零)
典型回调实现分析

File 类型的 CloseProcedure

IopCloseFile 是一个典型的回调实现:

c 复制代码
VOID NTAPI IopCloseFile(
    PEPROCESS Process,
    PVOID Object,
    ACCESS_MASK GrantedAccess,
    ULONG ProcessHandleCount,
    ULONG SystemHandleCount
)
{
    PFILE_OBJECT FileObject = (PFILE_OBJECT)Object;
    
    // 触发 IRP_MJ_CLEANUP
    IopCleanupFile(FileObject, TRUE);
    
    // 释放文件对象的私有数据
    if (FileObject->FsContext) {
        ExFreePool(FileObject->FsContext);
    }
    
    // 取消所有挂起的 I/O 操作
    IopCancelAllOperations(FileObject);
}

这个回调展示了 CloseProcedure 的典型职责:

  • 清理资源(释放内存、关闭句柄)
  • 取消挂起操作
  • 通知子系统(如文件系统驱动)

4.2.5 Wait / Notification 机制与同步对象

什么是「可等待对象」

并非所有内核对象都能作为 KeWaitForSingleObject / KeWaitForMultipleObjects 的目标。只有以下对象是可等待的:

类型 内部同步原语 Signaled 状态
Process (PsProcessType) KPROCESS.Header 进程退出时
Thread (PsThreadType) KTHREAD.Header 线程退出时
Event (ExEventObjectType) KEVENT KeSetEvent
Semaphore (ExSemaphoreObjectType) KSEMAPHORE 计数 > 0
Mutant (ExMutantObjectType) KMUTANT 释放 / 未被占用
Timer (ExTimerType) KTIMER 到时
File (IoFileObjectType) FILE_OBJECT.Event I/O 完成时设置事件
Key (CmpKeyObjectType) CM_KEY_BODY.KeyControlBlock->Lock 极少直接等待
IoCompletion (IoCompletionType) KQUEUE 队列非空

可等待对象的共同特点:对象体内部嵌入了 DISPATCHER_HEADER,它是内核调度器(Dispatcher)识别的统一头部。

DISPATCHER_HEADER 与 KWAIT_BLOCK

DISPATCHER_HEADER 定义在内核内部头文件(ntoskrnl/include/internal/ke.h)。简要结构:

复制代码
+0x00 UCHAR Type;          // 区分 Event/Mutant/Semaphore/Timer/Thread/Queue
+0x01 UCHAR Signalling;    // 是否处于 Signaled 状态
+0x02 UCHAR Size;          // 该对象体的 4 字节单位数
+0x03 UCHAR Reserved1;
+0x04 LONG SignalState;    // 当前信号状态 (Event: 0/1; Mutant: -1; Semaphore: N)
+0x08 LIST_ENTRY WaitListHead;  // 等待此对象的 KWAIT_BLOCK 链表

当线程调用 KeWaitForSingleObject(Object) 时:

  1. 对象管理器通过 ObpReferenceObjectByHandleWithTag 得到对象指针。
  2. 对象管理器负责从对象体获取 DISPATCHER_HEADER 指针 。对 File 类型,使用 DefaultObject 中保存的偏移量;对 Event/Mutant/Semaphore/Timer 类型,它们的对象体本身就是 KEVENT,所以 Body 指针即 Dispatcher Header 指针。
  3. KeWaitForSingleObject 分配一个 KWAIT_BLOCK,记录线程、对象、等待原因等,把 KWAIT_BLOCK 插入到 DISPATCHER_HEADER.WaitListHead 链表。
  4. 把线程状态置为 Waiting,并把 KTHREAD.WaitBlock[0..N] 指向这些 KWAIT_BLOCK
  5. 下次调度器触发时,该线程不会被调度。直到对象被设置为 Signaled,Dispatcher 遍历 WaitListHead 唤醒等待线程。

ObWaitForSingleObject 的角色

ObWaitForSingleObject(位于 ntoskrnl/ob/obwait.c)是 KeWaitForXxxObject 的上层封装。它负责:

  • 验证句柄 → 转换为对象体
  • 通过 OBJECT_TYPE::DefaultObject 决定内部的 DISPATCHER_HEADER
  • 在线程退出等待时引用对象并释放锁
  • 对于 FILE 对象,它通过 DefaultObject 中的偏移量 找到 FILE_OBJECT.Event 字段(一个 KEVENT),直接传递给 KeWaitForSingleObject

这种设计使得:对象管理器不需要知道任何同步原语的内部结构 ,只依赖类型对象中的 DefaultObject 偏移量或指针,实现了解耦。

等待机制的分层架构

等待机制涉及三个层次的协作:

第一层:用户态 API 层

复制代码
WaitForSingleObject(hEvent, INFINITE)
    ↓
NtWaitForSingleObject(hEvent, FALSE, NULL)
    ↓
KeWaitForSingleObject(Object, Executive, KernelMode, FALSE, NULL)

第二层:对象管理器层(ObWaitForSingleObject)

负责将句柄转换为对象指针,并定位到正确的 DISPATCHER_HEADER

c 复制代码
NTSTATUS ObWaitForSingleObject(
    HANDLE Handle,
    BOOLEAN Alertable,
    PLARGE_INTEGER Timeout
)
{
    // 1. 通过句柄获取对象
    ObReferenceObjectByHandle(Handle, SYNCHRONIZE, NULL, &Object);
    
    // 2. 获取 DISPATCHER_HEADER
    if (Object->Type->UseDefaultObject) {
        if (Object->Type->DefaultObject & 1) {
            // DefaultObject 是偏移量
            DispatcherHeader = (PDISPATCHER_HEADER)((PCHAR)Object + 
                (ULONG_PTR)Object->Type->DefaultObject & ~1);
        } else {
            // DefaultObject 是指针
            DispatcherHeader = (PDISPATCHER_HEADER)Object->Type->DefaultObject;
        }
    }
    
    // 3. 调用调度器等待
    Status = KeWaitForSingleObject(DispatcherHeader, ...);
    
    ObDereferenceObject(Object);
    return Status;
}

第三层:调度器层(KeWaitForSingleObject)

负责管理线程状态和等待队列:

复制代码
线程状态转换:Running → Waiting
插入 WaitListHead 链表
调度器跳过此线程直到被唤醒
信号状态的传播机制

当对象被设置为 Signaled 状态时:

c 复制代码
KeSetEvent(Event, IO_NO_INCREMENT, FALSE)
    ↓
唤醒 WaitListHead 上的所有等待线程
    ↓
线程状态:Waiting → Ready
    ↓
调度器在下一个时间片调度这些线程
特殊情况:FILE 对象的等待

FILE 对象的等待机制比较特殊:

  1. FILE_OBJECT.Event 是一个内嵌的 KEVENT
  2. DefaultObject 存储的是偏移量(0x70 左右,具体取决于版本)
  3. 当 I/O 完成时,I/O 管理器设置此事件
  4. 用户态可以通过 WaitForSingleObject 等待文件 I/O 完成

4.2.6 系统对象类型一览表

下表列出 ReactOS 中常见的对象类型,给出其类型名称用户态创建 API用途关键点/回调 以及注册位置

类型名称 创建 API 用途 关键点 / 回调 注册位置
Type (内核专用) 对象类型的类型 MaintainTypeList=TRUE, DeleteProc=ObpDeleteObjectType, 自举 obinit.c:278(file:///d:/reactos/ntoskrnl/ob/obinit.c#L278)
Directory NtCreateDirectoryObject 实现对象命名空间 CaseInsensitive=TRUE, 不可等待 obinit.c:289(file:///d:/reactos/ntoskrnl/ob/obinit.c#L289)
SymbolicLink NtCreateSymbolicLinkObject DOS 设备名 / 目录重定向 ParseProc=ObpParseSymbolicLink obinit.c:299(file:///d:/reactos/ntoskrnl/ob/obinit.c#L299)
Process NtCreateProcess, NtCreateProcessEx, NtOpenProcess 进程抽象 DeleteProc=PspDeleteProcess, SecurityRequired=TRUE, 不可 OBJ_PERMANENT psmgr.c:419(file:///d:/reactos/ntoskrnl/ps/psmgr.c#L419)
Thread NtCreateThread, NtCreateThreadEx, NtOpenThread 线程抽象 DeleteProc=PspDeleteThread psmgr.c:428(file:///d:/reactos/ntoskrnl/ps/psmgr.c#L428)
Job NtCreateJobObject, NtOpenJobObject 作业对象 DeleteProc=PspDeleteJob psmgr.c:438(file:///d:/reactos/ntoskrnl/ps/psmgr.c#L438)
File NtCreateFile, NtOpenFile 文件 I/O MaintainHandleCount=TRUE, CloseProc=IopCloseFile, ParseProc=IopParseFile, SecurityProc=IopGetSetSecurityObject, QueryNameProc=IopQueryName, UseDefaultObject=FALSE 但硬编码 DefaultObject=FILE_OBJECT.Event 偏移 iomgr.c:319(file:///d:/reactos/ntoskrnl/io/iomgr/iomgr.c#L319)
Device IoCreateDevice(驱动调用) 设备对象 ParseProc=IopParseDevice, DeleteProc=IopDeleteDevice, SecurityProc=IopGetSetSecurityObject iomgr.c:278(file:///d:/reactos/ntoskrnl/io/iomgr/iomgr.c#L278)
Driver IoCreateDriver 驱动对象 DeleteProc=IopDeleteDriver iomgr.c:289(file:///d:/reactos/ntoskrnl/io/iomgr/iomgr.c#L289)
Controller IoCreateController(内核专用) 控制器对象 --- iomgr.c:266(file:///d:/reactos/ntoskrnl/io/iomgr/iomgr.c#L266)
Adapter IoCreateAdapter(内核专用) 适配器对象 --- iomgr.c:258(file:///d:/reactos/ntoskrnl/io/iomgr/iomgr.c#L258)
IoCompletion NtCreateIoCompletion, NtOpenIoCompletion I/O 完成端口 DeleteProc=IopDeleteIoCompletion, UseDefaultObject=TRUE iomgr.c:301(file:///d:/reactos/ntoskrnl/io/iomgr/iomgr.c#L301)
Event NtCreateEvent, NtOpenEvent 事件同步 UseDefaultObject=TRUE, 无回调 event.c:55(file:///d:/reactos/ntoskrnl/ex/event.c#L55)
Semaphore NtCreateSemaphore, NtOpenSemaphore 信号量同步 UseDefaultObject=TRUE, 无回调 sem.c:59(file:///d:/reactos/ntoskrnl/ex/sem.c#L59)
Mutant NtCreateMutant, NtOpenMutant 互斥体 UseDefaultObject=TRUE mutant.c(file:///d:/reactos/ntoskrnl/ex/mutant.c)
Timer NtCreateTimer, NtOpenTimer 计时器 UseDefaultObject=TRUE timer.c:239(file:///d:/reactos/ntoskrnl/ex/timer.c#L239)
EventPair NtCreateEventPair 事件对 (快速 LPC 同步) --- evtpair.c:50(file:///d:/reactos/ntoskrnl/ex/evtpair.c#L50)
Key NtCreateKey, NtOpenKey, NtOpenKeyEx 注册表键 SecurityProc=CmpKeySecurityProcedure, 不可 OBJ_OPENLINK cmsysini.c:1029(file:///d:/reactos/ntoskrnl/config/cmsysini.c#L1029)
Section NtCreateSection, NtOpenSection 内存映射文件 ParseProc=MiSectionParseProcedure, 用于 \\KnownDlls\\xxx.dll 解析 section.c:2353(file:///d:/reactos/ntoskrnl/mm/section.c#L2353)
Port NtCreatePort, NtCreateWaitablePort LPC 端口 ParseProc=LpcpParsePortMessage port.c:58-69(file:///d:/reactos/ntoskrnl/lpc/port.c#L58)
Token NtCreateToken, NtDuplicateToken, NtOpenProcessToken 访问令牌 SecurityRequired=TRUE, DeleteProc=SepDeleteToken token.c:1669(file:///d:/reactos/ntoskrnl/se/token.c#L1669)
DebugObject DbgkCreateDebugObject 调试对象 --- dbgkobj.c:1518(file:///d:/reactos/ntoskrnl/dbgk/dbgkobj.c#L1518)
Profile (内核专用) 性能分析对象 --- profile.c:82(file:///d:/reactos/ntoskrnl/ex/profile.c#L82)

4.2.7 概念解释

术语 定义
TypeIndex / Index 每个类型对象在系统中的唯一序号(1-based)。0 号槽位留给内部使用。类型被 ObCreateObjectType 创建时分配,保存在 OBJECT_TYPE.Index 中。NtQueryObject(ObjectTypesInformation) 返回给用户态。
TypeList 存在于两个层面:(1) ObpTypeObjectType->TypeList ------ 系统所有类型对象挂在这个链表上;(2) 每个类型自己的 TypeList ------ 当 MaintainTypeList=TRUE 时,该类型的所有对象实例通过 OBJECT_HEADER_CREATOR_INFO.TypeList 挂在这个链表上,便于遍历与泄漏检测。
GENERIC_MAPPING 权限映射表。包含 4 个字段:GenericRead / GenericWrite / GenericExecute / GenericAll,分别定义当用户态请求 GENERIC_READ 等通用权限时应展开为哪些具体的类型权限位。RtlMapGenericMask(DesiredAccess, &GenericMapping) 实现此展开。
ValidAccessMask 该类型合法的访问权限位全集(含 STANDARD_RIGHTS_*SYNCHRONIZE)。创建句柄时若 DesiredAccess & ~ValidAccessMask 非零则失败。
InvalidAttributes 该类型对象创建时不允许 使用的 OBJ_* 标志位(如 OBJ_OPENLINKOBJ_PERMANENT 等)。
PoolType 对象实例分配的池类型 ------ NonPagedPool(常驻内存,可在任意 IRQL 访问)或 PagedPool(可换出,只能在 APC_LEVEL 及以下访问)。同步对象必须是 NonPagedPool。
Procedure Callbacks 类型对象内嵌的函数指针表。对象管理器在对象生命周期的关键节点调用对应回调,使子系统获得类型相关行为的控制权。本质上是「基于表的虚函数」。
Type Object(类型对象) OBJECT_TYPE 结构实例。它本身也是一种由对象管理器管理的对象 ------ 通过 ObpAllocateObject(ObpTypeObjectType) 分配,具有标准的 OBJECT_HEADER
Type Bootstrap(类型自举) 创建 ObpTypeObjectType 时的特殊逻辑:此时系统中尚不存在「类型对象」,所以 ObCreateObjectType 检测到 ObpTypeObjectType == NULL 就把它自己的 Header->Type 指向自身,完成自举。
HighWaterMark(高水位) OBJECT_TYPE 中记录的历史最大 TotalNumberOfObjectsTotalNumberOfHandles。当对象/句柄数创历史新高时更新,永不下降。

4.2.8 为什么要这样设计

1. 为什么对象类型本身也是一个对象?

  • 自描述性 :对象管理器内部的所有管理数据(类型、目录、符号链接)都用对象统一管理 ------ 分配、引用计数、安全描述符、命名空间、句柄、配额等机制对类型自身也适用。这意味着 NtOpenDirectoryObjectNtQueryObjectNtDuplicateObject 对类型对象同样有效,用户态可以以统一方式枚举/查询类型信息。
  • 生命周期统一OBJECT_TYPE 也受引用计数保护,卸载驱动时对一个未被释放的驱动对象进行 ObDereferenceObject,最终触发 DeleteProcedure(例如 IopDeleteDriver)。类型与对象共享同一套生命周期模型。
  • 命名空间统一 :所有类型都挂在 \ObjectTypes 目录下,ObReferenceObjectByName 可以按名查找某个类型对象。用户态的 NtQueryObjectObjectTypesInformation 查询全部类型。
  • 递归之美 :类型对象的类型就是自己(ObpTypeObjectType->Header->Type == ObpTypeObjectType),用很少的代码完成了整个类型系统的自举。

2. 为什么每个类型需要这么多回调函数(Open/Close/Delete/Parse/Security/QueryName/OkayToClose/Duplicate)?

这是对象管理器「类型无关的核心 + 类型相关的扩展」设计的直接产物。统一与差异的边界在哪里?

  • Open/Close :对象管理器统一管理句柄的创建与销毁,但每个类型对「句柄变化」的反应不同 ------ File 要在句柄关闭时发送 IRP_MJ_CLEANUP 释放 byte-range lock,Process 类型需要进行配额调整,Token 类型要更新审计信息。若没有这些回调,对象管理器就得硬编码对每种类型的判断,耦合度爆炸。
  • Delete :对象管理器统一释放 OBJECT_HEADER、NameInfo、QuotaInfo 等,但 FILE_OBJECT 内部还有 SectionObjectPointerFsContext、驱动分配的 IRP、缓存管理器缓存 BCB 等资源 ------ 这些只能由 IopDeleteFile 负责释放。
  • Parse :命名空间路径解析是对象管理器的核心算法,但「目录节点是否是真正的目录」只能由该类型决定。\\??\\C:\\foo.txt 解析到 \Device\HarddiskVolume1 之后,剩余的 C:\foo.txt 必须交给文件系统解析。同理 Section 对象、SymbolicLink 都有自己的内部解析语义。若没有 ParseProcedure,对象管理器就不可能「跨界」解析路径。
  • Security :注册表键的 SD 并不保存在 OBJECT_HEADER::SecurityDescriptor,而是保存在 hive 中。File 对象的 SD 在磁盘上的 NTFS 属性里。SecurityProcedure 允许类型重写安全存取,使得 NtQuerySecurityObject 对所有类型表现一致。
  • QueryName :File 对象的完整路径需要通过 FsContext 信息反向构造(因为 File 没有 NameInfo 字段)。其他类型直接返回名称。QueryNameProcedure 提供了这一层抽象。

结论:回调是保持对象管理器通用(Type-Agnostic)同时又能处理所有类型差异的唯一可行方式

3. 为什么用 TypeIndex + Procedure 表而不是 C++ 虚函数表(VTable)?

虽然在概念上非常相似(每个类型一张函数表),但内核选择了 TypeIndex + 显式表,而不是编译器生成的 VTable:

设计维度 TypeIndex + Procedure 表 C++ VTable
链接约定 显式定义,每个函数指针单独可选(可以为 NULL,表示使用默认行为) 类定义时 VTable 大小固定,必须为每个虚函数提供实现,难以表达「可选回调」
运行时动态性 ObCreateObjectType 在运行时组装 OBJECT_TYPE_INITIALIZER,同一类型的初始化器可以按配置变更(例如 File 的 MaintainHandleCount 是运行时决定的) VTable 在编译期生成,类型层级一旦确定即不可更改
字段组合 除回调外还需要池类型、配额费用、权限掩码、名称等多种配置。Procedure 表只是 Type 对象的一部分 VTable 只负责函数表,不负责静态字段(需要额外的 class 静态变量机制)
类型校验 通过 Key 四字节标签和 Index 可以快速校验句柄指向的对象是否为期望类型,不必依赖 RTTI 需要 RTTI 才能运行时判断类型,但内核不使用 C++ RTTI
ABI 稳定性 结构布局清晰,跨版本/编译器保持稳定 VTable 布局依赖编译器的 vtable 生成规则
代码量 OBJECT_TYPE_INITIALIZER 是纯数据结构,ObCreateObjectType 只需做一次 RtlCopyMemory 需要实现继承体系并引入构造/析构语义

本质区别:C++ 的 VTable 是「按类继承」的函数表,而内核的 Procedure 表是「按类型能力」的函数表 ------ 每个回调都是可选的独立能力位,按需实现。这种设计更贴近组合模式(Composition),更符合内核扩展模型。

4. 为什么要维护 TotalNumberOfObjects/HighWaterMark 统计?

  • 性能诊断 :通过 NtQueryObject(ObjectTypesInformation),用户态工具可以查询每个类型当前的对象数和句柄数,结合历史峰值快速定位异常增长。
  • 驱动泄漏检测 :如果 TotalNumberOfObjects 持续上升而 HighWaterMark 与之同步,通常暗示存在句柄泄漏或引用计数泄漏。例如 Device 对象数持续上升往往说明某驱动没有正确调用 IoDeleteDevice
  • 配额与安全:对象数量上限与进程配额共同构成了资源隔离。恶意进程若能无限创建事件对象就能耗尽系统非分页池。统计字段为配额系统提供精确计数。

4.2.9 增强子节:类型安全与 GENERIC_MAPPING

设计意图

不同类型的对象需要完全不同的权限模型。例如:

  • 对 File 类型:GENERIC_READ 应展开为「读数据、读属性、读扩展属性、同步」等。
  • 对 Process 类型:GENERIC_READ 应展开为「查询进程信息、查询虚拟内存、查询配额」等。
  • 对 Registry Key 类型:GENERIC_READ 应展开为「查询值、枚举子键、通知」等。

用户态调用者并不关心这些差异------他们只想说「我要读权限」。GENERIC_MAPPING 的作用就是在通用 API 与类型特定权限之间搭起桥梁

概念解释

GENERIC_MAPPING 定义在 sdk/include/xdk/setypes.h(file:///d:/reactos/sdk/include/xdk/setypes.h):

c 复制代码
typedef struct _GENERIC_MAPPING {
    ACCESS_MASK GenericRead;
    ACCESS_MASK GenericWrite;
    ACCESS_MASK GenericExecute;
    ACCESS_MASK GenericAll;
} GENERIC_MAPPING, *PGENERIC_MAPPING;

每个类型在 OBJECT_TYPE_INITIALIZER.GenericMapping 中填写自己的映射表。例如:

Event 类型的映射 ------ ntoskrnl/ex/event.c:20-26(file:///d:/reactos/ntoskrnl/ex/event.c#L20-L26):

c 复制代码
GENERIC_MAPPING ExpEventMapping = {
    STANDARD_RIGHTS_READ    | EVENT_QUERY_STATE,
    STANDARD_RIGHTS_WRITE   | EVENT_MODIFY_STATE,
    STANDARD_RIGHTS_EXECUTE | SYNCHRONIZE,
    EVENT_ALL_ACCESS
};

Directory 类型的映射 ------ ntoskrnl/ob/obinit.c:27-36(file:///d:/reactos/ntoskrnl/ob/obinit.c#L27-L36):

c 复制代码
GENERIC_MAPPING ObpDirectoryMapping = {
    STANDARD_RIGHTS_READ    | DIRECTORY_QUERY             | DIRECTORY_TRAVERSE,
    STANDARD_RIGHTS_WRITE   | DIRECTORY_CREATE_SUBDIRECTORY | DIRECTORY_CREATE_OBJECT,
    STANDARD_RIGHTS_EXECUTE | DIRECTORY_QUERY             | DIRECTORY_TRAVERSE,
    DIRECTORY_ALL_ACCESS
};

RtlMapGenericMask 的作用

当用户态以 GENERIC_READ | GENERIC_WRITE 作为 DesiredAccess 打开对象时,安全系统调用:

c 复制代码
VOID NTAPI RtlMapGenericMask(PACCESS_MASK DesiredAccess,
                             PGENERIC_MAPPING GenericMapping)
{
    if (*DesiredAccess & GENERIC_READ)    *DesiredAccess |= GenericMapping->GenericRead;
    if (*DesiredAccess & GENERIC_WRITE)   *DesiredAccess |= GenericMapping->GenericWrite;
    if (*DesiredAccess & GENERIC_EXECUTE) *DesiredAccess |= GenericMapping->GenericExecute;
    if (*DesiredAccess & GENERIC_ALL)     *DesiredAccess |= GenericMapping->GenericAll;
    *DesiredAccess &= ~GENERIC_ACCESS;   /* 清除 GENERIC_* 位 */
}

然后把映射后的具体权限位与该类型的 ValidAccessMask 比较,如果存在未定义位则拒绝访问。这一步在 ObCheckObjectAccess 中完成。

为什么这样设计

  • API 通用性NtOpenProcess(..., GENERIC_READ, ...)NtCreateFile(..., GENERIC_READ, ...) 语法完全一致,调用者不必区分 File 和 Process。
  • 权限粒度精确:每个类型可以独立定义其权限粒度(File 有 FILE_READ_DATA / FILE_READ_ATTRIBUTES 等 20 多种权限,Process 有 PROCESS_QUERY_INFORMATION / PROCESS_VM_READ 等 10 多种权限)。
  • 安全可控:展开后的权限位会被安全描述符中的 ACE 逐一匹配,确保只有被显式允许的权限才能生效。
  • 继承性 :SD 中的 ACE 本身也可使用 GENERIC_* 权限(由 RtlCreateSecurityDescriptor 时的 RtlMapGenericMask 预先展开),使得 ACL 在不同类型上具有语义一致性。

4.2.10 增强子节:命名对象 vs 匿名对象

设计意图

对象在命名空间中的可见性决定了其跨进程可见性安全模型 。可命名的对象(Event、Mutex、Semaphore、Section、File、Directory、Key 等)允许通过名称在全局或 session 级共享,匿名对象只能通过句柄复制(NtDuplicateObject)或继承传递给其他进程。

概念解释

命名对象
  • 创建方式 :调用 NtCreateEvent / NtCreateMutex 等 API 时在 OBJECT_ATTRIBUTES.ObjectName 中提供完整路径(例如 \BaseNamedObjects\MySharedEvent\Sessions\1\BaseNamedObjects\SessionLocalEvent)。
  • 内部机制ObCreateObject 会为对象分配 OBJECT_HEADER_NAME_INFO 结构,记录名称与所属目录(Directory 字段)。然后通过 ObpInsertEntryDirectory 把对象插入到父目录的哈希桶中。
  • 打开方式NtOpenEvent(L"\\BaseNamedObjects\\MySharedEvent") 触发 ObReferenceObjectByName → 目录解析 → 哈希查找 → 引用对象并返回句柄。
匿名对象
  • 创建方式ObjectName = NULL,对象没有 OBJECT_HEADER_NAME_INFO
  • 典型场景:进程内部使用的事件/信号量(用于线程间同步)、临时 IOCP、管道服务端句柄等。
  • 句柄传递 :匿名对象只能通过以下方式在进程间共享:
    1. 句柄继承 :子进程通过 STARTUPINFO.hStdInput/Output/Error 继承父进程的句柄(需要 OBJ_INHERIT)。
    2. NtDuplicateObject:在进程间复制句柄(RPC/LPC 场景常用)。
    3. 驱动内部共享 :驱动在 DeviceExtension 中保存对象指针,跨请求共享。
命名空间中的对象数量与安全
  • 目录哈希表OBJECT_DIRECTORY 使用 NUMBER_HASH_BUCKETS = 37 个哈希桶,链式解决冲突。
  • 命名对象的 ACL :命名对象必须具有安全描述符,否则谁都能打开。默认由创建者进程的 token 和父目录的 inherited SD 决定。这正是「命名对象存在安全风险」的根本原因------攻击者若能预测命名(如 Global\\MyAppEvent),就可以尝试以弱 ACL 打开。
  • Session 隔离 :从 Vista 开始,\BaseNamedObjects\ 在每个 Session 中是独立的(实际上重定向到 \Sessions\<id>\BaseNamedObjects),防止 Session 0 与交互式 Session 互相干扰。

为什么 NtCreateEventName 参数是可选的?

  • 线程间同步 ,命名既无意义也无必要。匿名 Event 只需分配 sizeof(KEVENT) + OBJECT_HEADER,无需目录查找、哈希插入、名称空间分配,速度更快。
  • 跨进程同步 (如互斥体、共享内存 Section),命名是唯一可行的发现机制------两个互不相关的进程必须通过名称「约定地点」。
  • 这种「可选名称」设计体现了「按需分配」的理念------不强制对象进入命名空间,避免不必要的开销与暴露。

4.2.11 增强子节:对象类型的统计信息与性能监控

设计意图

内核对象数量与句柄数量是衡量系统健康度的核心指标。对象管理器在每个类型对象上维护一套实时统计信息,供驱动作者、系统管理员与性能诊断工具使用。

概念解释

1. 对象计数与句柄计数
字段 含义 维护位置
TotalNumberOfObjects 当前存活的该类型对象实例数 ObCreateObject 中递增,ObpDeleteObject 中递减
TotalNumberOfHandles 当前打开的该类型句柄总数 ObpIncrementHandleCount 中递增,ObpDecrementHandleCount 中递减
HighWaterNumberOfObjects 历史最大 TotalNumberOfObjects 仅在对象数超过当前峰值时更新,永不回退
HighWaterNumberOfHandles 历史最大 TotalNumberOfHandles 同上
2. Pool 配额与对象计数的区别
  • Pool QuotaOBJECT_HEADER_QUOTA_INFO::PagedPoolCharge / NonPagedPoolCharge)是进程视角的统计:该进程一共「占用了」多少池内存。当进程创建对象时,对象管理器把配额记入该进程。
  • TotalNumberOfObjects类型视角的统计:系统中该类型一共有多少对象存活。不受进程边界限制。

两者的区别可以类比为:每个家庭的电费账单 vs 全市电表总数

3. 高水位 (HighWaterMark) 的意义

HighWaterMark 有两个重要用途:

  • 泄漏检测 :如果 File 类型的 TotalNumberOfObjectsHighWaterNumberOfObjects 持续同步增长,且在空闲期不回落,暗示某个驱动在 IRP_MJ_CLOSE 中没有正确解引用文件对象。
  • 容量规划:通过历史峰值,驱动开发者可以推断「在最高峰时系统需要分配多少非分页池给 Event 对象」,从而在驱动初始化时预留足够的 lookaside list 深度。
4. OBJECT_TYPE_INFORMATION ------ 用户态查询结构

用户态通过 NtQueryObject(NULL, ObjectTypesInformation, ...) 可以查询全部类型的统计信息,结构定义在

sdk/include/ndk/obtypes.h:285-308(file:///d:/reactos/sdk/include/ndk/obtypes.h#L285-L308):

c 复制代码
typedef struct _OBJECT_TYPE_INFORMATION {
    UNICODE_STRING TypeName;
    ULONG TotalNumberOfObjects;
    ULONG TotalNumberOfHandles;
    ULONG TotalPagedPoolUsage;
    ULONG TotalNonPagedPoolUsage;
    ULONG TotalNamePoolUsage;
    ULONG TotalHandleTableUsage;
    ULONG HighWaterNumberOfObjects;
    ULONG HighWaterNumberOfHandles;
    ULONG HighWaterPagedPoolUsage;
    ULONG HighWaterNonPagedPoolUsage;
    ULONG HighWaterNamePoolUsage;
    ULONG HighWaterHandleTableUsage;
    ULONG InvalidAttributes;
    GENERIC_MAPPING GenericMapping;
    ULONG ValidAccessMask;
    BOOLEAN SecurityRequired;
    BOOLEAN MaintainHandleCount;
    ULONG PoolType;
    ULONG DefaultPagedPoolCharge;
    ULONG DefaultNonPagedPoolCharge;
} OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;

TotalPagedPoolUsage 等字段即 TotalNumberOfObjects × DefaultPagedPoolCharge 的聚合值,用来快速估算该类型在池中所占内存。


4.2.12 小结(增强版)

本节覆盖的核心知识点

  1. 对象类型是对象的「模板 + 行为表」:决定对象体大小、池位置、配额费用、权限模型、回调行为。
  2. OBJECT_TYPE 本身也是一个对象 :由 ObpTypeObjectType(自举类型)统一管理。
  3. ObInitSystem 分阶段初始化 :先创建 Type/Directory/SymbolicLink 三种基础类型,再在 Phase 1 中创建 \ObjectTypes 目录并把所有类型挂入。
  4. ObCreateObjectType 负责类型注册 :校验参数 → 分配 OBJECT_TYPE → 复制 OBJECT_TYPE_INITIALIZER → 计算 Key → 分配 Index → 挂入全局 TypeList。
  5. Procedure 回调机制:Open/Close/Delete/Parse/Security/QueryName/OkayToClose 让对象管理器保持通用,由各子系统实现类型特定行为。
  6. 可等待对象与 DefaultObject :通过 OBJECT_TYPE.DefaultObject 字段提供 DISPATCHER_HEADER 的获取方式,实现统一的 KeWaitForSingleObject 语义。
  7. GENERIC_MAPPING:搭起通用 API 与类型特定权限之间的桥梁。
  8. 命名对象 vs 匿名对象:命名对象可跨进程发现,匿名对象只能通过句柄复制/继承。
  9. 统计与监控:TotalNumberOfObjects / HighWaterMark / Pool Quota 三套独立的计数体系。

类型 × 回调 × API 对照表

类型 关键回调 典型用户态 API
Process DeleteProc=PspDeleteProcess NtCreateProcessEx / NtOpenProcess / NtTerminateProcess
Thread DeleteProc=PspDeleteThread NtCreateThreadEx / NtOpenThread / NtTerminateThread
File CloseProc=IopCloseFile ParseProc=IopParseFile SecurityProc=IopGetSetSecurityObject QueryNameProc=IopQueryName NtCreateFile / NtOpenFile / NtReadFile / NtWriteFile
Device ParseProc=IopParseDevice DeleteProc=IopDeleteDevice 驱动调用 IoCreateDevice;用户态通过 File 创建间接使用
Event (无回调,纯同步对象) NtCreateEvent / NtOpenEvent / NtSetEvent
Semaphore (无回调) NtCreateSemaphore / NtReleaseSemaphore
Mutant (无回调) NtCreateMutant / NtReleaseMutant
Timer (无回调) NtCreateTimer / NtSetTimer
Section ParseProc=MiSectionParseProcedure NtCreateSection / NtMapViewOfSection
Key SecurityProc=CmpKeySecurityProcedure NtCreateKey / NtOpenKey / NtQueryValueKey
SymbolicLink ParseProc=ObpParseSymbolicLink DeleteProc=ObpDeleteSymbolicLink NtCreateSymbolicLinkObject
Directory (无回调) NtCreateDirectoryObject
Token DeleteProc=SepDeleteToken NtOpenProcessToken / NtAdjustPrivilegesToken
IoCompletion DeleteProc=IopDeleteIoCompletion NtCreateIoCompletion / NtSetIoCompletion

下一步学习路径

  • 4.3 句柄表 :对象创建后如何通过句柄在用户态被引用?HANDLE 如何被翻译为内核对象指针?进程句柄表的多层扩展结构、句柄继承、ObReferenceObjectByHandle 的具体实现。
  • 4.4 对象命名空间NtCreateFile(L"\\??\\C:\\foo.txt") 这样一个路径如何被 ObpLookupObjectName 解析?目录哈希、符号链接重定向、DosDevices 映射、Device 栈解析。
  • 4.5 引用计数与生命周期ObReferenceObject / ObDereferenceObject / ObpDeleteObject 形成的状态机;OBJECT_HEADER_CREATOR_INFONameInfoHandleInfoQuotaInfo 等可选附加结构如何按需分配。
  • 5.x 各子系统对象模型:深入 Process/Thread、File/Device、Section、Registry Key 等具体类型对象的创建/打开/关闭流程。
相关推荐
Ztopcloud极拓云视角13 小时前
ChatGPT超级应用改版技术解析:Codex集成架构与多模型路由实战
人工智能·chatgpt·架构
玖玥拾20 小时前
C/C++ 基础笔记(十四)多态与模板编程
c语言·c++·多态·模板
许彰午21 小时前
30_Java Stream流操作全解
java·windows·python
逻极1 天前
Hermes Agent深度探索:一个会自我沉淀经验的终端智能体
架构·llm·agent·rag·多智能体系统·hermes agent·hermes
数智顾问1 天前
(151页PPT)XX集团信息化整体架构规划及ERP方案建议书(附下载方式)
大数据·架构
caimouse1 天前
Reactos 第1章 概述
c语言·开发语言·架构
星间都市山脉1 天前
Android STS(Security Test Suite)完整介绍与测试流程
android·java·linux·windows·ubuntu·android studio·androidx
namexingyun1 天前
拆解Fable 5三重安全护栏:模型路由、蒸馏防护与生物安全分类器的技术原理 - 微元算力(weytoken)
java·人工智能·python·安全·架构·ai编程
小短腿的代码世界1 天前
行情快照与增量更新引擎:Qt在高频交易数据分发中的核心架构——你的行情推送为什么延迟了500ms?
开发语言·qt·架构
上海云盾第一敬业销售1 天前
高效阻止网站攻击的WAF防护架构解析
web安全·架构·ddos