Windows核心编程:内核对象

作者:shenzi

链接:http://hi.csdn.net/shenzi

Windows核心编程:内核对象

1.对象和句柄
   对象是静态定义的对象类型的单个运行时实例。对象类型包括系统定义的数据类型,在数据类型实例上的操作的函数以及一组对象属性集。
    对象为完成下列四种重要的操作系统任务提供了方便的方法:
  • 为系统资源提供可读的名字;
  • 在进程间共享资源和数据;
  • 保护资源以免非授权访问;
  • 引用跟踪,它允许系统确知对象什么时候不再使用以便自动被释放;(引用计数,稍后讲到)
   在Windows API中我们常常调用CreateXXX() 函数来建立一个对象,函数返回一个句柄(HANDLE),我们通过保存这个返回的句柄,在以后的程序中通过该句柄对建立的对象执行一些操作。
    在Windows操作系统中我们常常接触的有三种对象类型:
  • Windows内核对象 (事件对象,文件对象,进程对象,I/O完成端口对象,互斥量对象,进程对象,线程对象等等):由执行体(Excutive)对象管理器(Object Manager)管理,内核对象结构体保存在系统内存空间(0x80000000-0xFFFFFFFF),句柄值与进程相关。
  • Windows GDI对象 (画笔对象,画刷对象等):由Windows子系统管理,句柄值在系统,会话范围 (system-wide / session-wide) 有效。
  • Windows USER对象 (窗口对象,菜单对象等) :由Windows子系统管理,句柄值在系统,会话范围 (system-wide / session-wide) 有效。  

2.Windows 内核对象
   每个内核对象都只是一个内存块,它由操作系统分配,并只能由操作系统访问。这个内存块是一个数据结构,其成员维护着与对象相关的信息。少数成员(安全描述符和使用计数等)是所有对象都有的,但其它大多数成员都是不同类型的对象特有的。由于内核对象的数据结构只能由操作系统内核访问,所以应用程序不能在内存中定位这些数据结构并直接更改其内容。 应用程序只能间接的操纵这些数据结构,这就有了句柄(HANDL)。调用一个创建内核对象的函数后系统会返回一个句柄,它标识了多创建的对象,它可由进程中的任何线程访问。
    2.1使用计数
   
我们应该搞清楚的是:内核对象的所有者是操作系统,而内核对象的句柄的所有者是进程。
   内核对象的生命期可能长于创建它的那个进程。 进程创建一个内核对象,如果该内核对象使用了跨进程的内核对象共享(马上讲到),即其它进程也取得了该内核对象的使用权限,操作系统使用一个叫使用计数 的数据成员来记录内核对象的使用情况。每当有进程取得该内核对象的访问后,使用计数加1,当进程终止或关闭该内核对象句柄时,使用计数减1;操作系统记录每个内核对象的使用计数,一旦内核对象的使用计数为0,操作系统销毁该对象。

   2.2内核对象安全性
   内核对象可以用一个安全描述符(security descriptor,SD)来保护。它描述了谁拥有对象,哪些用户和用户被允许访问或使用此对象;哪些组和用户被拒绝访问此对象。通常在编写服务器应用程序的时候使用。
    用于创建内核对象的函数几乎都有指向一个SECURITY_ATTRIBUTES 结构的指针作为参数,如下面的CreateFileMapping

    HANDLE CreateFileMapping(
        HANDLE hFile,
        PSECURITY_ATTRIBUTES psa,
        DWORD flProtect,
        DWORD dwMaximumSizeHigh,
        DWORD dwMaximumSizeLow,
        PCTSTR pszName);
    大多数应用程序只是为这个参数传入NULL,这样创建的内核对象具有默认的安全性——具体包括哪些默认的安全性要取决于当前进程的安全令牌(security token)。
     typedef struct _SECURITY_ATTRIBUTES {
        DWORD nLength;
        LPVOID lpSecurityDescriptor;
        BOOL bInheritHandle;
    } SECURITY_ATTRIBUTES;

   2.3进程内核对象句柄表
  
