Windows 内核对象

来源:互联网 发布:琅琊榜香港遇冷 知乎 编辑:程序博客网 时间:2024/04/30 09:26

1. 何为内核对象
每个内核对象是一块内存,由操作系统内核维护的,并只能由操作系统内核直接访问;该内存块是一个数据结构,其成员维护着对象的相关信息。有些成员是内核对象共有的,如引用计数和安全描述符;不同的内核对象拥有自己特有的成员。用户无法直接操作内核对象,Windows提供一系列的函数来操纵这些内核对象,并用句柄来标识内核对象,句柄是与进程相关的。内核对象包括事件对象,文件对象,作业对象,互斥对象,进程对象,线程对象,等待计时器对象等等。

2. 引用计数
内核对象的直接拥有者是操作系统内核,所有进程共享这些内核对象,因此要有一种机制保证内核对象的正确构建、销毁,Windows采用引用计数的技术;内核对象维护着一个引用计数成员。一个进程创建了一个内核对象,对象的引用计数为1,如果该对象又被另外的进程共享,每多一个进程,引用计数会加1,当一个进程调用CloseHandle函数后,引用计数会减1,如果引用计数变为0,操作系统会销毁该内核对象。引用计数实现上跟COM计数的引用计数类似。

3. 安全描述符
安全描述符记录的内核对象的创建者(owner);有权限访问该内核对象的用户和组;拒绝访问的用户和组。创建内核对象的函数几乎都有一个指向SECURITY_ATTRIBUTES结构的指针作为参数。SECURITY_ATTRIBUTES结构为:

typedef struct _SECURITY_ATTRIBUTES {
    DWORD nLength;
    LPVOID lpSecurityDescriptor;
    BOOL bInheritHandle;
} SECURITY_ATTRIBUTES,
*PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;

该结构的lpSecurityDescriptor为安全描述符。在调用函数访问内核对象时,会进行安全检查,如果没有权限访问一个内核对象,函数调用会失败,GetLastError()会返回ERROR_ACCESS_DENIED。

4. 进程的内核对象句柄表
进程(的线程)是通过句柄访问内核对象的,进程内部维护着一个内核对象句柄表,内核对象句柄表没有详细的文档描述,并且在不同版本的Windows中,实现也不完全一致。内核对象句柄表有索引、内核对象指针、状态标志等几列,每条记录为一个内核对象。内核对象的句柄是通过索引进行一定的运算得到的,根据句柄也能够取得内核对象句柄表中对应的索引。
进程刚刚创建时,其内核对象句柄表没有记录。创建或通过内核对象共享获取到内核对象时会在内核对象句柄表中插入一条记录,内核对象的句柄可被进程内的所有线程访问。如果对内核对象的句柄调用CloseHandle函数,内核对象句柄表中对应的记录会标记为无效,再次用该句柄执行操作会失败,GetLastError()会返回ERROR_INVALID_HANDLE。

5. 关闭内核对象
上面提到过,关闭内核对象用CloseHandle函数,其原型为:
BOOL CloseHandle(Handle hObject);
该函数会根据hObject参数,在内核对象句柄表中定位到相应的记录,如果句柄有效,则将该对象的引用计数减1,如果对象的引用计数变为0,销毁该内核对象,将内核对象句柄表中对应的记录标记为无效;如果句柄无效,返回FALSE。
如果忘记关闭内核对象,在程序生命周期内,内核对象不会被关闭,但进程结束时,操作系统会遍历内核对象句柄表,将有效的记录执行关闭内核对象的操作。

6. 跨进程边界共享内核对象
内核对象是操作系统内核维护的,多个进程可以访问同一个内核对象。有三种方式实现内核对象共享:使用句柄继承;为对象命名;复制对象句柄。

1) 使用句柄继承
SECURITY_ATTRIBUTES中的bInheritHandle成员指定内核对象可否被子进程共享,如果为TRUE,表示内核对象可被子进程共享,否则不可。创建子进程时,CreateProcess函数的bInheritHandle参数标志子进程是否要继承父进程中可共享的内核对象,如果为TRUE,则共享父进程的可共享的内核对象,否则不共享。
子进程共享了父进程的内核对象,父子进程中对应的内核对象在进程各自的内核对象句柄表中的位置相同,句柄值(32为或64位整型)也相同;父进程需要一定的方式(进程间通信)将句柄传给子进程,子进程获取父进程传过来的句柄访问内核对象。句柄继承会使内核对象的引用计数加1。
只有在子进程创建的时候并且标志了子进程共享父进程内核对象,父进程中的可共享的内核对象才会共享到子进程。如果子进程创建后,父进程又创建了可共享的内核对象,则子进程无法通过句柄继承的方式共享该内核对象。
如果父进程的某些内核对象想要子进程共享,另一些内核对象想要另外的子进程共享,父进程可以调用GetHandleInformation和SetHandleInformation两个函数来获取或重设内核对象的继承标志。

2) 为对象命名
大多数创建内核对象的函数都有一个PCTSTR类型的参数,创建内核对象时,这个参数为NULL时,也就是不给内核对象命名,则无法通过对象的名称共享对象,因为对象是匿名的。对象名称的最大程度为MAX_PATH(260)。Windows没有一种机制保证内核对象不会重名,因此在创建或打开内核对象时应该检查返回值和GetLastError的错误代码。
Windows还提供命名空间的机制,如果内核对象名为TEXT(“Global//ObjName”),表示创建的是作用域为全局的内核对象;如果内核对象名为TEXT(“Local//ObjName”),表示创建的是作用域为当前会话的内核对象;如果不指定命名空间,默认的是作用域为当前会话的内核对象;用户以可以自己制定专有的命名空间。

3) 复制对象句柄
复制对象句柄用DuplicateHandle函数,其原型为:

BOOL DuplicateHandle(
    HANDLE hSourceProcessHandle,
    HANDLE hSourceHandle,
    HANDLE hTargetProcessHandle,
    LPHANDLE lpTargetHandle,
    DWORD dwDesiredAccess,
    BOOL bInheritHandle,
    DWORD dwOptions
);

获取到lpTargetHandle后,需要通过一定的进程间通信将句柄传给子进程。