《Windows核心编程》——三 内核对象

来源:互联网 发布:中国网络巨头有哪几个 编辑:程序博客网 时间:2024/06/05 18:40

前言

    内核对象用于管理进程、线程和文件等诸多种类的大量资源。

3.1 何为内核对象

    内核对象包括访问令牌对象、事件对象、文件对象、文件映像对象、I/O完成端口对象、作业对象、邮件槽对象、互斥量对象、管道对象、进程对象、信号量对象、线程对象、可等待的计时器对象以及线程池工厂对象等。每个内核对象都只是一个内存块,它由操作系统内核分配,并只能由操作系统内核访问。这个内存块是一个数据结构,其成员维护着与对象相关的信息。少数成员(安全描述符和使用计数等)是所有对象都有的,但其他大多数成员都是不同类型的对象特有的。应用程序不能在内存中定位这些数据结构并直接更改其内容,应用程序应该利用Windows提供的一组函数,这组函数会以最恰当的方式来操作这些结构。用户始终可以使用这些函数来访问这些内核对象。

    调用一个会创建内核对象的函数后,函数会返回一个句柄(handle),它标识了所创建的对象,句柄其实就是一个32位的数值或64位的数值。可以将这个句柄想象为一个不透明的值,它可由进程中的任何线程使用。为了增强操作系统的可靠性,这些句柄值是与进程相关的。

3.1.1 使用计数(可以想象成智能指针)

    内核对象的所有者是操作系统内核,而非进程。因此内核对象的生命周期大于等于进程的生命周期。操作系统内核知道当前有多少个进程正在使用一个特定的内核对象,因为每个内核对象都包含一个使用计数。使用计数是所有内核对象类型都有的一个数据成员。


3.1.2 内核对象的安全性

    内核对象可以用一个安全描述符(Security Descriptor,SD)来保护。安全描述符描述了谁(通常是对象的创建者)拥有对象;哪些组和用户被允许访问或使用此对象;哪些组和用户被拒绝访问此对象。用于创建内核对象的所有函数几乎都有指向一个SECURITY_ATTRIBUTES结构的指针作为参数,而用于创建用户对象或GDI对象的函数都没有该参数。

3.2 进程内核对象句柄表

    一个进程在初始化时,系统将为它分配一个句柄表。这个句柄表仅供内核对象使用,不适用于用户对象或GDI对象。下图显示了一个进程的句柄表,它只是一个由数据结构组成的数组。

                                            

3.2.1 创建一个内核对象

    一个进程首次初始化的时候,其句柄表为空。当进程内的一个线程调用一个会创建内核对象的函数时,内核将为这个对象分配并初始化一个内存块。然后,内核扫描进程的句柄表,查找一个空白的记录项。由于表3-1展示的是一个空白句柄表,所以内核在索引1位置找到空白的记录项,并对其进行初始化。具体地说,指针成员会被设置成内核对象的数据结构的内部内存地址,访问掩码将被设置成拥有完全访问权限,标志也会设置。索引值为句柄值/4。用于创建内核对象的所有函数都会返回一个与进程相关的句柄,这个句柄可由同一个进程中的所有线程所使用。系统用索引来表示内核对象的信息保存在进程句柄中的具体位置。

    只有在调用CreateFile时,才能将它的返回值与INVALID_HANDLE_VALUE比较,其他的都与NULL比较。


3.2.2 关闭内核对象(CloseHandle)

    无论以什么方式创建内核对象,用户都应该调用CloseHandle向系统表明我们已经结束使用对象,在内部,该函数首先检查主调函数进程的句柄表,验证“传给函数的句柄值”标识的是“进程确实有权访问的一个对象”。如果句柄是有效的,系统就将获得内核对象的数据结构的地址,并将结构中的“使用计数”成员递减。如果使用计数变成0,内核对象将被销毁,并从内存中移除。

                        

    对于内核对象,操作系统执行的是以下操作:进程终止时,系统自动扫描该进程的句柄表。如果这个表中有任何有效的记录项(即进程终止前没有关闭的对象),操作系统会为我们关闭这些对象句柄。只要这些对象中有一个的使用计数递减到0,内核就会销毁对象。


3.3 跨进程边界共享内核对象

    内核对象是受安全性保护的,进程在试图操作一个对象之前,必须先申请操作它的权限。对象的创建者为了阻止一个未经许可的用户“碰”自己的对象,只需拒绝该用户访问它。可以利用三种不同的机制来允许进程共享内核对象:使用对象句柄继承;为对象命名;复制对象句柄。

3.3.1 使用对象句柄继承(只能在父子进程时使用)

    Windows支持的是“对象句柄的继承”;换言之,只有句柄才是可以继承的,对象本身是不能继承的。

    使用这种方式时,父进程必须执行的几个步骤:

    首先,当父进程创建一个内核对象时,父进程必须向系统指出它希望这个对象的句柄是可以继承的。

    其次,由父进程创建子进程,并将bInheritHandles设置为true。

     

    内核对象的内容被保存在内核地址空间中——系统上运行的所有进程都共享这个空间。对象句柄的继承只会在生成子进程的时候发生。子进程并不知道自己继承了任何句柄。句柄继承只所以能够实现,唯一的原因就是“共享的内核对象”的句柄值在父进程和子进程中是完全一样的。这正是父进程能将句柄值作为命令行参数来传递的原因。

3.3.2 改变句柄的标志(SetHandleInformation)

3.3.3 为对象命名(进程可不为父子关系,跨进程边界共享内核对象的第二个方法)

    微软没有提供任何专门的机制来保证为内核对象指定名称的唯一的,所有的内核对象都共享同一个命名空间,即使它们的类型并不相同。

       

3.3.4 终端服务命名空间

3.3.6 复制对象句柄(跨进程边界共享内核对象的第三种方法)(DuplicateHandle)

    该函数获得一个进程的句柄表中的一个记录项,然后在另一个进程的句柄表中创建这个记录项的一个副本。该函数最常见的一种用法可能涉及系统中同时运行的三个不同的进程。同样这种方法的缺点是目标进程不知道它现在能访问一个新的内核对象。

   

0 0
原创粉丝点击