CSDN博客

img collision

windows程序设计 and windows核心编程(内核对象理论)

发表于2004/10/17 10:10:00  1332人阅读

windows程序设计 and windows核心编程(内核对象理论)

 

一、什么是内核对象

作为一个Windows软件开发人员,你经常需要创建、打开和操作各种内核对象。系统要创建和操作若干类型的内核对象,比如存取符号对象、事件对象、文件对象、文件映射对象、I / O完成端口对象、作业对象、信箱对象、互斥对象、管道对象、进程对象、信标对象、线程对象和等待计时器对象等。这些对象都是通过调用函数来创建的。例如, CreateFileMapping函数可使系统能够创建一个文件映射对象。每个内核对象只是内核分配的一个内存块,并且只能由该内核访问。该内存块是一种数据结构,它的成员负责维护该对象的各种信息。有些数据成员(如安全性描述符、使用计数等)在所有对象类型中是相同的,但大多数数据成员属于特定的对象类型。例如,进程对象有一个进程I D、一个基本优先级和一个退出代码,而文件对象则拥有一个字节位移、一个共享模式和一个打开模式。由于内核对象的数据结构只能被内核访问,因此应用程序无法在内存中找到这些数据结构并直接改变它们的内容。Microsoft规定了这个限制条件,目的是为了确保内核对象结构保持状态的一致。这个限制也使Microsoft能够在不破坏任何应用程序的情况下在这些结构中添加、删除和修改数据成员。

如果我们不能直接改变这些数据结构,那么我们的应用程序如何才能操作这些内核对象呢?解决办法是,Windows提供了一组函数,以便用定义得很好的方法来对这些结构进行操作。这些内核对象始终都可以通过这些函数进行访问。当调用一个用于创建内核对象的函数时,该函数就返回一个用于标识该对象的句柄。该句柄可以被视为一个不透明值,你的进程中的任何线程都可以使用这个值。将这个句柄传递给Windows的各个函数,这样,系统就能知道你想操作哪个内核对象。

1内核对象的使用计数

内核对象由内核所拥有,而不是由进程所拥有。换句话说,如果你的进程调用了一个创建内核对象的函数,然后你的进程终止运行,那么内核对象不一定被撤消。在大多数情况下,对象将被撤消,但是如果另一个进程正在使用你的进程创建的内核对象,那么该内核知道,在另一个进程停止使用该对象前不要撤消该对象,必须记住的是,内核对象的存在时间可以比创建该对象的进程长。

2、安全性

内核对象能够得到安全描述符的保护。安全描述符用于描述谁创建了该对象,谁能够访问或使用该对象,谁无权访问该对象。创建内核对象是由SECURITY_ATTRIBUTES结构标识。可以在实际编程时参考源代码学习其用法。

 

二、进程的内核对象句柄表

当一个进程被初始化时,系统要为它分配一个句柄表。该句柄表只用于内核对象,不用于用户对象或GDI对象。句柄表的详细结构和管理方法并没有具体的资料说明。可能如下:

1、创建内核对象

    创建内核对象时,如:HANDLE h = CreateMutex(NULL, FALSE, "HEHE");则返回值(句柄值)即为上表中的索引,而在内核对象内存块儿指针则填入第二栏。

2、关闭内核对象

无论怎样创建内核对象,都要向系统指明将通过调用C l o s e H a n d l e来结束对该对象的操作:

BOOL CloseHandle(HANDLE hObj);

该函数首先检查调用进程的句柄表,以确保传递给它的索引(句柄)用于标识一个进程实际上无权访问的对象。如果该索引是有效的,那么系统就可以获得内核对象的数据结构的地址,并可确定该结构中的使用计数的数据成员。如果使用计数是0,该内核便从内存中撤消该内核对象。

 

三、跨越进程边界共享内核对象

1、通过继承共享对象句柄

通过在创建子进程的过程中,将父进程的可用内核对象句柄暴露给子进程。继承性的实现需要两步实现:

A.   父进程在创建内核对象时要明确指出此对象是可继承的:

SECURITY_ATTRIBUTES sa;

Sa.nLength = sizeof(sa);

Sa.lpSecurityDescriptor = NULL;

Sa.bInheritHandle = TRUE;

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

B. 父进程在生成子进程时,要明确表明子进程可以继承自己的内核对象:

