1.每个内核对象都只是一个内存块,它由操作系统内核分配,并只能由操作系统内核访问。这个内存块是一个数据结构,其成员维护着与对象相关的信息。
2.调用一个会创建内核对象的函数后,函数会返回一个句柄,它标识了所创建的对象。在32位windows进程中,句柄是一个32位的值,在64位windows进程中,则是一个64位的值。
3.为了增强操作系统的可靠性,这些句柄值是与进程相关的,不可以直接跨进程使用。
4.内核对象的所有者是操作系统内核,而非进程,在进程内创建的内核对象在进程终止时不一定会被销毁。大多数情况下是会被销毁的,但是假如另一个进程正在使用我们的进程创建的内核对象,那么在其他进程停止使用它之前是不会被销毁的。
5.操作系统的内核知道当前有多少个进程正在使用一个特定的内核对象,因为每个对象都包含一个使用计数。使用计数是所有内核对象类型都有的一个数据成员。初次创建 一个对象的时候,使用计数被设为1,另一个进程获得对现有内核对象的访问后,使用计数就会递增。进程终止运行后,操作系统内核将自动递减此进程仍然打开的所有内核对象的使用计数。一旦使用计数变成0,操作系统内核就会销毁该对象。可以保证系统中不存在没有被任何进程引用的内核对象。
6.内核对象可以用一个安全描述符来保护。安全描述符描述了谁拥有对象,哪些组和用户被允许/拒绝访问或使用此对象。
7.一个进程在初始化时,系统将为它分配一个句柄表。这个句柄表仅供内核对象使用。
8.系统用索引来表示内核对象的信息保存在进程句柄表中的具体位置,要得到实际的索引值,句柄值实际应该除以4(忽略Windows操作系统内部使用的最后两位)。
9.CloseHandle() 会先检查句柄是否有效,如果有效,系统就将获得内核对象的数据结构的地址,并将结构中的“使用计数”成员递减,如果变成0,则内核对象将被销毁,并从内存中移出去。
10.在CloseHandle() 函数返回之前,它会清除进程句柄表中对应的记录项,无论内核对象当前是否销毁,这个清除过程都会发生。
11.三种不同的机制允许进程共享内核对象:使用对象句柄继承,为对象命名,复制对象句柄。
使用对象句柄继承
1.只有在进程之间有一个父子关系的时候,才可以使用对象句柄继承。句柄表中的每个记录项都有一个指明句柄是否可以继承的标志位,如果PSECURUTY_ATTRIBUTES传入的参数时NULL,则返回的句柄是不可继承的,这个标志位为0,将bInheritHandle成员设置为TRUE,则导致这个标志位被设为1。
2.例如在CreateProcess函数的bInheritHandles参数的值是TRUE,系统会遍历父进程的句柄表,对它的每一个记录项进行检查,凡是包含一个有效的"可继承的句柄"项,都会被完整地复制到子进程的句柄表。而且在子进程的句柄表中,复制项的位置与它在父进程句柄表中的位置是完全一样的。也意味着:在父进程和子进程中,对一个内核对象进行标识的句柄值是完全一样的。同时系统还会递增内核对象的使用计数。同理,孙进程也会继承这个内核对象句柄。
3.对象句柄的继承只会发生在生成子进程的时候,假如父进程后来又创建了新的内核对象,那么子进程是不会继承这些新的句柄。
4.子进程并不知道自己继承了任何句柄。
5. SetHandleInformation()函数可以用来改变内核对象句柄的继承标志,GetHandleInformation()可以获得指定句柄的状态标志。
为对象命名
1.所有对象都共享同一个命名空间,即使它们类型并不相同。
2.如果进程A中已经创建了一个命名对象(即使是不可继承的),那么在进程B中以同样的名字来创建命名对象时,系统会首先查看是否存在相同名称的内核对象,如果确实存在,内核接着检查对象的类型,如果类型不匹配就会返回NULL。如果匹配则系统接着执行一次安全检查,严重调用者是否拥有对该对象的完全访问权限,如果拥有则系统会在进程B的句柄表中查找一个空白记录项,并将其初始化为指向现有的内核对象。如果被拒绝访问,则失败。
// Process A
HANDLE hMutexA = CreateMutex(NULL,false,_T("MyMutex")); // Process B
HANDLE hMutexB = CreateMutex(NULL, false, _T("MyMutex"));
3.用于创建内核对象的函数(比如CreateSemaphore)总是返回具有完全访问权限的句柄。如果想限制一个句柄的访问权限,可以使用这些函数的扩展版本(*ex)。
4.由于在进程B的句柄表中,用一个新的记录项来引用了这个对象,所以这个互斥量对象的使用计数会被增加。
5。进程B创建内核对象时,如果已经存在,则传递的安全属性和其他参数会被忽略。
6.调用Create*函数和调用Open*函数的主要区别在于,如果对象不存在,Create*会创建,Open*只是调用失败。
7.终端服务的情况和前面描述的稍微有所区别。正在运行终端服务的计算机中,有多个用于内核对象的命名空间。其中一个时全局命名空间,所有客户端都能访问的内核对象要放在这个命名空间中。这个命名空间主要由服务使用。此外,每个客户端会话都有一个自己的命名空间,这样即使对象的名称相同也会不互相干扰。并非只有服务器才会遇到这种情况,因为Remote Desktop和快速用户切换特性也是利用终端服务会话来实现的。
8.在没有任何用户登陆的时候,服务会在第一个会话(Session 0)中启动,这个会话不是交互式的。和以前的版本不同,在Windows Vista中,只要用户登陆,应用程序就会在一个新的会话(与Session 0不同的一个会话)中启动。所以.任何对象想要和用户应用程序共享,都必须在全局命名空间中创建它。
9.如果必须知道我们的进程在哪个Terminal Services会话中运行,可以借助于ProcessIdToSessionId函数。
10.一个服务的命名内核对象始终位于全局命名空间内。默认情况下,在终端服务中,应用程序自己的命名内核对象在会话的命名空间内。不过我们可以在其名称前加上“Global\”前缀强制把一个命名对象放入全局命名空间。或者在名称前加上“Local\”前缀把内核对象放入当前会话的命名空间中。
HANDLE hGlobal = CreateEvent(NULL,FALSE,FALSE,_T("Global\\MyName"));
HANDLE hLocal = CreateEvent(NULL, FALSE, FALSE, _T("Local\\MyName"));
11.Microsoft认为Global和Local是保留关键字,所以除非为了强制一个特定的命名空间,否则不应在对象名称中使用它们。Session也是保留关键字,我们可以使用Session\<当前会话ID>\,但是不能使用另一个会话中的名称和Session前缀来新建一个对象,这样会导致函数调用失败。
12.所有保留关键字都是区分大小写的。
复制对象句柄
1.DuplicateHandle函数获得一个进程的句柄表中的一个记录项,然后在另一个进程的句柄表中创建这个记录项的一个副本。其第一个和第三个参数必须是进程内核对象。第二个参数是指向任何类型的内核对象的一个句柄。但是它的句柄值一定不能与调用DuplicateHandle函数的那个进程相关。
DuplicateHandle(
_In_ HANDLE hSourceProcessHandle,
_In_ HANDLE hSourceHandle,
_In_ HANDLE hTargetProcessHandle,
_Outptr_ LPHANDLE lpTargetHandle,
_In_ DWORD dwDesiredAccess,
_In_ BOOL bInheritHandle,
_In_ DWORD dwOptions
);
2.进程中永远不要关闭为另外一个进程复制出来的句柄。