windows核心编程--内核对象和句柄泄漏
1. 什么是内核对象?
内核对象是操作系统分配的一个内存块,该内存块是一个数据结构,用于管理对象的各种信息。
当应用程序要与系统设备进行交互的时候,将使用到内核对象,出于安全的考虑,进程是不能直接访问内核对象的,操作系统提供了对应的函数来对它们进行访问。
存取符号对象、事件对象、文件对象、作业对象、互斥对象、管道对象、等待计时器对象等都是内核对象。我们在编程时经常要创建、打开和操作它们。
内核对象由内核拥有,并不是进程所拥有,每个内核对象都有一个计数器来存储有多少个进程在使用它的信息。进程调用时,计数器增1,调用结束,计数器减1,内核对象计数器为零时,销毁此内核对象.
内核对象有安全描述符的保护,安全描述符描述了谁创建了该对象以及谁能够使用该对象。用于创建内核对象的函数几乎都有一个指向SEC URITY_ATTRIBUTES 结构的指针作为其参数。
大多数应用程序通过传NULL值创建具有默认安全性的对象。如果想限制别人对对象的访问,就需要单独创建一个SECURITY_ATTRIBUTES对象并对其初始化。
2. 什么是句柄?
通俗的概念:句柄是WONDOWS用来标识被应用程序所建立或使用的对象的唯一整数,WINDOWS使用各种各样的句柄标识诸如应用程序实例,窗口,控制,位图,GDI对象等等。当应用程序访问内核对象时,将返回一个标示内核对象的东东,这些则是句柄。内核对象的“句柄”,可以惟一地标志对象。当应用创建内核对象时,返回的就是句柄。此内核对象进程的所有线程都可以利用这个句柄访问内核对象。
3. 内核对象创建?
当利用creat*函数如creatfilemapping来创建内核对象时,调用该函数的时候内核就为该对象分配一个内存块,并进行初始化,然后内核再扫描该进程的句柄表,初始化一条记录并放在句柄表中。所谓的句柄表是指每个进程在被初始化的时候,将被分配一个句柄表,该句柄表将存储内核对象的句柄,该句柄表包含三个内容:内核对象句柄,内核对象句柄地址,访问屏蔽标志。
4. 关闭内核对象
无论进程怎样创建内核对象,在不使用该对象的时候都应当通过Bool CloseHandle(HANDLE hobj)来向操作系统声明结束对该对象的访问。
这里有一个问题:为什么结束进程能释放所有占用的资源?
是因为进程在运行时有可能出现内存泄露。在进程终止运行时,系统会自动扫描进程的句柄表。若表中拥有任何无效项目(进程终止前没关闭的对象),系统将关闭这些对象的句柄。对象的计数器被置0,内核便会撤销这些对象。
5. 如何发现和分析句柄泄露?
经上面的了解知道当应用程序使用完内核对象之后需要释放资源关闭内核对象。如果没用CloseHandle,那么将可能导致当前进程无法再打开对应的内核对象,而从系统层面上来说将会大量占用内存,导致系统变慢。所以有时候发现应用程序进程本身占用内存不多,但是系统内存占用却很高,可能就因为句柄泄露导致。
下面简单介绍两种判断句柄泄露的方法:
1)、打开任务管理器:选择菜单:查看—选择列,勾上“句柄数“,如下图:
此时任务管理器中多了一列句柄数,如果你发现一个进程句柄数在不断增加,那么可能该进程就存在内存泄露了。
2)使用工具Process Explorer,该工具能够非常明了的看到进程所正在使用的内核对象,当存在句柄对象时,它能够协助你分析找到原因。下面以一个存在句柄泄露的简单程序为例:
该程序在访问1.txt这个文件的时候,没有关闭句柄,导致文件不断被打开。
从这里你还可以看到有GDI handles和USER Handles的概念,我理解为这些handle可以统称为内核对像的handle,只是对应了不同的内容,handles直接操作文件,注册表这类东西,而gdi与user操作的是可见的这些东西,gdi与user又有细份,gdi更关注图形,而user更关注交互。