Windows核心编程笔记(三) 内核对象与句柄

来源:互联网 发布:嵌入式linux驱动开发 编辑:程序博客网 时间:2024/04/30 05:59

在写Windwos应用程序的时候HANDLE这个类型作为参数和返回值函数我们经常用到,这些函数有个共同的特点就是需要用到一个内核对象,这些内核对象有Job、Directory(对象目录中的目录)、SymbolLink(符号链接),Section(内存映射文件)、Port(LPC端口)、IoCompletion(Io完成端口)、File(并非专指磁盘文件)、同步对象(Mutex、Event、Semaphore、Timer)、Key(注册表中的键)、Token(用户/组令牌)、Process、Thread、Pipe、Mailslot、Debug(调试端口)等.



内核对象本质上是由系统内核维护的一个个结构体,它们大多用来标识某一资源的属性,用面向对象的方式来看,他们相当于没有成员函数的类,之所以没有是因为系统内核是用C语言来写的,但是抽象来他们又是面向对象的,每种内核对象对应着一个系列的内核函数,这些内核函数就相当于类的成员函数,他们接受一个对象的This指针做为参数,在这里这个This指针就是句柄,与C++This指针不同的是这指针不是一个内存地址,而是一个数组的索引,这个数组就是句柄表,Windows以进程为单来维护每个句柄表,句柄表里存放着每一个进程打来了的内核对象的地址,当我们调用一个HANDLE为返回值的函数(如CreateFile)内核种会创建一个相应的内核对象,并把这个对象地址挂入进程的句柄表内,返回它在表内的索引值,当我们使用一个以HANDLE为参数的函数时(如ReadFile),在内核中传给对应内核函数(如NtCreateFile),内核函数(调用ObReferenceObjectByHandle)获取本进程的(EPROCESS)在这个结构中找到举报表地址,根据HANDLE索引值获取内核对象的地址。




1、对一些需要在内核中构建全局对象来实现的功能(每个进程地址空间的内核部分是相同的)需要构建一个内核对象(一个结构体)来支持,因此完成这部分工作的API需要用到内核对象

2、位了内核对象的安全性,以及用户态来标识内核数据的可以操作性,(用户态代码不能访问内核地址空间),WInodws为每个进程引入了句柄表,句柄表是一个数组,每项存放一个内核对象的地址,以及一个访问掩码来标识需要的权限,以及一个标志用来表示是否有效。

3、API使用句柄表的索引,即HANDLE来标识内核对象,完成所需要的工作。

4、内核对象都是全局的,并不属于某一个进程,每个内核对象都有一个引用计数器,只有在引用为0时,系统内核才会销毁该对象。因此在打开和使用完成后需要CloseHandle 递减引用值。

5、没一个需要创建内核对象的API都有一个参数LPSECURITY_ATTRIBUTES ,

typedef struct _SECURITY_ATTRIBUTES {  DWORD  nLength;  LPVOID lpSecurityDescriptor;  BOOL   bInheritHandle;   标识是否该对象可以内子进程继承} SECURITY

共享内核对象的三种方式


    跨进程共享内核对象方法之一:使用对象句柄继承
 
 
    只有进程之间属于父子关系时才可以使用对象句柄继承。当父进程创建一个内核对象时,父进程必须向系统指出它希望这个对象的句柄是可继承的。为了创建可继承句柄父进程必须分配并初始化一个SECURITY_ATTRIBUTES结构,并将这个结构的地址传递给Create*函数。如:
SECURITY_ATTRIBUTES sa;
sa.nLength=sizeof(sa);
sa.lpSecurityDescriptor=NULL;//使用默认安全性。
sa.bInheritHandle=TRUE;//是此句柄可以继承。
HANDLE mutex=CeattMutex(&sa,FALSE,NULL);
 
    以上代码初始化了一个SECURITY_ATTRIBUTES结构,表明使用默认安全性来创建此对象,且返回的对象时可继承的。
 
 
    句柄表的每个记录中还有一个指明该句柄是否可继承的标志位,如果在创建内核对象的时候将NULL作为PSECURITY_ATTRIBUTES的参数传入,则返回的句柄是不可继承的,标志位为0。
 
下一步是由父进程创建子进程,这是通过CreateProcess实现的,此函数第四章会详细介绍,此处仅仅注意bInheritHandles参数。如果在创建进程时,此参数被设为false,则表明不希望子进程继承父进程句柄表中的可继承句柄。如为true,则表明希望子进程继承父进程句柄表中的可继承句柄。注意只有可继承句柄才可以被继承。
 
