[经验]由OpenProcess的返回值引出“句柄表”
//redice 2008.7.20//redice@
晚上做了如下的一个小实验,试验结果让我大吃一惊。也就有了这篇日志。
#include <windows.h>
int main()
{
DWORD dwPID=2096;
HANDLE hProcess=NULL;
//根据PID获取进程句柄
hProcess=OpenProcess(PROCESS_ALL_ACCESS,TRUE,dwPID);
printf("process handle:%p\n",hProcess);
CloseHandle(hProcess);
return 0;
}
根据任务管理器中显示的PID,我测试了好几个进程(修改dwPID的初始值),实验的结果让我大吃一惊。
因为显示的结果竟然都是一样的。这太奇怪了。
试验截图如下:
因为按我的理解:"进程句柄是进程除PID外的另一个标示,它是一个指向进程内存首地址的指针"。这样不同的进程句柄肯定应该不同呀??怎么实验的结果会一样呢??
"我不知,百度知",在网上查了一下,没有找到类似的问题,但是看到了一个叫做"句柄表"的东西。
认真看过这个东西的介绍后,我找到了问题的所在。原来我在认识上一直存在一个误区。我们在API中常用的这
些“进程句柄”其实不是真正的进程句柄,而是进程句柄在“句柄表”中的索引(偏移),真正的进程句柄保存在进程句柄表中。
下面是关于“句柄表”的解释:
进程在被初始化时,系统为其分配一个句柄表,此句柄表只用于内核对象。
句柄表的每一项记录了每个内核对象的信息,所谓信息主要包括:(句柄在句柄表表中的)索引、指向内核对象内存的地址、访问屏蔽标志、内核对象句柄继承标志。
进程在初始化时句柄表是空的,每当进程创建一个内核对象时,系统就在该进程的句柄表中找出一个空项,设置新创建的内核对象的信息。
内核对象的创建函数几乎都是形如Cerate*的形式,这些函数返回新创建的内核对象的句柄。这里返回的所谓句柄,其值实际上是该内核对象在进程句柄表中的索引,用于标识该对象在进程句柄表中的位置。
Cerate*函数返回失败的话,返回值通常为NULL(0),但也有一些是INVALID_HANDLE_VALUE(-1)。
关闭内核对象通过调用CloseHandle完成,每调用一次,内核对象使用计数递减1。
内核对象句柄的继承性
只用当进程具有父子关系时,才能使用内核对象的句柄的继承性。在父进程创建内核对象,并对SECURITY_ATTRIBUTES的数据成员BOOL bInheritHandle设为FALSE时,则子进程不能继承父进程的内核对象的句柄。
但若设置了TRUE,则子进程可以继承父进程的内核对象的句柄值。当子进程创建初始化时,系统也会为其分配句柄表,然后系统扫描父进程句柄表,对于继承标志位为TRUE的内核对象,系统把该项目拷贝到子进程的句柄表,并且拷贝到子进程句柄表中的位置和父进程句柄表中的位置是一样的。这就意味在父子进程中,此内核对象的句柄值是一样的,所以能够保证父子进程都能使用该内核对象。
最后再来分析一下上面试验的结果:
每次运行这个程序,系统都为它分配新的“句柄表”。执行OpenProcess时,系统会在“句柄表”增加一项,并把这一项的索引值返回。因为每次试验在main函数中我们都只创建了一个内核对象,因此“句柄表”每一次都是增加了一项,返回的索引也理应一样了。
再做如下验证:两次打开同一进程,返回的值应该不一样,因为这次"句柄表"增加了两项内容。其中前一项和上面的试验的值应该相同。
#include <windows.h>
int main()
{
DWORD dwPID=2096;
HANDLE hProcess=NULL;
//打开进程
hProcess=OpenProcess(PROCESS_ALL_ACCESS,TRUE,dwPID);
printf("process handle:%p\n",hProcess);
//打开进程
hProcess=OpenProcess(PROCESS_ALL_ACCESS,TRUE,dwPID);
printf("process handle:%p\n",hProcess);
CloseHandle(hProcess);
return 0;
}
试验截图如下(和我们的推测吻合了)