浅尝《Windows核心编程》之内核对象

这篇文章,主要的内容来自于一位大哥的博文(http://www.cnblogs.com/xuyuan77/archive/2008/04/23/1167623.html

这里主要做的是对其内容的补充,我的补充用蓝色标出。

内核对象主要要用来供系统和应用程序管理系统资源,像进程、线程、文件等。存取符号对象、事件对象、文件对象、作业对象、互斥对象
、管道对象、等待计时器对象等都是内核对象。我们在编程时经常要创建、打开和操作它们。内核对象通过调用函数来创建,如要创建文件映射对象,就调用CreateFileMapping函数。每个内核对象都会分配一个内存块,只能由其内核访问。该内存块是一种数据结构,用于管理对象的各种信息。

我们的应用程序不能直接访问内核对象的数据结构。需要通过Windows提供的函数来访问。

内核对象由内核拥有,并不是进程所拥有。每个内核对象都有一个计数器来存储有多少个进程在使用它的信息。由于这个计数器,系统能够判别出是否应该去撤销该内核对象。必须要记住,内核对象的存在时间可以比创建该内核对象的进程长。

内核对象有安全描述符的保护,安全描述符描述了谁创建了该对象以及谁能够使用该对象。用于创建内核对象的函数几乎都有一个指向SECURITY_ATTRIBUTES 结构的指针作为其参数。CreateFileMapping函数的指针的代码如下所示:

1   HANDLE CreateFileMapping(
2   HANDLE hFile.
3   PSECURITY_ATTRIBUTES psa,
4   DWORD flProtect,
5   DWORD dwMaximumSizeHigh,
6   DWORD dwMaximuniSizeLow,
7   PCTSTR pszNarne);

大多数应用程序通过传NULL值创建具有默认安全性的对象(对象管理小组的任何成员及创建者拥有全部访问权,而其他任何人均无权访问)。如果你想限制别人对对象的访问,你就需要单独创建一个SECURITY_ATTRIBUTES对象并对其初始化。代码如下:

1   SECURITY_ATTRIBUTES sa;
2   sa.nLength = sizeof(sa);       //Used for versioning
3   sa.lpSecuntyDescriptor = pSD,  //Address of an initialized SD
4   sa.bInheritHandle = FALSE;     //Discussed later
5   HANDLE hFileMapping = CreateFileMapping(INVALID_HANDLE_VALUE, 
6                    &sa, PAGE_REAOWRITE, 01024"MyFileMapping");

   当一个进程被初始化时,系统会为其分配一个句柄表。句柄表用于内核对象,而不用于用户对象和GDI对象。
 表3-1 进程的句柄结构

索引 内核对象内存块的指针 访问屏蔽(标志位的D W O R D ) 标志(标志位的D W O R D )
1 0 x ? ? ? ? ? ? ? ? 0 x ? ? ? ? ? ? ? ? 0 x ? ? ? ? ? ? ? ?
2 0 x ? ? ? ? ? ? ? ? 0 x ? ? ? ? ? ? ? ? 0 x ? ? ? ? ? ? ? ?
... ... ... ...
   
这里引出了关于句柄的事宜,由于句柄的概念在SDK文档里没有被文档化,所以相对于Windows 2000以后还可能会改变。至少在Windows 2000里的句柄的概念就是以上所表示的句柄表的索引。所以如果用Watch视图去查看的话,很有可能会看到比较小的数值句柄实际是用来表示内核对象的信息存放的位置。


创建内核对象
进程初次初始化时,句柄表是空的。进程中的线程调用创建内核对象的函数时,内核就为相应的内核对象分配一个内存块,并初始化。内核
对进程的句柄表进行扫描,找到一个空项,用对象的数据结构的内存地址进行初始化。下面是一些创建内核对象的函数:
 1   HANDLE CreateThread(
 2     PSECURITY_ATTRIBUTES psa,
 3     DWORD dwStackSize,
 4     PTHREAD_START_ROUTINE pfnStartAddr,
 5     PVOID pvParam,
 6     DWORD dwCreationFlags,
 7     PDWORD pdwfhreadId);
 8
 9   HANDEE CreateFile(
10     PCTSTR pszFileName,
11     DWORD dwDesiredAccebS,
12     DWORD dwShareMode,
13     PSECURITY_ATTRIBUTES psa,
14     DWORD dwCreationDistribution,
15     DWORD dwFlagsAndAttnbutes,
16     HANDEE hTemplateFile);

那么怎样去区别哪个是内核对象哪个是用户对象或者GDI对象?办法是通过查看创建对象的函数,如果该函数带有PSECUTIRY_ATTRIBUTES的参数,那它一定是内核对象。

注意,如果创建内核对象成功了,函数将会返回于句柄表中标示该内核对象的索引——即句柄。如果函数调用失败了,比如在内存很紧张,或者遇到了安全方面的问题的时候,按照不同的函数可能会返回不同的值,不过通常是NULL(0)或者INVALID_HANDLE_VALUE(-1)。所以这里的检查要格外小心,最好查看SDK文档。

关闭内核对象
不论通过何种方式创建内核对象,都通过调用CloseHandle方法来结束对内核对象的操作。他所做的事情是将对应内核对象的计数器减1,清除句柄表中的相应项,将其填充为空。
   BOOL CloseHandle(HANDLE hobj);
为什么结束进程能释放所有占用的资源?进程在运行时有可能出现内存泄漏。在进程终止运行时,系统会自动扫描进程的句柄表。若表中拥有任何无效项目(进程终止前没关闭的对象),系统将关闭这些对象的句柄。对象的计数器被置0,内核便会撤销这些对象。

跨进程边界共享内核对象
很多时候,不同进程的线程需要共享内核对象。如邮箱和指定的管道使应用程序能在联网的计算机上不同的进程之间发送数据块。
对象句柄的继承性:只有当进程之间是父子关系时,才能使用对象句柄的继承性。在这种情况下,父进程可以使用一个或多个内核对象句柄,并且该父进程可以决定生成一个子进程,为子进程赋予对父进程的内核对象的访问权。
实现过程
1.父进程创建内核对象,并指明对象的句柄是可继承的句柄(实现过程就是将SECUTIRY_ATTRIBUTE结构进行人为的确定,特别是bInheritHandle属性,必须得设置为true),注意这样做的直接结果就是此内核对象所标示的句柄具有继承性,而非内核对象本身具备继承性。这样做的原因正是模拟指针的行为。
2.
使用对象句柄继承性时要执行的下一个步骤是让父进程生成子进程。这要使用CreateProcess函数来完成: 

 1   BOOL CreateProcess(
 2     PCTSTR pszApplicationName,
 3     PTSTR pszCommandLine,
 4     PSECURITY_ATTRIBUTES psaProcess,
 5     PSECURITY_ATTRIBUTES psaThread,
 6     BOOL bInheritHandles, //TRUE表可继承,FALSE表不可继承
 7     DWORD fdwCreale,
 8     PVOIO pvEnvironment,
 9     PCTSTR pszCurDir,
10     PSTARTUPINFO psiStartInfo,
11     PPROCESS_INFORMATION ppiProcInfo);

当对bInheritHandle传递TRUE时,操作系统就创建该新子进程,但是不允许子进程立即开始执行它的代码。首先,它先创建一个新的和空的属于子进程的句柄表,它要遍历父进程的句柄表,对于它找到的包含有效的可继承句柄的每一个项目,系统会将该项目准确地拷贝到子进程的句柄表中。该项目拷贝到子进程的句柄表中的位置将与父进程的句柄表中的位置完全相同。这样标示内核对象所用的句柄值就变得相同了。这样子,就可以通过对CreateProcess的参数来传递句柄。

如果想要控制哪个子进程来继承内核对象的句柄。若要改变内核对象句柄的继承标志,可以调用SetHandleInformation函数。如下是打开一个内核对象句柄的继承标志:
SetHandleInformation(hObj, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)
如下是关闭该标志:
SetHandleInformation(hObj, HANDLE_FLAG_INHERIT, 0)

CreateProcess函数返回后,父进程会立即关闭对象的句柄,而不影响子进程对该对象进行相关操作。

命名对象共享跨越进程边界的内核对象的另一种方法。大部分内核对象可以被命名。若要按名字共享对象,则必须为对象赋予同一个名字。如下面的创建命名内核对象的代码:   

 1HANDLE CreateMutex(
 2   PSLCURITY_ATTRIBUTES psa,
 3   BOOL bInitialOwner,
 4   PCTSTR pszName);
 5
 6HANDLE CreateEvent(
 7   PSECURITY_ATTRIBUTES psa,
 8   BOOL bManualReset,
 9   BOOL bInitialState,
10   PCTSTR pszName);
两个函数的最后一个参数都是为对象命名用。

由于所有这些对象都共享单个名空间,所以再给对象起名字的时候要确保内核对象命名没有重复。利用命名解决了使用双方不一定是父子关系的缺点。
用的时候在Process A里使用CreateMutex(NULL, FALSE, "a");在Process B里使用CreateMutex(NULL,FALSE,"a");系统就会去查找命名空间,如果找到了名字为"a"的内核对象,那么就将它的地址在Process B的句柄表里加入一项,并且返回对应的句柄。

赋值对象句柄: 简单来说,就是DuplicateHandle函数取出一个进程的句柄表中的项目,并且将该项目copy到另外一个进程的句柄表中。
原文地址:https://www.cnblogs.com/aicro/p/1553875.html