Windows 内核对象(Kernel Object)笔记

1. 什么是内核对象

Windows 内核对象是操作系统内核分配的一块内存块,存放着用于管理该对象的数据结构。Windows 内核对象的所有者是 Windows 操作系统内核,而非进程。应用程序无法在内存中直接定位或修改这些数据结构,只能通过 Windows 提供的 API 来操作。

  • 每个内核对象的数据结构包含一些公共成员(如安全描述符、引用计数),以及大量不同类型对象特有的成员。
  • 内核对象位于操作系统内核地址空间,由内核负责管理生命周期。

2. 内核对象的身份标识:句柄(Handle)

当调用一个创建内核对象的函数(如 CreateThreadCreateFile)并成功返回后,系统会返回一个句柄。句柄是一个与进程相关的、用于唯一标识该内核对象的数值:

  • 32 位进程中:32 位值
  • 64 位进程中:64 位值

有了句柄,进程中的任何线程都可以通过它调用相应的 API 来操作对应的内核对象。

3. 如何区分内核对象与非内核对象

Windows 中除了内核对象,还有用户对象(如窗口、菜单)和 GDI 对象(如字体、位图)。一个简单有效的区分方式是:

几乎所有创建内核对象的函数,都带有一个 PSECURITY_ATTRIBUTES 类型的参数,用于指定安全属性。

4. 进程句柄表

每个进程都维护着一张句柄表(Handle Table),这张表只用于记录该进程所持有的内核对象句柄。当函数返回一个句柄时,实际上是在当前进程的句柄表中新增了一项,指向对应的内核对象。

5. 内核对象的生命周期与引用计数

5.1 引用计数的基本规则

内核对象由内核管理,其生命周期由**引用计数(Reference Count)**决定。当一个内核对象的引用计数降为 0 时,内核会销毁该对象,释放其占用的内存。

5.2 创建与句柄返回时的计数变化

CreateThread 为例:

  1. 创建内核对象:内核在地址空间中分配并初始化该对象的数据结构,此时引用计数被初始化为 1

    这个 1 代表该对象自身处于“活跃/有效”状态,是内核对该对象的基础引用,并非因为任何进程打开它。

  2. 返回句柄给调用进程:函数将句柄返回给调用进程,相当于当前进程“打开”或“访问”了该对象,系统会在进程的句柄表中新增一项,并将内核对象的引用计数 +1

因此,创建并返回句柄后,引用计数变为 2

5.3 关闭句柄时的计数变化

调用 CloseHandle(hThread) 时:

  • 系统通过句柄值查找进程句柄表,找到对应的表项并将其标记为空闲。
  • 内核对象的引用计数 -1(从 2 变为 1)。
  • 此时该句柄与内核对象之间的关联已解除,不能再通过该句柄访问内核对象。

5.4 内核对象最终何时销毁

接上述例子,引用计数变为 1 后,内核对象不会被销毁,因为那个基础引用(代表对象活跃)仍然存在。只有当线程结束运行时:

  • 系统会移除线程对象的基础引用,引用计数再 -1(从 1 变为 0)。
  • 内核对象被销毁,内存被释放。

注意:虽然我们已经关闭了线程句柄,句柄与内核对象已无关系,但线程对象本身仍然与正在运行的线程相关联。系统正是通过线程 ID 来追踪和管理这个关系,并不依赖句柄。