共享内核对象之--跨边界共享内核对象

来源:互联网 发布:网络信息发展趋势 编辑:程序博客网 时间:2024/06/05 09:27

         了解内核对象创建的程序员都知道进程中的内核对象句柄一般是相对于进程的,也就是说它是进程内有效,虽然 内存对象句柄指向的是操作系统的一段内存但是我们通过理解每个进程的句柄表知道它们只是记录了句柄表的索引。所以当我们在一个进程中需要访问另外一个进程中的句柄是不能直接通过另一进程的句柄标识。虽然访问不一定失败但是访问的是完全不同资源。

         查阅了资料我发现共享内存对象可以通过三种不同的机制:(1)使用对象句柄继承  (2) 为对象命名 (3)复制对象句柄。

 首先我们来学习下使用对象句柄继承。

        使用对象句柄继承来共享内核对象有一个条件-----只有父子进程之间才可以使用对象句柄继承,父进程有一个和多个内核对象可以使用,当父进程决定要创建一个子进程的时候可以决定是否子进程可以共享父进程的内核对象。

        父子进程之间共享内核对象的主要步骤如下:

                 (1) 父进程为了让自己创建的子进程能够继承自己的内核对象,父进程在创建子进程的时候需要设定一个结构体SECURITY_ATTRIBUTES 结构体并将这个结构体传给相应的create *函数,创建进程的函数为createProcess();

                       SECURITY_STTRIBUTES sa;

                       sa.nLength = sizeof(sa);

                       sa.lpSecurityDescriptor = NULL;

                       sa.BinheritHandle = TRUE;   //    此行使子进程可以继承父进程的句柄。

                       HANDLE hMutex = CreateMutex(&sa,FALSE,NULL);

                     

           为了使用对象句柄继承,下一步是由父进程生成子进程。这是通过 CreateProcess 函数来完成的,如下所示:
                        BOOL CreateProcess(
                                                           PCTSTR pszApplicationName,
                                                           PTSTR pszCommandLine,
                                                           PSECURITY_ATTRIBUTES psaProcess,
                                                           PSECURITY_ATTRIBUTES psaThread, 
                                                           BOOL bInheritHandles,
                                                           DWORD dwCreationFlags,
                                                           PVOID pvEnvironment,
                                                           PCTSTR pszCurrentDirectory, 
                                                           LPSTARTUPINFO pStartupInfo,
                                                           PPROCESS_INFORMATION pProcessInformation);

         这样当我们通过设置SECURITY_ATTRIBUTES中的BinheritHandle 为真来来使子进程获得父进程的句柄。现在请注意 bInheritHandles参数。通常,在生成一个
进程时,要向该参数传递 FALSE。这个值向系统表明:你不希望子进程继承父进程句柄表中的“可继承的句柄”。
 
        相反,如果向这个参数传递 TRUE,子进程就会继承父进程的“可继承的句柄”的值。传递TRUE 时,操作系统会创建新的子进程,但不允许子进程立即执行它的代码。当然,系统会为子进程创建一个新的、空白的进程句柄表——就像它为任何一个新进程所做的那样。但是,由于你向 CreateProcess 函数的 bInheritHandles 参数传递了 TRUE,所以系统还会多做一件事情:它会遍历父进程的句柄表,对它的每一个记录项进行检查。凡是包含一个有效的“可继承的句柄”的项,都会被完整地拷贝到子进程的句柄表。在子进程的句柄表中,拷贝项的位置与它在父进程句柄表中的位置是完全一样的。这是非常重要的一个设计,因为它意味着:在父进程和子进程中,对一个内核对象进行标识的句柄值是完全一样的。

         除了拷贝句柄表的记录项,系统还会递增内核对象的使用计数,因为两个进程现在都在使用这个对象。一个内核对象要想被销毁,父进程和子进程要么都对这个对象调用 CloseHandle,要么都终止运行。子进程不一定先终止——但父进程也不一定。事实上,父进程可以在CreateProcess 函数返回之后立即关闭它的内核对象句柄,子进程照样可以操纵这个对象。

         记住,对象句柄的继承只会在生成子进程的时候发生。假如父进程后来又创建了新的内核对象,并同样将它们的句柄设为可继承的句柄。那么正在运行的子进程是不会继承这些新句柄的。

         对象句柄继承还有一个非常奇怪的特征:子进程并不知道自己继承了任何句柄。在子进程的文档中,应指出当它从另一个进程生成时,希望获得对一个内核对象的访问权——只有在这种情况下,内核对象的句柄继承才是有用的。通常,父和子应用程序是由同一家公司编写的;但是,假如在公司的文档中,已经指出子应用程序有这方面的期待,另一家公司就可以据此来编写一个子应用程序。 
  

