Windows高级编程之内核对象

3.1、内核对象的分类: Event Objects 事件对象 File-mapping Objects 文件映射对象 File Objects 文件对象 Mailslot Objects 邮件槽对象 Mutex Objects 互斥对象 Pipe Objects 管道对象 Process Objects 进程对象 Semaphore Objects 信号量对象 Thread Objects 线程对象内核对象是由内核分配的一块内存,而且只能由内核访问。这一内存块是一个数据结构,成员包含该对象的信息。一些成员(对象名称、安全描述、使用计数等)对于所有的对象都是一样的,但大部分是针对某一特定对象类型的。内核对象数据结构只能由内核访问,应用程序不可能在内存中定位这些数据结构和直接改变它们的内容。使用Win32 API函数操作内核对象。当调用创建内核对象的函数时,函数返回一个标识该对象的句柄,可以把这一句柄想象为32位的数值,它能为进程中的任意线程使用。句柄的值都是进程相关的,所以如果把这一句柄值传递给另一进程中的某一线程(利用进程间通讯机制),当另一进程试图使用该句柄时,就会产生错误。 3.1.1 使用计数内核为每个内核对象使用计数,使用计数是所有内核对象都拥有的数据成员,但对象被创建和访问时,使用计数增1,对象终结时,内核自动把该进程使用的所有内核对象的使用计数减1.但某一对象的使用计数为0,内核释放该对象。 3.1.2 安全内核对象使用安全描述保护。编写服务器应用的时候通常使用安全描述,编写客户端的应用则不用管它。大多数应用程序传递NULL为参数,使用缺省的安全属性,即管理员和对象的创建者对它有完全的访问权限,其他都不能访问该对象。分配一个SECURITY_ATTRIBUTES结构,初始化后,把它的地址作为参数进行传递。SECURITY_ATTRIBUTES结构如下所示: typedef struct _SECURITY_ATTRIBUTES{ DWORD bLength; LPVOID lpSecurityDescriptor; BOOL bInheritHandle; }SECURITY_ATTRIBUTES; 但想要获得对某一已存在的访问时,必须指定将要对该对象进行的操作。除了内核对象外,应用程序可能使用其他类型的对象,如菜单、窗口、鼠标光标、刷子和字体等,这些对象是用户或者GDI对象,而不是内核对象。区分用户对象还是内核对象的方法是查看该对象在Win32中的创建函数。创建用户或GDI不含有LPSECURITY_ATTRIBUTES属性。 3.2、进程的内核对象句柄表当进程初始化时,系统自动分配一张句柄表,该句柄只用于内核对象。而不用于用户或GDI对象。 3.2.1 创建内核对象当进程首次初始化时,句柄表还是空的。当进程中的某一线程调用了创建内核对象的函数时,内核为该对象分配一块内存,并且初始化它,然后内核扫描进程的句柄表来寻找空白项,成员指针指向内核对象数据结构的内存地址,访问掩码设为完全控制,标志也将被设置。如下列创建内核函数CreateThread/CreateFile/CreateFileMapping/CreateSemaphore 所有创建内核对象的函数都返回一个进程相关的句柄,它能被运行在该进程中的所有线程正确使用,该句柄是进程的句柄表中指明内核对象的信息存储之处的索引。如果创建内核对象的函数失败了,返回句柄通常是0(NULL)。一些函数在失败时返回的句柄值是-1(INVALID_HANDLE_VALUE)。在检查创建核心对象的返回值时应该非常消息。 3.2.2 关闭内核对象关闭内核对象通过调用CloseHandle。BOOL CloseHandle(HANDLE hObj) 该函数首先检查调用进程句柄表,来确认进程是否对传递的索引所指出的对象有访问权。在CloseHandle返回之前,它清除了在进程句柄表中所占的表项。 不管内核对象是否被释放,都要进行清除,在调用CloseHandle后,就不能再访问内核对象了。 3.3 进程间共享内核对象许多情况下,在不同进程中运行的线程需要共享内核对象。下面是为何需要共享的原因: • 文件映射对象使你能够在同一台机器上运行的两个进程之间共享数据块。 • 邮箱和指定的管道使得应用程序能够在连网的不同机器上运行的进程之间发送数据块。 • 互斥对象、信标和事件使得不同进程中的线程能够同步它们的连续运行,这与一个应用程序在完成某项任务时需要将情况通知另一个应用程序的情况相同将句柄设计成与进程相关的句柄,主要是健壮性与安全性。 3.3.1 对象句柄的继承性只有当进程具有父子关系时,才能使用对象句柄的继承性。在这种情况下,父进程可以使用一个或多个内核对象句柄,并且该父进程可以决定生成一个子进程,为子进程赋予对父进程的内核对象的访问权。若要使这种类型的继承性能够实现,父进程必须执行若干个操作步骤。首先,当父进程创建内核对象时,必须向系统指明,它希望对象的句柄是个可继承的句柄。请记住,虽然内核对象句柄具有继承性,但是内核对象本身不具备继承性。若要创建能继承的句柄,父进程必须指定一个SECURITY_ATTRIBUTESS结构并对它进行初始化,然后将该结构的地址传递给特定的Create函数。如下述互斥对象的创建 SECURITY_ATTRBITES sa; sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = NULL; sa.bInheritHandle = TRUE;/*Make the returned handle interitable*/ HANDLE hMutex = CreateMutex(&sa,FALSE,NULL); 每个句柄表项目都有一个标志位,用来指明该句柄是否具有继承性。当创建一个内核对象时,如果传递NULLL作为PSECURITY_ATTRIBUTES的参数,那么返回的句柄是不能继承的,并且该标志位是 0。如果将bInheritHandle成员置为TRUE,那么该标志位将被置为1。使用对象句柄继承性时要执行的下一个步骤是让父进程生成子进程。这要使用CreateProcess函数来完成:参数bInheritHandle在生成进程时,将为该参数传递FALSE,该值告诉系统,不希望子进程继承父进程的句柄表中的可继承句柄。如果为该参数传递TRUE,那么子进程就可以继承父进程的可继承句柄值。当传递TRUE时,操作系统就创建该新子进程,但是不允许子进程立即开始执行它的代码。系统为子进程创建一个新的和空的句柄表,由于将TRUE传递给了CreateProcess的bInheritHandle参数,因此系统要进行另一项操作,即它要遍历父进程的句柄表,对于它找到的包含有效的可继承句柄的每个项目,系统会将该项目准确地拷贝到子进程的句柄表中。该项目拷贝到子进程的句柄表中的位置将与父进程的句柄表中的位置完全相同。这个情况非常重要,因为它意味着在父进程与子进程中,标识内核对象所用的句柄值是相同的。除了拷贝句柄表项目外,系统还要递增内核对象的使用计数,因为现在两个进程都使用该对象。如果要撤消内核对象,那么父进程和子进程必须调用该对象上的CloseHandle函数,也可以终止进程的运行。子进程不必首先终止运行,但是父进程也不必首先终止运行。实际上,CreateProcess函数返回后,父进程可以立即关闭对象的句柄,而不影响子进程对该对象进行操作的能力。 对象句柄的继承性只有在生成子进程的时候才能使用。如果父进程准备创建带有可继承句柄的新内核对象,那么已经在运行的子进程将无法继承这些新句柄。 对象句柄的继承性有一个非常奇怪的特征,那就是当使用它时,子进程不知道它已经继承了任何句柄。子进程为了确定它期望的内核对象的句柄值,最常用的方法是将句柄值作为一个命令行参数传递给子进程,该子进程的初始化代码对命令行进行分析(通常通过调用sscanf函数来进行分析) ,并取出句柄值。一旦子进程拥有该句柄值,它就具有对该对象的无限访问权。请注意,句柄继承权起作用的唯一原因是,父进程和子进程中的共享内核对象的句柄值是相同的,这就是为什么父进程能够将句柄值作为命令行参数来传递的原因。 可以使用其他形式的进程间通信,将已继承的内核对象句柄值从父进程传送给子进程。方法之一是让父进程等待子进程完成初始化,然后,父进程可以将一条消息发送或展示在子进程中的一个线程创建的窗口中。另一个方法是让父进程将一个环境变量添加给它的环境程序块。该变量的名字是子进程知道要查找的某种信息,而变量的值则是内核对象要继承的值。这样,当父进程生成子进程时,子进程就继承父进程的环境变量,并且能够非常容易地调GetEnvironmentVariable函数,以获取被继承对象的句柄值。如果子进程要生成另一个子进程,那么使用这种方法是极好的,因为环境变量可以被再次继承。 3.3.2 改变句柄的标志改变内核对象句柄的继承标志,调用SetHandleInformation BOOL SetHandleInformation(HANDLE hObject,DWORD dwMask,DWORD dwFlags) 第一个参数hObject用于标识一个有效的句柄。第二个参数dwMask告诉该函数想要改变哪个或那几个标志。目前有两个标志与每个句柄相关联: #define HANDLE_FLAG_INHERIT 0x0000001 #define HANDLE_FLAG_PROTECT_FROM_CLOSE 0x00000002 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 GetHandleInformation(HANDLE hObj,PDWORD pdwFlags);使用GetHandleInformation获取特定句柄的当前标志的设置值。 3.3.3 命名对象 共享跨越进程边界的内核对象的第二种方法是给对象命名。许多(虽然不是全部)内核对象都是可以命名的。如CreateMutex,CreateEvent,CreateSemaphore,CreateFileMapping,CreateJobObject。所有函数pzName参数传递NULL时,系统创建未命名的内核对象,此时可以通过继承性或DuplicateHandle共享跨越进程的对象。若要按名字共享对象,必须为对象赋予一个名字。如果没有为pszName参数传递NULL,应该传递一个以0结尾的字符串名字的地址。该名字的长度最多可以达到MAX_PATH(定义为260)个字符。 HANDLE hMutexProcessA = CreateMutex(NULL,FALSE,"JeffMutex"); 注意:hMutexProcessA不是一个可移植的句柄,并且当你只是命名对象时,它不必是个可继承的句柄。 当Process B调用CreateMutex(NULL,FALSE,"JeffMutex")取得成功时,它并不实际创建一个互斥对象。相反,Process B只是被赋予一个与进程相关的句柄值,用于标识内核中现有的互斥对象。当然,由于Process B的句柄表中的一个新项目要引用该对象,互斥对象的使用计数就会递增。注意,这两个进程中的句柄值很可能是不同的值。Process A将使用它的句柄值,而Process B则使用它自己的句柄值来操作一个互斥内核对象。 注意 当你的多个内核对象拥有相同的名字时,有一个非常重要的细节必须知道。当Process B调用CreateMutex时,它将安全属性信息和第二个参数传递给该函数。如果已经存在带有指定名字的对象,那么这些参数将被忽略。应用程序能够确定它是否确实创建了一个新内核对象,而不是打开了一个现有的对象。方法是在调用Create*函数后立即调用GetLastError: if(GetLastError() == ERROR_ALREADY_EXISTS) { /*open a handle to an existing object*/ else /*created a brand new object/ 按名字共享对象的另一种方法是,进程不调用Create*函数,而是调用下面显示的Open*函数中的某一个: OpenMutex/OpenEvent/OpenSemaphore/OpenWaitableTimer/OpenFileMapping/OpenJobObject 最后一个参数pszName用于指明内核对象的名字。不能为该参数传递NULL,必须传递以0结尾的地址。这些函数要搜索内核对象的单个名空间,以便找出匹配的空间。如果不存在带有指定名字的内核对象,该函数返回NULL,GetLaseError返回2(ERROR_FILE_NOT_FOUND) 。但是,如果存在带有指定名字的内核对象,并且它是相同类型的对象,那么系统就要查看是否允许执行所需的访问(通过dwDesireAccess参数进行访问) 。如果拥有该访问权,调用进程的句柄表就被更新,对象的使用计数被递增。如果为bInheritHandle参数传递TRUE,那么返回的句柄将是可继承的。 调用Create*函数与调用Open*函数之间的主要差别是,如果对象并不存在,那么Create*函数将创建该对象,而Open*函数则运行失败。 Microsoft没有提供创建唯一对象名的指导原则。为了保证对象的唯一性,建议创建一个GUID,并将GUID的字符串表达式用作对象名。命名对象常常用来防止运行一个应用程序的多个实例。 3.3.4 终端服务器的名字空间 终端服务器拥有内核对象的多个名字空间。如果存在一个可供内核对象使用的全局名字空间,就意味着它可以供所有的客户程序会话访问。该名字空间主要供服务程序使用。此外,每个客户程序会话都有它自己的名字空间。它能防止运行相同应用程序的两个或多个会话之间出现互相干扰的情况,也就是说一个会话无法访问另一个会话的对象,尽管该对象拥有相同的名字。在没有终端服务器的机器上,服务程序和应用程序拥有上面所说的相同的内核对象名字空间,而在拥有终端服务器的机器上,却不是这样。服务程序的名字空间对象总是放在全局名字空间中。按照默认设置,在终端服务器中,应用程序的命名内核对象将放入会话的名字空间中。但是,如果像下面这样将“Global/”置于对象名的前面,就可以使命名对象进入全局名字空间: HANDLE h = CreateEvent(NULL,FALSE,"Global//MyName"); 也可以显式说明想让内核对象进入会话的名字空间,方法是将“Local/”置于对象名的前面: HANDLE h = CreateEvent(NULL,FALSE,"Local//MyName"); Microsoft将Global和Local视为保留关键字,除非要强制使用特定的名字空间,否则不应该使用这两个关键字。 3.3.5 复制对象句柄共享跨越进程边界的内核对象的最后一个方法是使用DuplicateHandle函数:该函数取出一个进程的句柄表中的项目,并将该项目拷贝到另一个进程的句柄表中。DuplicateHandle函数最普通的用法要涉及系统中运行的3个不同进程。当调用DuplicateHandle函数时,第一和第三个参数 hSourceProcessHandle和hTargetProceeHandle是内核对象句柄。这些句柄本身必须与调用DuplicateHandle函数的进程相关。此外,这两个参数必须标识进程的内核对象。如果将句柄传递给任何其他类型的内核对象,那么该函数运行就会失败。每当系统中启动一个新进程时都会创建一个进程内核对象。第二个参数hSourceHandle是任何类型的内核对象的句柄。但是该句柄值与调用DuplicateHandle的进程并无关系。相反,该句柄必须与hSourceProcessHandle句柄标识的进程相关。第四个参数phTargetHandle是HANDLE变量的地址,它将接收获取源进程句柄信息拷贝的项目索引。返回的句柄值与hTargetProceeeHadnle标识的进程相关。DuplicateHandle的最后3个参数用于指明该目标进程的内核对象句柄表项目中使用的访问屏蔽值和继承性标志。 dwOptions参数可以是0(零) ,也可以是下面两个标志的任何组合: DUPLICATE_SAME_ACCESS和DUPLICATE_CLOSE_SOURCE。如果设定了DUPLICATE_SAME_ACCESS标志,则告诉DuplicateHandle函数,你希望目标进程的句柄拥有与源进程句柄相同的访问屏蔽。使用该标志将使DuplicateHandle忽略它的 dwDesiredAccess参数。如果设定了DUPLICATE_CLOSE_SOURCE标志,则可以关闭源进程中的句柄。该标志使得一个进程能够很容易地将内核对象传递给另一个进程。当使用该标志时,内核对象的使用计数不会受到影响。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值