BOOL CreateProcess (

  LPCTSTR lpApplicationName,

                         // pointer to name of executable module

  LPTSTR lpCommandLine,  // pointer to command line string

  LPSECURITY_ATTRIBUTES lpProcessAttributes, 

                                   // process security attributes

  LPSECURITY_ATTRIBUTES lpThreadAttributes,  

                                   // thread security attributes

  BOOL bInheritHandles,  // handle inheritance flag

  DWORD dwCreationFlags, // creation flags

  LPVOID lpEnvironment,  // pointer to new environment block

  LPCTSTR lpCurrentDirectory,   // pointer to current directory name

  LPSTARTUPINFO lpStartupInfo,  // pointer to STARTUPINFO

  LPPROCESS_INFORMATION lpProcessInformation 

                                         // pointer to PROCESS_INFORMATION

);

参数bInheritHandleTRUE时,子进程就可以继承父进程的可继承句柄值。但是不允许子进程立即开始执行它的代码。当然,系统为子进程创建一个新的和空的句柄表,就像它为任何新进程创建句柄表那样。不过,由于将TRUE传递给了CreateProcessbInheritHandles参数,因此系统要进行另一项操作,即它要遍历父进程的句柄表,对于它找到的包含有效的可继承句柄的每个项目,系统会将该项目准确地拷贝到子进程的句柄表中。该项目拷贝到子进程的句柄表中的位置将与父进程的句柄表中的位置完全相同。这个情况非常重要,因为它意味着在父进程与子进程中,标识内核对象所用的句柄值是相同的。除了拷贝句柄表项目外,系统还要递增内核对象的使用计数,因为现在两个进程都使用该对象。如果要撤消内核对象,那么父进程和子进程必须调用该对象上的CloseHandle数,也可以终止进程的运行。子进程不必首先终止运行,但是父进程也不必首先终止运行。实际上,CreateProcess函数返回后,父进程可以立即关闭对象的句柄,而不影响子进程对该对象进行操作的能力。应该知道,对象句柄的继承性只有在生成子进程的时候才能使用。如果父进程准备创建带有可继承句柄的新内核对象,那么已经在运行的子进程将无法继承这些新句柄。

    在此介绍一个函数用来改变句柄的标志:

BOOL SetHandleInformation(HANDLE hObject,

                         DWORD dwMask,

                         DWORD dwFlags);

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

#define HANDLE_FLAG_INHERIT     0x00000001

#define HANDLE_FLAG_PROTECT_FROM_CLOSE      0x00000002

第三个参数是d w F l a g s,用于指明想将该标志设置成什么值。

2、通过命名对象共享内核对象

许多(虽然不是全部)内核对象都是可以命名的。如:

HANDLE CreateMutex(

    PSECURITY_ATTRIBUTES psa,

    BOOL bInitialOwner,

    PCTSTR pszName);

最后的参数pszName即为此内核对象的名称。当为该参数传递NULL时,就向系统指明了想创建一个未命名的(匿名)内核对象。当创建一个未命名的对象时,可以通过使用继承DuplicateHand le (下面将要介绍)共享跨越进程的对象。若要按名字共享对象,必须为对象赋予一个名字。

按名字共享对象的另一种方法是,进程不调用Create*函数,而是调用Open*函数中的某一个如:

HANDLE OpenMutex(

    PSECURITY_ATTRIBUTES psa,

    BOOL bInitialOwner,

    PCTSTR pszName);

调用Create*函数与调用Open*函数之间的主要差别是,如果对象并不存在,那么Create*函数将创建该对象,而Open*函数则运行失败。

3、通过复制对象句柄共享内核对象

共享跨越进程边界的内核对象的最后一个方法是使用DuplicateHandle函数:

BOOL DuplicateHandle (
 HANDLE hSourceProcessHandle,  // handle to the source process
  HANDLE hSourceHandle,         // handle to duplicate
  HANDLE hTargetProcessHandle,  // handle to process to duplicate to
  LPHANDLE lpTargetHandle,  // pointer to duplicate handle
  DWORD dwDesiredAccess,    // access for duplicate handle
  BOOL bInheritHandle,      // handle inheritance flag
  DWORD dwOptions           // optional actions
);

简单说来,该函数取出一个进程的句柄表中的项目,并将该项目拷贝到另一个进程的句柄表中。D u p l i c a t e H a n d l e函数配有若干个参数,但是实际上它是非常简单的。D u p l i c a t e H a n d l e函数最普通的用法要涉及系统中运行的3个不同进程。具体编程细节可参考相关例程。

阅读全文
0 0

相关文章推荐

img
取 消
img