新创建的进程句柄表为空,由于我们希望它继承父进程句柄表,此时系统会遍历父进程句柄表,对它的每一个项进行检查,将所有的可继承的句柄的项全部复制到子进程的句柄表中。在子进程的句柄表中,复制项的位置与它在父进程句柄表中的位置是完全一样的,这是非常重要的。它意味着在父进程和子进程中,对一个内核对象进行标识的句柄是完全一样的。除了复制句柄表,系统还会递增每个可继承句柄的使用计数。为了销毁内核对象,父进程和子进程必须都不再使用才可以。这可以通过CloseHandle和进程终止来实现。注意:句柄进程仅仅发生在进程刚被创建时,如果此后父进程又创建了新的内核对象,那么此时子进程不会继承这些新创建的内核对象句柄。
 
    如果父进程创建了一个内核对象,得到一个不可继承的句柄,但是后来父进程又希望后来创建的子进程继承它,这怎么办呢?这可以通过使用SetHandleInformation修改内核对象句柄的继承标志 。它需三个参数,第一个标识了一个有效句柄,第二个标识想更改哪些标识。第三个标识指出想把它设成什么。这个标识可以是
HANDLE_FLAG_INHERI,//打开句柄继承标识。
HANDLE_FLAG_PROTECT_FROM_CLOSE//不允许关闭句柄。
    GetHandleInformation可以用来返回指定句柄的当前标识。
 
 
    跨进程共享内核对象方法之二:命名对象
 
 
    许多对象都可以进行命名,但并不是全部。因此该方法有一定局限性。有些创建内核对象的函数都有一个指定内核对象名称的参数,如果传入NULL,则会创建一个匿名的内核对象。如果不为NULL,则应该传入一个一'\0'结尾的字符串。所有这些命名对象共享一个名字空间。即使它们类型不同,如果已存在同名对象,创建就会失败。
 
    一旦一个命名的内核对象被创建,其他进程(不仅仅是子进程)可以通过调用Open*或是Create*函数来访问它。当使用Create*函数时,系统会检查是否存在一个传给此函数的名字,如果确实存在一个这样的对象,内核执行安全检查,验证调用者是否有足够的安全权限。如果是,系统就会在此进程的句柄表中查找空白记录项,并将其初始化为指向已存在的命名的内核对象。两个进程的句柄不一定相同,这没有任何影响。由于内核对象被再一次引用,所以其引用计数会被递增。
 
    为了防止在创建一个命名对象时,仅仅打开了一个现有的而不是新建的,可以在创建后调用GetLastError获得详细信息。
 
    使用Open*函数可以打开已存在的命名内核对象,如果没有找到这个名称的内核对象将返回NULL。如果找到这个名称的内核对象,但类型不同,函数仍返回NULL。只有当名称相同且类型相同的情况下系统才会进一步检查访问权限。如果有权访问,系统就会更新此进程的句柄表,并递增内核对象的引用计数。在Open*函数中也可以指定此句柄的继承性。
 
    Open*和Create*的区别:如果对象不存在,Create*会创建它,Open*将会调用失败。
 
    我们经常使用命名的内核对象来防止运行一个程序的多个实例。可以在main函数中建立一个命名对象,返回后调用GetLastError如果GetLastError返回ERROR_ALREADY_EXISTS表明此程序的另一个实例在运行。
 
     关于终端服务命名空间不再介绍,只需知道它是为了防止命名内核对象命名冲突而设计的。以后有需要的可以仔细研究下。
 
 
    跨进程共享内核对象方法之三:复制对象句柄
 
 
    实现该方法使用的是Duplicatehandle函数。
bool DuplicateHandle(
   HANDLE hSourceProcessHandle,
HANDLE hSourceHandle,
HANDLE hTargetProcessHandle,
PHANDLE phTargethandle
DWORD ddwDesiredAccess,
BOOL bInheritHandle,
DWORD dwOptions
);
 
这个函数的功能就是获得进程句柄表的一个记录项,然后在另一个进程中创建这个记录项的副本。第一个和第三个参数分别标识源进程和目标进程内核对象句柄。第二个参数标识要复制的内核对象句柄,它可以指向任何类型的内核对象。第四个参数是一个目标句柄的地址,用来接收复制到的HANDLE值。
 
函数将源进程中的句柄信息复制到目标进程所标识的句柄表中。第五第六个参数用以指定此内核对象句柄在目标进程句柄表中应该使用何种访问掩码和继承标志。
 
dwOption参数可以是DUPLICATE_SAME_ACCESS和DUPLICATE_CLOSE_SOURCE任一个。如果是DUPLICATE_SAME_ACCESS标志,将向DuplicateHandle函数表明我们希望目标句柄拥有与源进程句柄一样的访问掩码,此时会忽略dwDesiredAccess。如果是DUPLICATE_CLOSE_SOURCE标志,会关闭源进程的句柄,此时将一个内核对象从一个进程复制到另一个进程,但是内核对象的使用计数不受影响。
 
     GetCurrentProcess可以返回当前进程的句柄,但是它是一个伪句柄。其值为-1,GetCurrentThread返回的也是伪句柄其值为-2,它们并不在句柄表中而仅仅代表当前进程和当前线程。
     这一章很抽象,原来学习的时候读了很多遍也不是很明白,后来干脆跳过去了,一段时间的学习之后再回来看看,发现竟然非常简单。所以有时候学习不能钻牛角尖该跳过就跳过。随着学习的深入,你所站的高度、看问题的角度都会不一样。理解起来也会更容易!!!


0 0
原创粉丝点击