一个进程在初始化时,系统将为它分配一个句柄表(handle table)。这个句柄表仅供内核对象使用 ,不适用于USER对象或GDI对象。
而对于句柄表的结构我们不得而知,不过大概如下:

索引

指向内核对象内存块的指针

访问掩码 标志

1

0x????????

0x????????

0x????????

2

0x????????

0x????????

0x????????

 

图1—句柄,指针,对象

  2.3.1创建一个内核对象
   一 个进程首次初始化时,其句柄表为空。当进程的某个线程调用了一个会创建内核对象的函数时,内核将为这个对象分配内存,并扫描进程句柄表,找到一个空白的记 录项,并对其进行初始化。指针成员被设置成内核对象的数据结构的内部内存地址,访问掩码被设置成拥有完全访问权限,标志也会被设置,根据句柄的继承性。
    由于句柄值实际是作为进程句柄表的索引来使用的,所以这些句柄是与当前所使用的进程相关的,无法供其它进程使用。如果我们真的在其它进程使用它,那么实际引用的知识那个进程的句柄表中位于同一个索引的内核对象——只是索引值相同而已,我们根本不知道它会指向什么对象。
   调用函数 来创建一个内核对象时,如果调用失败,那么返回的句柄值通常为0(NULL),有几个函数调用失败时返回的是-1,即INVALID_HANDLE_VALUE;所以在检查函数返回值时务必相当仔细。
  2.3.2关闭内核对象
   
我们通过调用函数:
    
BOOL CloseHandle(HANDLE hobject);

   来表明结束使用内核对象,该函数将清除进程句柄表中对应的记录项,并且递减该内核对象的“使用计数” ,如果该内核对象的"使用计数"减为“0”,那么操作系统销毁该内核对象,即将对象从内存中清除,但是“使用计数”大于“0”,该内核对象将继续保留,因为还有其它进程拥有该内核对象的访问权限。
   通常,在创建一个内核对象时,我们会将相应句柄保存到一个变量中。将此变量作为参数调用CloseHandle()函数后,最好能同时将这个变量设为NULL。
    在不需要再使用对象时,释放相应对象句柄是一个好习惯,如果句柄没有及时释放,可能引起内存泄露。
   当进程终止运行,操作系统会确保此进程所使用的所有资源都被释放。进程终止时,系统扫描进程句柄表,操作系统将关闭所有句柄表中的有效记录项。只要这些对象中有一个的“使用计数”减为0,内核就会销毁对象。
   进程终止时,系统也将保证进程拥有的所有资源(GDI对象等)以及内存块都将得到正确清除。
3.跨进程共享内核对象
 
不同进程运行中的线程处于某些原因需要共享内核对象:

  • 利用文件映射对象,可以再同一台机器上运行的两个不同进程之间共享数据块;
  • 借助邮件槽和命名管道,在网络中的不同计算机上运行的进程可以相互发送数据块;
  • 互斥量,信号量和事件允许不同进程中的线程同步执行。  

   有三种不同机制来允许进程共享对河对象:使用对象句柄继承;为对象命名;复制对象句柄。
   3.1使用对象句柄继承
    还记得