跨边界共享内核对象之:为对象命名

          许多(但不是全部)内核对象都可以进行命名。例如,以下所有函数都可以创建命名的内核对象:

                   HANDLE CreateMutex(   PSECURITY_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( PSECURITY_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);

 
          所有这些函数的最后一个参数都是 pszName。向此参数传入 NULL,相当于向系统表明你要创建一个未命名的(即匿名)内核对象。如果创建的是一个无名对象,可以利用上一节讨论过的继承技术,或者利用下一节即将讨论的 DuplicateHandle 函数来实现进程间的对象共享。如果要根据对象名称来共享一个对象,你必须为此对象指定一个名称。 
         如果不为 pszName 参数传递 NULL,则应该传入一个“以 0来终止的名称字符串”的地址。这个名称可以长达 MAX_PATH 个字符(定义为 260)。遗憾的是,Microsoft 没有提供任何专门的机制来保证为内核对象指定的名称是惟一的,。例如,假如你试图创建一个名为"JeffObj"的对象,那么没有任何一种机制来保证当前不存在一个名为"JeffObj"的对象。更糟的是,所有这些对象都共享同一个命名空间,即使它们的类型并不相同。例如,以下CreateSemaphore 函数调用肯定会返回 NULL,因为已经有一个同名的 mutex 对象了:  
         HANDLE hMutex = CreateMutex(NULL, FALSE, TEXT("JeffObj")); 
         HANDLE hSem = CreateSemaphore(NULL, 1, 1, TEXT("JeffObj"));
         DWORD dwErrorCode = GetLastError();

          执行上述代码之后,如果检查 dwErrorCode 的值,会发现返回的代码为 6 (ERROR_INVALID_HANDLE)。这个错误代码当然说明不了什么问题,不过我们目前对
此无能为力。 
 
       知道如何命名对象之后,接着来看看如何以这种方式共享对象。假设 Process A 启动并调以下函数:  
         HANDLE hMutexProcessA = CreateMutex(NULL, FALSE, TEXT("JeffMutex"));

      这个函数调用创建一个新的 mutex内核对象,并将其命名为"JeffMutex"。注意,在 Process A的句柄(表)中,hMutexProcessA 并不是一个可继承的句柄——但是,通过为对象命名来实现共享时,是否可以继承并非一个必要条件。 
       在以后某个时间,假定某个进程生成了 Process B。Process B 不一定是 Process A 的子进程;它可能是从 Windows 资源管理器或者其他某个应用程序生成的。利用对象的名称(而非利用继承)来共享内核对象时,最大的一个优势就是“Process B 不一定是 Process A的子进程”。Process B 开始执行时,它执行以下代码:
        HANDLE hMutexProcessB = CreateMutex(NULL, FALSE, TEXT("JeffMutex"));

         当 Process B发出对 CreateMutex 的调用时,系统首先会查看是否存在一个名为“JeffMutex”的内核对象。由于确实存在这样的一个对象,所以内核接着检查对象的类型。由于试图创建一个 mutex,而名为“JeffMutex”的对象也是一个 mutex,所以系统接着执行一次安全检查,验证调用者是否拥有对象的完全访问权限。如果答案是肯定的,系统就会在 Process B 的句柄表中查找一个空白记录项,并将其初始化为指向现有的内核对象。如果对象的类型不匹配,或调用者被拒绝访问,CreateMutex就会失败(返回ULL)。  

         Process B 调用 CreateMutex 成功之后,不会实际地创建一个 mutex。相反,会为 Process B分配一个新的句柄值(当然,和所有句柄值一样,这是一个相对于该进程的句柄值),它标识了内核中的一个现有的 mutex 对象。当然,由于在 Process B 的句柄表中,用一个新的记录项来引用了这个对象,所以这个 mutex 对象的使用计数会被递增。在 Process A 和 Process B 都关闭这个对象的句柄之前,该对象是不会销毁的。注意,两个进程中的句柄值极有可能是不同的值。这没有什么关系。Process A用它自己的句柄值来引用那个 mutex 对象,ProcessB 也用它自己的句柄值来引用同一个 mutex 对象。

 

共享内核对象之:复制对象句柄 

               为了跨越进程边界来共享内核对象,最后一个技术是使用 DuplicateHandle 函数:  
                                      BOOL DuplicateHandle(HANDLE hSourceProcessHandle, HANDLE hSourceHandle, HANDLE hTargetProcessHandle,PHANDLE phTargetHandle,
                                                                             DWORD dwDesiredAccess,
                                                                             BOOL bInheritHandle,
                                                                             DWORD dwOptions);

 

              调用 DuplicateHandle 时,它的第一个参数和第三个参数——hSourceProcessHandle 和hTargetProcessHandle—— 是内核对象句柄。这两个句柄本身必须相对于调用
DuplicateHandle 函数的那个进程。此外,这两个参数标识的必须是进程内核对象;如果你传递的句柄指向的是其他类型的内核对象,函数调用就会失败。我们将在第 4 章详细讨论进程内核对象。就目前来说,你只需知道一旦在系统中调用了一个新的进程,就会创建一个进程内核对象。
 
第二个参数 hSourceHandle 是指向任何类型的内核对象的一个句柄。但是,它的句柄值一定不能相对于调用 DuplicateHandle 函数的那个进程。相反,该句柄必须相对于
hSourceProcessHandle 句柄所标识的那个进程。第四个参数是 phTargetHandle,它是一个HANDLE 变量的地址;在 hTargetProcessHandle(第四个参数)标识的目标进程的句柄表中,会拷贝源进程(第一个参数)中的一个源内核对象句柄(第二个参数)的句柄信息。这些句柄信息会拷贝到句柄表的一个记录项中。而在第四个参数 phTargetHandle 指向的那个变量中,将接收与这个记录项对应的句柄值。