关于Windows内核对象

前言: 为了巩固和增进多线程方面的知识,捧起了Jeffrey Richter和Christophe Nasarre的精典力作《Windows核心编程--第五版》,做些笔记,供日后再巩固和增进时之用。

PS:1) 如需转载,注明出处,不胜感激; 2) 如侵您版权,请及时通知,速处理之
1、概念
   使用计数
   安全性
2、进程内核对象的句柄表
   创建内核对象
   关闭内核对象
3、跨进程边界共享内核对象
   使用对象句柄
   为对象命名
   复制对象句柄

 

后记

1、概念
作为WINDOWS软件开发人员,我们经常需要创建、打开、和处理内核对象。系统会创建和处理几种类型的内核对象。比如访问令牌(ACCESS TOKEN)对象、事件对象、文件对象、文件映射对象、I/O完成端口对象、作业对象、邮件槽(MAILSLOT)对象、互斥量对象、管道(PIPE)对象、进程对象、信号量(SEMAPHORE)对象、线程对象、可等待的计时器(WAITABLE)对象以及线程池工厂对象等。每个内核对象都只是一个内存块,它由操作系统内核分配,并只能由系统内核访问。这个内存数据块是一个数据结构。其成员维护着与对象相关的信息。少数成员是所有对象都有的(如安全描述符和使用计数等),大多数成员都是不同类型特有的。例如,进程对象有一个进程ID、一个基本的优先级和一个退出代码;而文件对象有一个字节偏移量、一个共享模式和一个打开模式。
由于内核对象的数据结构都是操作系统内核级访问,所以应用程序不能在内存中定位这些数据结构并直接更改其内容。所以Windows提供了的一组函数会以最恰当的方式来操纵这些结构。
使用API函数会创建内核对象,函数会返回一个句柄(HANDLE),它标识了所创建的内核对象。为了提高操作系统的可靠性,这些句柄值是与进程相关的。

使用计数
内核对象的生命周期可能长于创建它的进程。因为内核对象的所有者是操作系统内核,而非进程。
系统内核知道当前有多少个进程正在使用一个特定的内核对象,因为每个对象都包含一个使用计数。能通使用计数来确定是否销毁内核对象(使用计数为0时销毁)。

内核对象的安全性
内核对象可以使用一个安全描述符(SECURITY DISCRIPTOR)来保护。安全描述符通常在编写服务器应用程序的时候使用。用于创建内核对象的API几乎都有指向一个 SECURITY_ATTRIBUTES结构的指针作为参数。大多数应用程序只是为这个参数传入NULL,这样创建的内核对象具有默认的安全性---具体包括哪些安全性,要取决于当前进程的安全令牌。当然,也可以分配一个SECURITY_ATTRIBUTES结构,并对它进行初始化,再将它的地址传给这个参数。
区别用户/GDI对象和内核对象最简单的方式是查看创建这个对象的API函数。几乎所有创建内核对象的函数都有一个允许我们指定安全属性信息的参数。
注:关于用户/GDI/内核对象,看一下MSDN:对象分类(Windows)

2 、进程内核对象的句柄表
进程初始化时,系统将为它分配一个句柄表。这个表仅供内核对象访问。
创建内核对象
一个进程首次初始化的时候,其句柄表为空。当进程内的一个线程调用一个会创建内核对象的函数时,内核将为这个对象分配并初始化一个内存块。然后进程扫描进程的句柄表,查找一个空白的记录项,并对其进行初始化。
关闭内核对象
CloseHandle( HANDLE hObject );
减少使用计数,为0则销毁。
详细见MSDN:内核对象(Windows)   

3 、跨进程边界共享内核对象
在很多时候,不同进程中运行的线程需要共享内核对象。下面罗列了一些理由:
1)利用文件映射对象,可以在同一台机器上的两个不同进程之间共享数据块
2)借助邮槽和命名管道,在网络中的不同计算机上运行的进程可以相互发送数据块
3)互斥量、信号量和事件允许不同进程中的线程同步执行。


利用三种不同的机制来允许进程共享内核对象:
1)使用对象句柄继承
句柄表中的每个记录项都有一个指明句柄是否可以继承的标志位。如果在创建内核对象的时候将NULL作为PSECURITY_ATTRIBUTES参数传入,则返回的句柄是不可继承的,这个标志位为0。将bInheriteHandle成员设为TRUE,则导致这个标志位被设为1。
也可以通过SetHandleInformation改变句柄的继承标志

 

详见MSDN:SetHandleInfomation
2)为对象命名
许多内核对象(但不是全部)都可进行命名。
可创建命名的内核对象的API函数的最后一个参数是pszName,向此参数传入NULL,相当于向系统表明我们要创建一个示命名的内核对象。也可以传入一个“以0为终止符的名称字符串”的地址,这个名称可长达MAX_PATH个字符(定义为260),遗憾的是,Microsoft没有提供任何专门的机制来保证为内核对象指定的名称是唯一的。所以可以创建GUID,使用GUID的字符串形式作为自己内核对象的名称使用。
调用Create* API(如CreateMutex/CreateEvent等)创建一个带名字的内核对象时,系统首先会查看是否存在一个同名的内核对象,如果确实存在这样的一个对象,内核接着检查对象的类型,如果类型也相同,会接着执行一次安全检查,验证调用者是否拥有对该对象的完全访问权限。如果答案是肯定的,系统就会在此进程的句柄表中查找一个空白记录项,并将其初始化为指向现有的内核对象。如果类型不匹配或调用者被拒绝访问,就会失败(返回NULL)
当然,也可以调用Open* API(如OpenMutex/OpenEvent等)来实现内核对象的共享。
Create*函数和Open*函数的区别在于,如果对象不存在,Create*函数会创建它,而Open*函数则只是简单的以调用失败而告终。

3)复制对象句柄

利用DuplicateHandle()函数
这个函数获得一个进程的句柄表中的一个记录项,然后在另一个进程的句柄表中创建这个记录项的一个副本。

详见MSDN:DuplicateHandle

后记:

内核对象用于管理进程、线程和文件等诸多种类的大量资源,透彻理解内核对象至关重要。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值