现在的位置: 首页 > 综合 > 正文

内核对象与内核对象的句柄——2013年12月20日(学习总结)

2013年01月03日 ⁄ 综合 ⁄ 共 3636字 ⁄ 字号 评论关闭

内核对象分类:

        作为一个Windows软件开发人员,你经常需要创建、打开和操作各种内核对象。系统要创建和操作若干类型的内核对象,比如存取符号对象、事件对象、文件对象、文件映射对象、I/O完成端口对象、作业对象、信箱对象、互斥对象、管道对象、进程对象、信标对象、线程对象和等待计时器对象等。这些对象都是通过调用函数来创建的。例如,CreateFileMapping函数可使系统能够创建一个文件映射对象。每个内核对象只是内核分配的一个内存块,并且只能由该内核访问。该内存块是一种数据结构,它的成员负责维护该对象的各种信息。有些数据成员(如安全性描述符、使用计数等)在所有对象类型中是相同的,但大多数数据成员属于特定的对象类型。例如,进程对象有一个进程ID、一个基本优先级和一个退出代码,而文件对象则拥有一个字节位移、一个共享模式和一个打开模式。

        除了内核对象外,你的应用程序也可以使用其他类型的对象,如菜单、窗口、鼠标光标(这些属于用户对象)、画笔、刷子和字体(这些对象属于图形设备接口(GDI)对象)等。这些对象都不属于内核对象。

句柄表:保存了各个内核对象的句柄。句柄就好比指针,指向属于它的内核对象,或者说:句柄就是某内核对象的一个“标识”或"ID"。

        每个内核对象都是内核(操作系统)分配的一个内存块,并且只能由内核去访问它。该内存块是一种数据结构,它的成员负责维护对象的各种信息(eg: 安全性描述,计数器等)。安全描述符描述了谁创建了该对象;以及,哪些组和用户被允许访问或使用此对象;哪些组和用户被拒绝访问此对象。安全描述符通常在编写服务器应用程序的时候使用。计数器表示内核对象被引用的次数。如果想要访问这些内核对象,必须通过windows为我们提供的特有API来实现间接访问,比如,通过内核对象句柄,就可以间接访问这些内核对象。此外,句柄是一种资源,如果我们不再使用它,就需要释放句柄。CloseHandel(Handle
hobj)方法可用于释放句柄资源。如果不及时释放,就有可能出现句柄泄漏的现象。

什么是句柄泄漏或内核对象泄漏?为了讲明白,首先谈一下内存泄漏的概念(其实它们道理是相似的):

内存泄漏定义:

       一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显式释放的内存。应用程序一般使用malloc,realloc,new等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。

浅析句柄泄漏或内核对象泄漏:

     Windows提供一组函数用于创建和操作内核对象。调用一个创建内核对象的函数时,函数会返回一个句柄,该句柄标识了这个内核对象,这个句柄可由当前进程中的所有线程调用,也可以通过跨进程边界共享内核对象(下面会谈到内核共享的问题),让其他的进程调用。句柄每被调用一次,它引用的内核对象的计数器就会加1。当执行CloseHandle方法后,会释放句柄资源,同时内核对象的引用计数也会减1,如果引用计数为0,那么内核就会删除该内核对象。如果某句柄不再使用,且未执行CloseHandle方法将句柄资源释放,那么该句柄在句柄表中的位置就不能被再次使用,这就出现了句柄泄漏的问题。同时,它的内核对象也就不会被内核删除掉,也即意味着之前内核为它分配的内存块将不能被再次使用,这势必也就导致了内核对象泄漏的问题。随着句柄表中的句柄累增,相应的内核对象也会累增,内核对象便会占用较大的内存空间,这不是我们所希望出现的结果。然而,值得庆幸的是,操作系统设计者们早已考虑到该问题,这些句柄资源,虽未被程序设计者及时释放,但在进程或线程资源退出的时候,操作系统会自动帮我们销毁(尽管如此,程序设计者在开发应用程序时,也要注意句柄或内核对象泄漏的问题,以免造成资源开销的浪费)。

     操作系统为每一个进程创建了一个句柄表,它负责存储进程中所有内核对象的句柄。当调用关闭内核对象的函数Closehandle时,引用该内核对象的句柄会被清除,当进程退出时句柄表中的所有记录也都会被清除,相应引用的内核对象的使用计数也都会减一,当使用计数为0时,内核对象就会被内核自动销毁。就算没有手动关闭内核对象,当进程完全退出时,由于此时内核对象没有被任何进程或线程所使用,内核对象的引用计数就变成了零,一旦为零,内核就会自动将其销毁的。

