windows笔记-跨越进程边界共享内核对象【对象句柄的继承性】
时间:2010-08-31 来源:fangyukuan
都是一些概念性的东西,看得可能会有点烦,不过看了后面多线程和内存管理再回过头来看,会有不一样的感觉。
许多情况下,在不同进程中运行的线程需要共享内核对象。下面是为何需要共享的原因:
• 文件映射对象使你能够在同一台机器上运行的两个进程之间共享数据块。
• 邮箱和指定的管道使得应用程序能够在连网的不同机器上运行的进程之间发送数据块。
• 互斥对象、信标和事件使得不同进程中的线程能够同步它们的连续运行,这与一个应用程序在完成某项任务时需要将情况通知另一个应用程序的情况相同。
跨越进程边界共享内核对象有三种方法:
- 对象句柄的继承性
- 命名对象
- 复制对象句柄
对象句柄的继承性
只有当进程具有父子关系时,才能使用对象句柄的继承性。
首先,当父进程创建内核对象时,必须向系统指明,它希望对象的句柄是个可继承的句柄。(虽然内核对象句柄具有继承性,但是内核对象本身不具备继承性。)
若要创建能继承的句柄,父进程必须指定一个SECURITY_ATTRIBUTES结构并对它进行初始化,然后将该结构的地址传递给特定的Create***函数。下面的代码用于创建一个互斥对象,并将一个可继承的句柄返回给它:
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecuntyDescriptor = NULL;
sa.bInheritHandle = TRUE;
HANDLE hMutex = CreateMutex(&sa, FALSE,NULL);
存放在进程句柄表项目中的标志。每个句柄表项目都有一个标志位,用来指明该句柄是否具有继承性。如果将bInheritHandle 成员置为TRUE ,那么该标志位将被置为1 。
包含两个有效项目的进程句柄表
索引 |
内核对象内存块的指针 |
访问屏蔽(标志位的D W O R D ) |
标志(标志位的D W O R D ) |
1 |
0 x F 0 0 0 0 0 0 0 |
0 x ? ? ? ? ? ? ? ? |
0 x 0 0 0 0 0 0 0 0 |
2 |
0 x 0 0 0 0 0 0 0 0 |
(无) |
(无) |
3 |
0 x F 0 0 0 0 0 1 0 |
0 x ? ? ? ? ? ? ? ? |
0 x 0 0 0 0 0 0 0 1 |
让父进程生成子进程
使用对象句柄继承性时要执行的下一个步骤是让父进程生成子进程。这要使用CreateProcess函数来完成:
BOOLCreateProcess(
PCTSTR pszApplicationName,
PTSTR pszCommandLine,
PSECURITY_ATTRIBUTES psaProcess,
PSECURITY_ATTRIBUTES psaThread,
BOOL bInheritHandles,
DWORD fdwCreale,
PVOIO pvEnvironment,
PCTSTR pszCurDir,
PSTARTUPINFO psiStartInfo,
PPROCESS_INFORMATION ppiProcInfo);
bInheritHandle参数传递TRUE ,那么子进程就可以继承父进程的可继承句柄值。当传递TRUE 时,操作系统就创建该新子进程,但是不允许子进程立即开始执行它的代码。当然,系统为子进程创建一个新的和空的句柄表,就像它为任何新进程创建句柄表那样。不过,由于将TRUE 传递给 了CreateProcess 的bInheritHandles 参数,因此系统要进行另一项操作,即它要遍历父进程的句柄表,对于它找到的包含有效的可继承句柄的每个项目,系统会将该项目准确地拷贝到子进程的句柄表中。该项目拷贝到子进程的句柄表中的位置将与父进程的句柄表中的位置完全相同。这个情况非常重要,因为它意味着在父进程与子进程中,标识内核对象所用的句柄值是相同的。
除了拷贝句柄表项目外,系统还要递增内核对象的使用计数,因为现在两个进程都使用该对象。如果要撤消内核对象,那么父进程和子进程必须调用 该对象上的CloseHandle函数,也可以终止进程的运行。子进程不必首先终止运行,但是父进程也不必首先终止运行。实际上,CreateProcess函数返回后,父进程可以立即关闭对象的句柄,而不影响子进程对该对象进行操作的能力。
子进程为了确定它期望的内核对象的句柄值,最常用的方法是将句柄值作为一个命令行参数传递给子进程,该子进程的初始化代码对命令行进行分析(通常通过调用s s c a n f 函数来进行分析),并取出句柄值。一旦子进程拥有该句柄值,它就具有对该对象的无限访问权。句柄继承权起作用的唯一原因是,父进程和子进程中的共享内核对象的句柄值是相同的,这就是为什么父进程能够将句柄值作为命令行参数来传递的原因。
当然,可以使用其他形式的进程间通信,将已继承的内核对象句柄值从父进程传送给子进程。方法之一是让父进程等待子进程完成初始化,然后,父进程可以将一条消息发送或展示在子进程中的一个线程创建的窗口中。
另一个方法是让父进程将一个环境变量添加给它的环境程序块。该变量的名字是子进程知道要查找的某种信息,而变量的值则是内核对象要继承的值。这样,当父进程生成子进程时,子进程就继承父进程的环境变量,并且能够非常容易地调用GetEnvironmentVariable函数,以获取被继承对象的句柄值。如果子进程要生成另一个子进程,那么使用这种方法是极好的,因为环境变量可以被再次继承。
改变句柄的标志
有时会遇到这样一种情况,父进程创建一个内核对象,以便检索可继承的句柄,然后生成两个子进程。父进程只想要一个子进程来继承内核对象的句柄。
换句话说,有时可能想要控制哪个子进程来继承内核对象的句柄。
若要改变内核对象句柄的继承标志,可以调用SetHandleInformation 函数:
BOOLSetHandleInformation(
HANDLE hObject,
DWORD dwMask,
DWORD dwFlags);
第一个参数hObject 用于标识一个有效的句柄。
第二个参数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));