Windows核心编程 内核对象

内核对象:系统会创建几种类型的内核对象,比如文件对象,互斥量对象,管道对象,信号量对象。。。

1.每个内核对象都只是一个内存块,他由操作系统内核分配,并只能由操作系统内核访问。该内存块是一个数据结构,维护着与对象相关的信息。

使用计数和安全描述符是所有对象都具有的。

  1.1 使用计数   内核对象使用了计数方法维护生命周期

  1.2 内核对象的安全性 内核对象使用安全描述符来保护。其描述了谁拥有这个对象,哪些组和用户被容许访问和使用此对象,哪些组和用户被拒绝访问此对象。用于创建内核对象的所有函数几乎都有一个SECURITY_ATTRIBUTES结构的指针作为参数。(eg:CreateFileMapping()等等),没哟该属性则创建的是用户对象

  
  
typedefstruct _SECURITY_ATTRIBUTES {
 
  DWORD nLength;//Set this value to the size of the SECURITY_ATTRIBUTES structure.
 
  LPVOID lpSecurityDescriptor;//Pointer to a security descriptor for the object that controls the sharing of it.
 
  BOOL bInheritHandle;//Specifies whether the returned handle is inherited when a new process is created. If this member is TRUE, the new process inherits the handle.
 
} SECURITY_ATTRIBUTES,*PSECURITY_ATTRIBUTES;

2.进程内核对象句柄表handle table) 一个进程在初始化时,系统将为他分配一个句柄表属于进程),这个句柄表仅供内核对象使用,不适用与用户对象和GDI

访问掩码即访问权限,标志即是否继承