内核对象的共享:

三种不同的机制来允许进程共享内核对象:通过继承或复制对象句柄的方式实现对象的共享,创建命名对象。
1、使用对象句柄继承
设置可继承标志为1。对象句柄的继承只会在生成子进程的时候发生。
程序初始化时会为父进程创建一个进程句柄表,使用对象句柄继承,系统会为子进程创建一个新的、空白的进程句柄表——就像它为任何一个新进程所做的那样。系统会遍历父进程的句柄表,对它的每一个记录项进行检查。凡是包含一个有效的“可继承的句柄”的项,都会被完整地拷贝到子进程的句柄表中。在子进程的句柄表中,拷贝项的位置与它在父进程句柄表中的位置是完全一样的。这是非常重要的一个设计,因为它意味着:在父进程和子进程中,对一个内核对象进行标识的句柄值是完全一样的。内核对象的使用计数将递增。
2、改变句柄的标志
 父进程创建了一个内核对象,得到了一个可继承的句柄,然后生成了两个子进程。但是,父进程只希望其中的一个子进程继承内核对象句柄。调用SetHandleInformation(

_In_ HANDLE hObject,//第一个参数hObject标识了一个有效句柄。
_In_ DWORD dwMask,//第二个参数dwMask指定想更改哪个标志:
_In_ DWORD dwFlags//第三个参数dsFlags想把标志改为什么(true or false)

)函数来改变内核对象句柄的继承标志。比如将句柄继承标志HANDLE_FLAG_INHERIT设为TRUE,表示它可被继承,再用CreateProcess(bInheritHandle设为TRUE,表示有继承的权利)创建出来的子进程就可以继承内核对象句柄。如果在创建子进程时,把blnheritHandle设为false的话,则该子进程不能继承内核对象句柄。

3、创建命名对象来共享内核对象

Process A 启动运行,并调用下面的函数:

HANDLE hMutexPronessA = CreateMutex(NULL, FALSE, "JeffMutex");

另一进程ProcessB(不一定是Process A 的子进程)启动运行时,执行下面的代码:

HANDLE hMutexProcessB = CreateMutex(NULL, FALSE, "JeffMutex");

当Process B调用CreateMutex时,系统首先要查看是否已经存在一个名字为“JeffMutex ”的内核对象。

由于确实存在一个带有该名字的对象,因此内核要检查对象的类型。由于试图创建一个互斥对象,而名字为“JeffMutex ”的对象也是个互斥对象,

因此系统会执行一次安全检查,以确定调用者是否拥有对该对象的完整的访问权。

如果拥有这种访问权,系统就在ProcessB的句柄表中找出一个空项目,并对该项目进行初始化,使该项目指向现有的内核对象。

如果该对象类型不匹配,或者调用者被拒绝访问,那么CreateMutex 将运行失败(返回NULL)。

当Process B 对CreateMutex的调用取得成功时,它并不实际创建一个互斥对象。相反,Process B 只是被赋予一个与进程相关的句柄值,用于标识内核中现有的互斥对象。当然,由于Process B 的句柄表中的一个新项目要引用该对象,互斥对象的使用计数就会递增。在Process A和Process
B 同时关闭它们的对象句柄之前,该对象是不会被撤消的。请注意,这两个进程中的句柄值很可能是不同的值。这是可以的。Process A 将使用它的句柄值,而Process B 则使用它自己的句柄值来操作一个互斥内核对象。


4、复制对象句柄
为了跨越进程边界来共享内核对象,最后一个技术是使用DuplicateHandle函数。

这个函数获得一个进程的句柄表中的一个记录项,然后在另一个进程的句柄表中创建这个记录项的一个拷贝。


线程对象与线程:

createThread(...)创建完线程对象之后,线程对象会把线程工作上下文注册到线程处理系统中,注册完之后,它的使命就算完成了。线程具体要完成的工作任务将交由线程处理系统去统一维护和管理。这时,如果其他进程不再使用该线程对象,就应该把线程对象句柄关闭,释放线程句柄资源,同时内核会销毁线程对象。值得注意的是,虽然线程对象被销毁了,但线程本身并不受影响,仍然可执行任务。整个创建线程对象的过程,线程对象只完成了注册事件的功能。

抱歉!评论已关闭.