何为内核对象
令牌对象、事件对象、文件对象、文件映射对象、I/O完成对口对象、作业对象、邮件槽对象、互斥量对象、管道对象、进程对象、信号量对象、线程对象、可等待计时对器象以及线程池工厂对象等
内核对象是和线程相关的,但是线程不是内核对象的所有者
- 使用计数
使用计数是所有内核对象类型都有的一个数据成员。初次创建一个对象的时候,其实用计数被设为1。另一个进程获得对现有对象的访问后,使用计数就会递增。进程终止后,操作系统内核将自动递减此进程仍然打开的所有内核对象的使用计数。
- 内核对象的安全性
内核对象可以用一个安全描述符来保护。要想查看一个对象是不是内核对象,就查看该对象的创建函数是不是包含了一个允许我们指定安全信息的参数
- 进程内核对象句柄表
进程句柄表仅供内核对象使用。它只是一个由数据结构组成的数组,每个结构都包含一个指向一个内核对象的指针、一个访问掩码和一些标志
- 创建一个内核对象
当一个进程首次初始化的时候,其句柄表为空,当一个线程调用一个会创建内核对象的函数时,内核将对句柄表进行初始化。
用于创建内核对象的任何一个函数都会返回一个与进程相关的句柄,这个句柄可由同一个进程中运行的所有线程使用。
调用一个函数时,如果它接受一个内核对象句柄作为参数,这个函数会查找进程的句柄表,获得目标内核对象地址,然后以一种恰当的方式来操纵内核对象的数据结构。
调用一个函数来创建内核对象时,如果调用失败,那么返回的句柄值通常会是0(NULL),遗憾的是,有几个函数在调用失败时会返回句柄值-1。在检查它们的返回值时,务必相当仔细。
- 关闭内核对象
无论以什么方式创建内核对象,我们都要调用CloseHandle向系统表明我们已经结束使用对象
该函数同过检查主调进程的句柄表对使用计数进行递减,如果为0,就销毁这个内核对象,如果是一个无效的句柄,CloseHandle将返回False,GetLastError将返回ERROR_INVALID_HANDLE。如果进程正在调试,系统将会抛出异常。在CloseHandle返回之前,它会清除主调进程句柄表中对应的记录项
在创建一个内核对象时,我们会将相应的句柄保存到一个变量中,将此变量作为参数调用CloseHandle后,还应同时将这个变量设置为NULL。
跨进程边界共享内核对象
在很多时候,不同进程中的运行线程需要共享内核对象。
1.利用文件映射对象,可以共享数据块。
2.借助邮件槽和命名管道,在网络中的不同计算机上运行的进程可以相互发送数据块。
3.互斥量、信号量和事件允许不同进程中的线程同步执行。
- 使用对象句柄继承
只有进程之间有一个父子关系时,才可以使用对象句柄继承。
为了创建一个可继承的句柄,父进程必须分配并初始化一个SECURITY_ATTRIBUTES结构,并将这个结构的地址传给具体的Create函数。
句柄表中的每一个记录项都有一个指明句柄是否可以继承的标志位,如果在创建内核对象的时候将NULL作为PSECURITY_ATTRIBUTES参数传入,则返回的句柄是不可继承的,这个标志位为0,将bInheritHandle成员设为TRUE,则导致这个标志位被设为1。
为了使用对象句柄继承,下一步是由父进程生成子进程。通过CreateProcess函数来完成,把bInheritHandle传递TRUE,子进程就会继承父进程的"可继承的句柄"的值
对象句柄的继承只会在生成子进程的时候发生。
子进程并不知道自己继承了任何句柄。
到目前为止,为了使子进程得到它想要的一个内核对象的句柄值,最常见的方式是将句柄值作为命令行参数传给子进程。
也可以采用其它进程间通讯技术将继承的内核对象句柄值传入子进程。一个技术是让父进程等待子进程完成初始化,发送一条消息到子进程中的一个线程创建的一个窗口。
另一种方式是让父进程向其环境块添加一个环境变量。变量的名称应该是子进程知道的一个名称,变量的值应该是准备被子进程继承的那个内核对象的句柄值。
�* 改变句柄的标志
我们有时候想控制哪些子进程能继承内核对象句柄。可以调用SetHandleInformation函数来改变内核对象句柄的继承标志。
SetHandleInformation(hObj, HANDLE_FLAG_INHERIT , HANDLE_FLAG_INHERIT);
HANDLE_FLAG_PROTECT_FROM_CLOSE
标志告诉我们系统不允许关闭句柄
GetHandleInformation用来检查一个句柄可否继承
DWORD dwFlags; GetHandleInformation(hObj, &dwFlags); BOOL fHandleIsInheritable = (0 != (dwFlags & HANDLE_FLAG_INHERIT ));
- 为对象命名
跨进程边界共享内核对象的第二个方法是对对象命名。
通过为对象命名来实现共享时,是否可以继承并非一个必要条件。
为了实现这些内核对象的共享,还可以考虑另一个办法。我们可以不调用Create函数,相反,可以调用的一个Open函数
Create*函数返回后,调用一下GetLastError,可以查看是创建了新的内核对象还是返回了现有的
- 终端服务命名空间
在正在运行终端服务的计算机中,有多个用于内核对象的命名空间。其中一个是全局命名空间,所有用户都能访问的内核对象放到这个命名空间中。此外,每个客户端会话都有一个自己的命名空间。
如果必须知道进程在哪个会话中运行,可以借助于ProcessIdToSessionId();
我们也可以强制把一个命名对象放入全局命名空间或者当前命名空间,具体做法是在其名称前加上"Global"前缀或者"Local"
所有保留关键字都是区分大小写的
- 专有命名空间
专有命名空间相当于可供我们在其中创建内核对象的目录。和其他目录一样,专有命名空间也有一个和它相关联的安全描述符,这个描述符是在CreatePrivateNamespace的时候设置的。
- 复制对象句柄
跨进程边界共享内核对象的最后一招是使用DuplicateHandle函数