句柄表、对象管理器:Windows是如何管住所有系统资源的?
引言
获课:999it.top/15452/
你有没有想过一个问题:当你同时打开十几个文件、跑着几十个进程、每个进程又创建了无数线程和事件——Windows是怎么把这些“乱糟糟”的资源管理得井井有条的?
答案是两个字:对象。
在Windows的世界里,一切皆是对象。文件是对象,进程是对象,线程是对象,事件、信号量、互斥体、注册表项……统统都是对象。这些对象散布在内核的各个角落,如果让每个开发者直接操作它们,系统早就乱套了。
所以Windows设计了两套精妙的机制来管住这些资源:一套叫对象管理器,负责全局统筹;一套叫句柄表,负责进程级的资源索引。今天,我们就来拆解这对“黄金搭档”的底层逻辑。
一、对象管理器:Windows的“资源大管家”
对象管理器是Windows执行体(Executive)中的一个核心子系统,它的职责简单粗暴:管理所有系统资源。无论是物理设备、文件目录、正在运行的进程,还是同步用的各种内核对象,都要经过它的“手”。
对象长什么样?
每个被管理的对象,都包含两部分:
- 对象头:由对象管理器统一维护的元数据,包括对象名、安全描述符、打开句柄计数、引用计数等。
- 对象体:对象专属的数据和服务,不同的对象类型体部结构完全不同。
这种设计的好处是:通用操作统一处理,专有操作各自实现。比如关闭句柄、复制句柄、查询安全信息这些通用操作,对象管理器提供标准接口;而像文件读写这种专有操作,则由具体的对象类型(如文件对象)自己实现。
对象名字空间:树形结构的全局目录
对象管理器维护着一个类似文件系统的树形结构名字空间。根目录是反斜杠“\”,中间节点是对象目录(也是对象),叶节点是具体的对象实例。
常用的预定义目录包括:
\??:Win32设备名(符号链接)\BaseNamedObjects:互斥体、事件、信号量等同步对象\Device:设备对象\ObjectTypes:所有对象类型对象
你可以用WinObj工具(Sysinternals提供)直观地查看这个树形结构,会发现整个Windows资源世界被组织得整整齐齐。
对象的生命周期管理
对象管理器通过引用计数来控制对象的生命。每个对象头里有一个PointerCount和一个HandleCount:
- 当进程打开或引用对象时,引用计数+1
- 当进程关闭句柄或解除引用时,引用计数-1
- 当引用计数归零时,对象被释放
这套机制保证了:只要还有人用,对象就活着;没人用了,立刻回收。不会出现“空悬指针”或“内存泄漏”的尴尬。
二、句柄表:进程访问对象的“通行证”
对象在内核里待得好好的,用户态进程怎么访问它?直接给地址?不行,太危险——用户态程序不能直接操作内核内存。
于是有了句柄。句柄是一个进程内唯一的索引值,指向进程自己的句柄表中的一项,该项里存储着真正指向内核对象的指针。
句柄表长什么样?
每个进程都有一个句柄表,入口在EPROCESS结构的ObjectTable字段。句柄表的核心数据结构是HANDLE_TABLE,其中的TableCode是关键:
- 指向句柄表的存储结构
- 低2位表示表的层级(0=一层,1=两层,2=三层)
为什么是多层?为了可扩展。Windows采用类似页表的多级结构:
- 一层表:最多512个句柄
- 二层表:最多512×1024个句柄
- 三层表:最多512×1024×1024个句柄(约5.6亿)
当然,实际限制是16,777,216(2^24)个句柄/进程,已经足够用了。
句柄表项里有什么?
每个HANDLE_TABLE_ENTRY包含两个关键信息:
- Object指针:指向内核对象的地址(低3位有特殊含义,比如是否可继承、是否允许关闭)
- GrantedAccess:该句柄对这个对象的访问权限
重点来了:访问权限检查的结果被缓存在句柄表里。当进程首次打开一个对象时,对象管理器会根据进程令牌和对象安全描述符做一次完整的权限检查,然后把结果(允许的权限)存到句柄表项的GrantedAccess中。之后该进程再通过这个句柄访问对象,直接查表即可,无需重复检查。这极大地提升了系统性能。
句柄的创建流程
以创建一个文件对象为例:
- 进程调用
CreateFile - 对象管理器在进程句柄表中找到一个空闲表项
- 内核创建一个文件对象实例(分配内存、初始化)
- 将对象指针填入句柄表项,设置访问权限
- 返回句柄表项的索引给用户态——这就是我们熟悉的HANDLE值
值得注意的是,句柄值通常是4的倍数(0x4、0x8、0xc……),因为句柄表项大小是8字节,索引需要对齐。
句柄的继承与共享
句柄是进程私有的,一个进程的句柄值传给另一个进程毫无意义。但如果需要共享对象怎么办?Windows提供了三种机制:
- 继承:父进程创建可继承句柄,子进程创建时指定继承标志,系统会把父进程的可继承句柄项复制到子进程句柄表中
- 命名对象:像互斥体、事件、信号量这类对象可以指定名字,其他进程用同一名字打开,获得不同的句柄,但指向同一个内核对象
- DuplicateHandle:将一个进程的句柄复制到另一个进程(需要目标进程句柄)
三、黄金搭档如何协同工作?
对象管理器和句柄表的分工很明确:
- 对象管理器:管对象的存在(创建、销毁、命名、全局可见性)
- 句柄表:管进程对对象的访问(索引、权限、进程级可见性)
举个例子,一个进程要访问一个命名事件对象:
- 调用
CreateEvent(NULL, FALSE, FALSE, "Global\\MyEvent") - 对象管理器在
\BaseNamedObjects目录下查找或创建该事件对象 - 创建对象头,设置引用计数=1
- 在进程句柄表中分配表项,填入对象指针和访问权限
- 返回句柄值给调用者
另一个进程用同一名字打开时:
- 对象管理器找到已存在的对象
- 对象引用计数+1(现在=2)
- 在新进程的句柄表中分配新表项,指向同一对象
- 返回新的句柄值(两个进程的句柄值可能完全不同,但指向同一个内核对象)
当第一个进程关闭句柄:
- 从该进程句柄表中移除表项
- 对象引用计数-1(现在=1)
- 对象仍然存活(因为第二个进程还在用)
当第二个进程也关闭:
- 对象引用计数-1(现在=0)
- 对象管理器回收对象内存
你看,整个过程行云流水:对象管理器管全局生命周期,句柄表管进程级访问,两者通过引用计数协同。
四、为什么这套设计很牛?
- 统一性:所有资源都用同一套机制管理,无论是文件还是同步对象,创建、关闭、共享的流程高度一致
- 安全性:访问权限检查集中做、缓存用,每次访问不再重复检查,但又不牺牲安全
- 隔离性:进程通过句柄访问对象,不能直接操作内核内存;句柄表是进程私有的,天然实现资源隔离
- 可扩展性:多级句柄表设计,从小到大的进程都能高效运行
- 可调试性:可以用WinDbg的
!process、!handle命令实时查看句柄表内容
总结
对象管理器和句柄表,是Windows内核资源管理的“双子星”。
对象管理器站在全局视角,把物理设备、文件、进程、同步对象等一切资源抽象为“对象”,用统一的规则管理它们的创建、命名、安全和生命周期。它维护的树形名字空间,让整个系统的资源变得可查找、可访问。
句柄表则站在进程视角,为每个进程维护一张私有的资源索引表。用户态程序只跟小小的整数(句柄)打交道,背后是句柄表到内核对象的映射。权限检查结果缓存在表项里,让后续访问高效无比。
两者通过引用计数协同工作:进程打开对象,句柄表加一项,对象引用计数加一;进程关闭句柄,引用计数减一;归零时对象管理器回收资源。这套机制简洁而强大,撑起了Windows资源管理的整片天空。
下次你再打开一个文件、创建一个线程、或者用互斥体做同步时,不妨想想——背后这对“黄金搭档”正在默默为你工作。
你在Windows开发或逆向中遇到过句柄相关的有趣问题吗?评论区聊聊,我们一起探讨。












评论(0)