SECURITY_ATTRIBUTES 结构吗? SECURITY_ATTRIBUTES 结构中有个 BOOL bInheritHandle属性,该属性就是制定该内核对象是否具有继承性。
   对象句柄继承,就是说让进程的子进程继承父进程的对象句柄,实例如下:

  • 首先建立一个可继承的对象句柄;

      SECURITY_ATTRIBUTES sa; 
      sa.nLength = sizeof(sa);
      sa.lpSecurityDescriptor = NULL;
      sa.bInheritHandle = TRUE; // 指定返回的句柄具有继承性;
      HANDLE hMutex = CreateMutex(&sa, FALSE, NULL);
   

  • 父进程生成子进程,并让子进程继承对象句柄值;

    BOOL CreateProcess(
    PCTSTR pszApplicationName,
    PTSTR pszCommandLine,
    PSECURITY_ATTRIBUTES psaProcess,
    PSECURITY_ATTRIBUTES psaThread,
    BOOL bInheritHandles,//制定子进程是否继承父进程句柄表中“可继承的句柄”,设为“TRUE”
    DWORD dwCreationFlags,
    PVOID pvEnvironment,
    PCTSTR pszCurrentDirectory,
    LPSTARTUPINFO pStartupInfo,
    PPROCESS_INFORMATION pProcessInformation);

   这样创建的子进程就继承了父进程句柄表中“可继承的句柄”,并且在子进程句柄表中的位置和父进程句柄中的位置是一样的。即在父进程和子进程中,对一个内核对象的句柄值是完全一样的,在句柄表中的记录项也是一样的。
    为了使子进程得到它想要的一个内核对象的句柄值,最常见的方式是将句柄值作为命令行参数传给子进程。
    介绍两个函数:
    改变对象句柄表项信息:
    BOOL SetHandleInformation(
    HANDLE hObject,
    DWORD dwMask,
    DWORD dwFlags);

    dwMask:
    #define HANDLE_FLAG_INHERIT 0x00000001
    #define HANDLE_FLAG_PROTECT_FROM_CLOSE 0x00000002

    获取对象句柄表项信息:
    BOOL GetHandleInformation(
    HANDLE hObject,
    PDWORD pdwFlags);

   3.2为对象命名
   
许多(但不是全部)
内核对象 都可以进行命名。举例:
    HANDLE CreateMutex(
    PSECURITY_ATTRIBUTES psa,
    BOOL bInitialOwner,
    PCTSTR pszName);

    要为内核对象制定一个名称,参数
pszName应该传入一个“以0为终止符的名称字符串”的地址。所有这些对象都共享同一个命名空间,及时它们的类型并不相同。
   
相较于使用句柄继承,利用对象的名称来共享内核对象,最大的一个优势是“进程B不一定是进程A的子进程”。
   当要建立指定名称的对象时,系统先检查是否存在此“名称”的对象,如果确实存在内核将接着检查对象的类型,如果类型匹配,再进行一个安全检查,验证调用者 是否拥有对该对象的安全访问权限。如果答案是肯定的,系统将在调用进程句柄表中建立以记录项,并将其初始化为指向现有的内核对象。如果对象的类型不匹配或 调用者被拒绝访问,函数返回NULL。
    
HANDLE hMutex = CreateMutex(&sa, FALSE, TEXT("JeffObj"));
    if (GetLastError() == ERROR_ALREADY_EXISTS) {
    // Opened a handle to an existing object.
    // sa.lpSecurityDescriptor and the second parameter
    // (FALSE) are ignored.
    } else {
    // Created a brand new object.
    // sa.lpSecurityDescriptor and the second parameter
    // (FALSE) are used to construct the object.
    }

    HANDLE OpenMutex(
    DWORD dwDesiredAccess,
    BOOL bInheritHandle,
    PCTSTR pszName);

    Create*函数和Open*函数的主要区别是,如果对象存在,Create*函数会创建它;Open*函数则不同,如果对象不存在,它只是简单地以调用失败而告终。
   3.3复制对象句柄
    使用函数DuplicateHandle 实现共享内核对象

   BOOL DuplicateHandle(
    HANDLE hSourceProcessHandle,
    HANDLE hSourceHandle,
    HANDLE hTargetProcessHandle,
    PHANDLE phTargetHandle,
    DWORD dwDesiredAccess,
    BOOL bInheritHandle,
    DWORD dwOptions);

   dwOptions:
    DUPLICATE_SAME_ACCESS 表明我们希望目标句柄拥有与源进程的句柄一样的访问掩码。
     DUPLICATE_CLOSE_SOURCE :会关闭源进程中的句柄,内核对象的“使用计数”不会受到影响。








  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值