2.1创建内核对象:

  一个进程首次初始化时其句柄表(句柄表属于当前进程为空。当进程内的一个线程调用会创建内核对象的函数时,内核将为这个对象分配并初始化一个内存块,然后,内核扫描进程的句柄表,查找一个空白的记录项。然后设置结构中的值。

  用于创建内核对象的任何函数都会返回一个与进程相关的句柄,这个句柄可由同一个进程中的所有线程使用。系统使用索引来表示内核对象的信息保存在进程句柄表中的具体位置,要得到实际的索引值,句柄值实际应该除以4(以忽略windows内部使用的最后2位)。

  在调用一个函数时,如果他接受一个内核对象句柄作为参数,就必须把创建内核对象的函数返回的句柄传给他。在该进程内部,这个函数会查找进程的句柄表,获得目标内核对象的地址,然后操作对象的数据结构。当然,由于句柄值实际是作为进程句柄表的索引来使用的,所以这些句柄是与当前这个进程相关的,无法供其他进程使用。注意不同创建内核对象函数的返回值,在失败时会是什么 

2.2 关闭内核对象:

    调用CloseHandle结束使用对象BOOL CloseHandle(HANDLE hobject);//它并没有真正的关闭内核对象,只是将计数减1,也就是说,这个时候,如果这个内核对象的引用计数不为0的话,内核对象依然存在,如果你有办法找到他,那么你依然可以操作他。

     在CloseHandle函数返回前会清除进程句柄表中对应的记录项。进程无法访问那个内核对象。

     若没有调用该函数,该进程运行期间,将可能造成内核对象泄露。但在进程终止运行时,系统会自动扫描进程的句柄表。若表中拥有任何无效项目(进程终止前没关闭的对象),系统将关闭这些对象的句柄。对象的计数器被置0,内核便会撤销这些对象。

 3.跨进程边界共享内核对象
进程共享内核对象:
1.1. 使用对象句柄继承   进程之间是父子关系时,才可使用。将 SECURITY_ATTRIBUTES中的 bInheritHandle设置为true。然后使用
CreateProcess函数由父进程创建子进程。注意:对象句柄的继承只会在生成子进程的时候发生
  
  
BOOL CreateProcess(
LPCTSTR lpApplicationName,// name of executable module
LPTSTR lpCommandLine,// command line string
LPSECURITY_ATTRIBUTES lpProcessAttributes,// SD
LPSECURITY_ATTRIBUTES lpThreadAttributes,// SD
BOOL bInheritHandles,// handle inheritance option
DWORD dwCreationFlags,// creation flags
LPVOID lpEnvironment,// new environment block
LPCTSTR lpCurrentDirectory,// current directory name
LPSTARTUPINFO lpStartupInfo,// startup information
LPPROCESS_INFORMATION lpProcessInformation // process information
);
注意 bInheritHandles参数,值为true时,子进程就会继承父进程的“可继承的句柄”的值。函数执行过程:
[ 系统会为子进程创建一个新的、空白的进程句柄表——就像它为任何一个新进程所做的那样。系统会遍历父进程的句柄表,对它的每一个记录项进行检查。凡是包含一个有效的“可继承的句柄”的项,都会被完整地拷贝到子进程的句柄表中。在子进程的句柄表中,拷贝项的位置与它在父进程句柄表中的位置是完全一样的。这是非常重要的一个设计,因为它意味着:在父进程和子进程中,对一个内核对象进行标识的句柄值是完全一样的。内核对象的使用计数将递增。父进程和子进程都要调用CloseHandle()
句柄继承能实现的唯一原因是“共享的内核对象”的句柄值在父进程和子进程中是完全一样的。
其他对象句柄继承方法:
1.让父进程等待子进程初始化完成,再将一条消息发送到由子进程中的一个线程创建的一个窗口。
2.使用环境变量
    1.2. 改变句柄的标志
    时会遇到这样一种情况,父进程创建一个内核对象,以便检索可继承的句柄,然后生成两个子进程。父进程只想要一个子进程来继承内核对象的句柄。 换句话说,有时可能想要控制哪个子进程来继承内核对象的句柄。 若要改变内核对象句柄的继承标志,可以调用SetHandleInformation 函数:
BOOLSetHandleInformation(
   HANDLE hObject,//用于标识一个有效的句柄
   DWORD dwMask,
   DWORD dwFlags);

第二个参数dwMask 告诉该函数想要改变哪个或那几个标志。目前有两个标志与每个句柄相关联:

 

  #define HANDLE FLAG_INHERIT 0x00000001
   #define HANDLE FLAG PROTECT FROM CLOSE0x00000002

如果想同时改变该对象的两个标志,可以逐位用O R 将这些标志连接起来。

第三个参数是dwFlags,用于指明想将该标志设置成什么值。

例如,若要打开一个内核对象句柄的继承标志,请创建下面的代码:

SetHandleInformation(hobj, HANDLE_FLAG_INHERIT,  HANDLE_FLAG_INHERIT);

若要关闭该标志,请创建下面的代码:

SetHandleInformation(hobj,HANDLE_FLAG_INHERIT, 0);

HANDLE_FLAG_PROTECT_FROM_CLOSE标志用于告诉系统,该句柄不应该被关闭:

   SetHandleInformation(hobj,HANDLE_FLAG_PROTECT_FROM_CLOSE,HANDLE_FLAG_PROTECT_FROM_CLOSE);

   CloseHandle(hobj); //Exception is raised

如果一个线程试图关闭一个受保护的句柄,CloseHandle 就会产生一个异常条件。很少想要将句柄保护起来,使他人无法将它关闭。但是如果一个进程生成了子进程,而子进程又生成了孙进程,那么该标志可能有用。父进程可能希望孙进程继承赋予子进程的对象句柄。不过,子进程有可能在生成孙进程之前关闭该句柄。如果出现这种情况,父进程就无法与孙进程进行通信,因为孙进程没有继承该内核对象。通过将句柄标明为“受保护不能关闭”,那么孙进程就能继承该对象。

但是这种处理方法有一个问题。子进程可以调用下面的代码来关闭HANDLE_FLAG_PROTECT_FROM_CLOSE标志,然后关闭句柄。

   SetHandleInformation(hobj, HANDLEMFLAG_PROlECl_FROM_CLOSE,  0);

   CloseHandle(hobj);

GetHandleInformation函数:

BOOLGetHandleInformation(HANDLE hObj,PDWORD pdwFlags);

该函数返回pdwFlags指向的DWORD 中特定句柄的当前标志的设置值。

若要了解句柄是否是可继承的,请使用下面的代码:

   DWORD dwFlags;
   GetHandleInformation(hObj, &dwFlags);
   BOOL fHandleIsInheritable = (0 != (dwFlags& HANDLE_FLAG_INHERIT));
3.为对象命名
3.1  共享跨越进程边界的内核对象的第二种方法是给对象命名,注意有些内核对象是不可以命名的,但多数内核对象可以命名 (例如绘图程序和浏览器共享)

下面的所有函数都可以创建命名的内核对象:

    
    
HANDLE CreateMutex(
PSLCURITY_ATTRIBUTES psa,
BOOL bInitialOwner,
PCTSTR pszName);
HANDLE CreateEvent(
PSECURITY_ATTRIBUTES psa,
BOOL bManualReset,
BOOL bInitialState,
PCTSTR pszName);
HANDLE CreateSemaphore(
PSECURITY_ATTRIBUTES psa,
LONG lInitialCount,
LONG lMaximumCount,
PCTSTR pszName);
HANDLE CreateWaitableTimer(
PSLCURITY_ATTRIBUTES psa,
BOOL bManualReset,
PCTSTR pszName);
HANDLE CreateFileMapping(
HANDLE hFile,
PSECURITY_ATTRIBUTES psa,
DWORD flProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
PCTSTR pszName);
HANDLE CreateJobObject(
PSECURITY_ATTRIBUTES psa,
PCTSTR pszName);

所有这些对象都共享单个名空间。因此下面的用法是错的。

HANDLE hMutex = CreateMutex(NULL. FALSE, "JeffObj");
HANDLE hSem
= CreateSemaphore(NULL, 1, 1, "JeffObj");
DWORD dwErrorCode
= GetLastError();
所以为了防止名字的冲突,建议创建一个GUID ,并将GUID的字符串表达式用作对象名。
  如何用命名对象来共享对象

Process A 启动运行,并调用下面的函数:

HANDLE hMutexPronessA = CreateMutex(NULL, FALSE, "JeffMutex");

另一进程ProcessB(不一定是Process A 的子进程)启动运行时,执行下面的代码:

HANDLE hMutexProcessB = CreateMutex(NULL, FALSE, "JeffMutex");

当Process B调用CreateMutex时,系统首先要查看是否已经存在一个名字为“JeffMutex ”的内核对象。

由于确实存在一个带有该名字的对象,因此内核要检查对象的类型。由于试图创建一个互斥对象,而名字为“JeffMutex ”的对象也是个互斥对象,因此系统会执行一次安全检查,以确定调用者是否拥有对该对象的完整的访问权。如果拥有这种访问权,系统就在ProcessB的句柄表中找出一个空项目,并对该项目进行初始化,使该项目指向现有的内核对象。如果该对象类型不匹配,或者调用者被拒绝访问,那么CreateMutex 将运行失败(返回NULL)。运行成功时调用GetLastError()得到ERROR_ALREADY_EXISTS。通过getlasterror可得知刚才自己是创建了一个新的内核对象还是仅仅打开了一个现有的:

     
     
HANDLE hMutex =CreateMutex(&sa, FALSE,"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.
}

按名字共享对象的另一种方法

不调用Create*函数,而是调用Open*函数:

     
     
HANDLE OpenMutex(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName);
 
HANDLE OpenEvent(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName);
 
HANDLE OpenSemaphore(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName),
 
HANDLE OpenWaitableTimer(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName);
 
HANDLE OpenFileMapping(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName);
 
HANDLE Openjob0bject(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName);
如果存在带有指定名字的内核对象,并且它是相同类型的对象,那么系统就要查看是否允许执行所需的访问(通过dwDesiredAccess参数进行访问)。如果拥有该访问权,调用进程的句柄表就被更新,对象的使用计数被递增。如果为bInheritHandle 参数传递TRUE,那么返回的句柄将是可继承的。
如果对象并不存在,那么Create*函数将创建该对象,而Open*函数则运行失败。
注意:Microsoft没有提供任何专门的机制来保证我们创建独一无二的对象名。
 3.2 .终端服务命名空间
终端服务能够稍稍改变上面所说的情况。终端服务拥有内核对象的多个名字空间。

4.复制对象句柄
使用 DuplicateHandle 函数, 这个函数获得一个进程的句柄表中的一个记录项,然后在另一个进程的句柄表中创建这个记录项的一个副本.
hSourceProcessHandle进程中的 hSourceHandle记录项复制给 hTargetProcessHandle 进程中的 lpTargetHandle 记录项
    
    
BOOL DuplicateHandle(
HANDLE hSourceProcessHandle,// handle to source process 当前的源进程句柄
HANDLE hSourceHandle,// handle to duplicate 当前的资源句柄,即内核对象句柄
HANDLE hTargetProcessHandle,// handle to target process 目标进程的句柄
LPHANDLE lpTargetHandle,// duplicate handle 要得到的目的句柄,这里应该是一个变量,使用指针,DuplicateHandle函数会将得到的句柄设置到这个参数上
DWORD dwDesiredAccess,// requested access 访问的方式
BOOL bInheritHandle,// handle inheritance option 能不能被得到的其的进程的子进程继承
DWORD dwOptions // optional actions
);
  dwOptions:  可以为0或以下两个对象的组合
    DUPLICATE_SAME_ACCESS    表明我们希望目标句柄拥有与源进程的句柄一样的访问掩码。  
     DUPLICATE_CLOSE_SOURCE :会关闭源进程中的句柄,内核对象的“使用计数”不会受到影响。  
    
    
具体应用如下:
#include<stdio.h>
#include<windows.h>
int main(void)
{
//进程 A 创建一个名为 "MYOBJ"的 互斥量
HANDLE hObjInProcessA =CreateMutex(NULL, FALSE,"MYOBJ");
//判断,如果已经创建了这个互斥量就打印 "run error\n"
if(GetLastError()== ERROR_ALREADY_EXISTS)
{
printf("run error\n");
system("pause");
return0;
}
//获得进程B的进程句柄(7036为进程B的PID,随便弄了个做了下试验)
HANDLE hProcessB =OpenProcess(PROCESS_ALL_ACCESS, FALSE,7036);
// hObjInProcessB是相对于 进程 B的句柄,他标示的对象就由"进程A中代码引用的 hObjInProcessA"所标示的对象.
HANDLE hObjInProcessB;
DuplicateHandle(GetCurrentProcess(),
hObjInProcessA,
hProcessB,
&hObjInProcessB,
0,
FALSE,
DUPLICATE_SAME_ACCESS);
//收尾工作.
//ps: hObjInProcessB千万不能关掉
CloseHandle(hProcessB);
CloseHandle(hObjInProcessA);
printf("run \n");
system("pause");
return0;
}
以上程序第一次运行的时候把互斥量句柄 
hObjInProcessA复制到进程B的句柄表中,用变量名
 
hObjInProcessB来保存.
此时A退出,B不退出,
再运行A,就会打印 run error 的提示,表明互斥量复制成功,已存在于B进程中.

   4.2还可以通过另一种方式来使用 

DuplicateHandle:假设一个进程有对一个文件映射对象的读写权限.在程序中的某个位置,我们要调用一个函数,并希望它对文件映像对象进行只读访问.为了使应用程序变得更健壮,可以使用
 
DuplicateHandle 为现有的对象创建一个新句柄,并确保这个这个新句柄只有只读权限.然后,把这个只读权限的句柄传递给函数.

  
  
HANDLE hFileMapRW =CreateFileMapping(INVALID_HANDLE_VALUE,
NULL,
PAGE_READWRITE,
0,
10240,
NULL);
HANDLE hFileMapRO;
DuplicateHandle(GetCurrentProcess(), hFileMapRW,
GetCurrentProcess(),&hFileMapRO,
FILE_MAP_READ, FALSE,0);
//调用自己的函数
ReadFromTheFileMapping(hFileMapRO);
CloseHandle(hFileMapRO);
CloseHandle(hFileMapRW);

            


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值