内核对象:系统会创建几种类型的内核对象,比如文件对象,互斥量对象,管道对象,信号量对象。。。
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
);
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);
如何用命名对象来共享对象所有这些对象都共享单个名空间。因此下面的用法是错的。
所以为了防止名字的冲突,建议创建一个GUID ,并将GUID的字符串表达式用作对象名。HANDLE hMutex = CreateMutex(NULL. FALSE, "JeffObj");
HANDLE hSem = CreateSemaphore(NULL, 1, 1, "JeffObj");
DWORD dwErrorCode = GetLastError();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还可以通过另一种方式来使用
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);