每一个内核对象都只是一个内存块,它由操做系统内核分配,并只能由操做系统内核访问。这个内存块是一个数据结构,其成员维护着与对象相关的信息。少数成员(安全描述符和使用计数)是全部对象都有的,但其余大多数成员都是不一样类型的对象特有的数组
因为内核对象的数据结构只能由操做系统内核访问,因此应用程序不能在内存中定位这些数据结构并直接更改其内容。正由于有这个限制,因此微软能自由地添加、删除或修改这些数据结构中的成员,同时不会干扰任何应用程序的正常运行安全
为了加强系统的可靠性,内核对象的句柄是与进程相关的。若是进程A将一个属于A进程的内核对象句柄传送给进程B,那么进程B中的线程用进 程A传入的句柄发出调用时,就有可能失败;更糟糕的状况是,进程B会根据传入的句柄值在进程B的句柄表中引用另外一个彻底不一样的内核对象,将致使没法预测的 后果session
内核对象的全部者是操做系统内核,而非进程数据结构
使用计数:函数
操做系统内核知道有多少个进程正在使用一个特定的内核对象,由于每一个对象都包含一个使用计数(usage count)spa
使用计数是全部内核对象类型都有的一个数据成员操作系统
初次建立一个对象的时候,其使用计数被设为1。另外一个进程得到对现有内核对象的访问后,使用计数就会递增。进程终止运行后,操做系统内核将自动递减此进程仍然打开的全部内核对象的使用计数.net
若是一旦对象的使用计数递减为0,操做系统就会销毁该对象命令行
内核对象能够用一个安全描述符(security descriptor, SD)来保护。安全描述符描述了谁拥有对象;哪些组和用户被容许访问或使用此对象;那些组和用户被拒绝访问此对象线程
进程内核对象句柄表(后文简称句柄表):
一个进程在初始化时,系统将为它分配一个句柄表(handle table)。这个句柄表仅供内核对象使用,不适用于用户对象或GDI对象
一 个进程首次初始化的时候,其句柄表为空。当进程内的一个线程调用一个会建立内核对象的函数,内核将为这个对象分配并初始化一个内存块。而后,内核扫描进程 的句柄表,查找一项空白的记录项,并对其进行初始化(指针成员会被设置成内核对象的数据结构的内部内存地址,访问掩码将被设置成拥有彻底访问权限,继承标 志也会被设置)
用于建立内核对象的任何函数都会返回一个与进程相关的句柄,这个句柄可由同一个进程中运行的全部线程使用。系统用索引来表示内核对象的信息保存在进程句柄表中的具体位置,句柄值除以四便是实际的索引值
调用一个函数时,若是它接受一个内核对象句柄做为参数,就必须把Create*函数返回的值传给它。在内部,这个函数会查找进程的句柄表,得到目标内核对象的地址,而后以一种恰当的方式来操纵对象的数据结构
关闭内核对象(CloseHandle):
该函数首先检查调用进程的句柄表,验证“传给函数的句柄值”标识的是“进程确实有权访问的一个对象”。若是句柄是有效的,系统将得到内核对象的数据结构的地址,并将结构中的“使用计数”成员递减。若是使用计数变为0,内核对象将被销毁,并从内存中除去
就在CloseHandle函数返回以前,它会清楚进程句柄表中对应的记录项——这个句柄如今对咱们的进程来讲是无效的,不要再试图用 它。不管内核对象当前是否销毁,这个清除过程都会发生!一旦调用CloseHandle,咱们的进程就不能访问那个内核对象;可是若是对象的使用计数还没 递减至0,它就不会被销毁
调用CloseHandle后,应将保存句柄值的变量置NULL。若是不当心使用了未被置NULL的已被CloseHandle的值,将会发生两种状况:
因为此变量所引用的句柄表记录项已被清除,调用函数调用失败
建立一个新的内核对象时,Windows会在句柄表中查找空白记录。因此,未被置NULL的变量极有可能匹配到新建立的内核对象。函数 调用时,一旦错误地用这个还没有置NULL的变量,就可能定位到一个错误类型的内核对象(这种状况会报错)。跟糟糕的是,可能会定位到一个类型(和已经关闭 的内核对象)相同的内核对象(这种状况不会报错)。这种状况下,应用程序的状态将损坏,没有任何办法能够恢复
当进程终止运行,操做系统会确保此进程使用的全部资源(内核对象、资源、内存块等)都被释放,系统会确保进程不会留下任何东西。对于内核 对象,操做系统执行如下操做:进程终止时,系统自动扫描该进程的句柄表。若是这个表中的任何有效记录项(进程终止前没有关闭的对象),操做系统会关闭这些 对象的句柄。若某对象的使用计数递减为0,内核就会销毁对象
只有在进程之间有一个父——子关系的时候,才可使用对象句柄继承
步骤:
当为CreateProcess函数的bInheritHandles传递TRUE时,操做建立新的子进程,但不容许子进程当即执行它 的代码。系统会为子进程建立一个新的、空白的进程句柄表——就像它为任何一个新进程全部的那样。系统还会多作一件事:它会遍历父进程的句柄表,对它的每一 个及录像进行检查。凡是包含一个有效的“可继承的句柄”的项,都会被完成地复制到子进程的句柄表。在子进程的句柄表中,复制项的位置与它在父进程句柄表中 的位置是彻底同样的。这意味着:在父进程和子进程中,对一个内核对象进行标识的句柄值是彻底同样的
对象句柄的继承只会在生成子进程的时候发生。假如父进程后来又建立了新的内核对象,并一样将它们的句柄设为可继承的句柄。那么正在运行的子进程是不会继承这些新句柄的
对象句柄的继承还有一个很是奇怪的特征:子进程并不知道本身继承了任何句柄。为了使子进程获得它想要的一个内核对象的句柄值,最经常使用的方式是将句柄值做为命令行参数传递给子进程。能够这样作的缘由是,父子进程对一个内核对象进行标识的句柄值是彻底同样的
能够调用SetHandleInformation来改变内核对象句柄的继承标志,这样父进程就能够控制哪些子进程能继承内核对象句柄了
当父进程建立一个内核对象时,父进程必须向系统指出它但愿这个对象的句柄是能够继承的。注意,只有句柄是能够继承的,对象自己是不能继 承的。为了建立一个可继承的句柄,父进程必须分配并初始化一个SECURITY_ATTRIBUTES结构,将 SECURITY_ATTRIBUTES.bInheritHandle设为TRUE,并将这个结构的地址传给具体的Create函数
建立子进程,调用CreateProcess来完成。若是参数bInheritHandles被设置为TRUE,子进程就会继承父进程的“可继承句柄”的值
全部进程的全部内核对象都共享同一个命名空间,即便它们的类型并不相同
进程A建立了一个名为“TTMutex”的互斥量内核对象,以后,进程B发生如下调用
HANDLE hMutexProcessB = CreateMutex(NULL, FALSE, TEXT("TTMutex"));
用于建立内核对象的函数Create*老是返回具备彻底访问权限的句柄。若是想限制一个句柄的访问权限,可使用Create*ex
在调用Create*以后,能够当即调用GetLastError,若返回ERROR_ALREADY_EXIST则代表仅仅打开了一个现有的对象,而并不是建立了一个新的
微软没有提供任何专门的机制来保证咱们建立独一无二的对象名
能够利用命名对象来防止运行一个应用程序的多个实例
当进程B调用CreateMutex时,系统首先会查看是否存在一个名为“TTMutex”的内核对象。因为已存在,因此内核接着检查 对象的类型。因为试图建立一个互斥对象,而名为“TTMutex”的对象也是一个互斥对象,因此系统接着执行一次安全检查,验证调用者是否拥有对该对象的 彻底访问权限。若是答案是确定的,系统就会在进程B的句柄表中查找一个空白记录项,并将其初始化为指向现有的内核对象。若是类型不匹配,或调用者被拒绝访 问,CreateMutex就会失败(返回NULL)
进程B调用CreateMutex时,它会向函数传递安全属性信息和第二个参数,若是已经存在一个指定名称的对象,这些参数就会被忽略
终端服务命名空间:
HANDLE h = CreateEvent(NULL, FALSE, FALSE, TEXT("Global\\MyName"));
也能够显示把一个内核对象放入当前会话的命名空间,只要在名称前加上“Local\”便可:
HANDLE h = CreateEvent(NULL, FALSE, FALSE, TEXT("Local\\MyName"));
在正在运行终端服务的计算机中,有多个用于内核对象的命名空间。其中一个是全局命名空间,全部客户端都能访问的内核对象要放在这个命名空间中。这个命名空间主要由服务使用。此外,每一个客户端会话(client session)都有一个本身的命名空间,这样不会相互干扰
服务的命名内核对象始终位于全局命名空间中。默认状况下,在终端服务中,应用程序本身的命名内核对象在会话的命名空间内。能够在其名称前加上“Global\”前缀来强制把一个命名对象放入全局命名空间:
BOOL DuplicateHandle(HANDLE hSourceProcessHandle, ///< 源进程进程内核对象句柄 HANDLE hSourceHandle, ///< 要复制的源内核对象句柄 HANDLE hTargetProcessHandle, ///< 目标进程进程内核对象句柄 LPHANDLE lpTargetHandle, ///< 用来接收获得的句柄值 DWORD dwDesiredAccess, ///< 访问掩码 BOOL bInheritHandle, ///< 继承标志 DWORD dwOptions); ///< 句柄表项,若为DUPLICATE_SAME_ACCESS,函数会忽略dwDesiredAccess;若为DUPLICATE_CLOSE_SOURCE,内核对象的使用计数不变
这个函数得到一个进程的句柄表中的一个记录项,而后在另外一个进程的句柄表中建立这个记录项的一个副本
调用函数以后,目标进程不知道它如今能访问一个新的内核对象,必须使用进程间通讯方法来通知目标进程(命令行和环境变量均行不通,由于进程早就存在,需考虑其余方法)
假设一个进程拥有对一个文件映射对象的读写权限,可使用该函数为现有的对象建立一个新句柄,并确保这个新句柄只有只读权限,这样就能够保证新句柄不会有意外的写入操做,这是该函数经常使用的方法之一
函数原型