第3章 内核对象
3.1 何为内核对象
- 进程对象有一个进程ID 、一个基本优先级和一个退出代码,而文件对象则拥有一个字节位移、一个共享模式和一个打开模式;
- 内核对象是系统提供的用户模式下代码与内核模式下代码进行交互的基本接口;
- 内核对象的数据结构只能被内核访问,因此应用程序无法在内存中找到这些数据结构并直接改变它们的内容;
- 当调用一个用于创建内核对象的函数时,该函数就返回一个用于标识该对象的句柄;
3.1.1 使用计数
- 内核对象的所有者是操作系统内核,而非进程;
- 操作系统内核知道有多少进程正在使用一个内核对象,因为每个对象都包含一个使用计数(usage count);
- 当使用计数为0,操作系统内核就销毁该对象;
3.1.2 内核对象的安全性
- 内核对象可用安全描述符(SD)来保护;
- 创建内核对象的函数都有指向一个SECURUTY_ATTRIBUTES结构的指针;
- 若想访问一个文件映射内核对象,以从中读取数据,可以调用OpenFlieMapping;
HANDLE hFileMapping = OpenFileMapping(FILE_MAP_READ, FALSE, TEXT("MyFileMapping"));
//第一个参数表明要获取访问权,并从中获取数据
3.2 进程内核对象句柄表
句柄表是一个由数据结构组成的数组,每个结构都包含一个指向内核对象的指针、一个访问掩码(access mask)、一些标志;
3.2.1 创建一个内核对象
一些用于创建内核对象的函数:CreateThread,CreateFile,CreateFileMapping,CreateSemaphore;
创建内核对象的函数失败后,可能的返回值:
ERROR_INVALID_HANDLE | 6 |
NULL | 0 |
INVALID_HANDLE_VALUE (在WinBase.h中定义) | -1 |
3.2.2 关闭内核对象
只要闯将了内核对象,就要调用CloseHandle向系统表明我们已结束了使用:
BOOL CloseHandle(HANDLE hobject);
如果传给CloseHandle函数一个无效的句柄可能出现以下2种情况:
- 如果程序正常运行,CloseHandle将返回FALSE,而GetLastError返回ERROR_INVALID_HANDLE;
- 如果进程在被调试,将出现0xC0000008异常(指定了无效的句柄);
3.3 跨进程边界共享内核对象
三种不同的机制来允许进程共享内核对象:使用对象句柄继承,为对象命名,复制对象句柄;
3.3.1 使用对象句柄继承
- 句柄表中的每个记录项有一个指明句柄是否可以继承的标志位,为0句柄不可继承,为1可继承;
- 通过CreateProcess函数,使父进程生成子进程,注意bInheritHandles参数,传入FALSE子进程不继承父进程句柄表中的"可继承的句柄",传入TRUE则继承;递增内核对象的使用计数;
- 内核对象的内容被保存在内核空间地址中,系统运行的所有进程共享这个空间:32位系统是0x80000000到0xFFFFFFFF之间的内存空间,64位系统是0x00000400’00000000到0xFFFFFFFF'FFFFFFFF之间;
- 对象句柄的继承只会在生成子进程的时候发生,之后父进程创建新的内核对象并可继承,子进程不会继承这些新句柄;
- 为了使子进程获得一个内核对象的句柄值,常用的方式是将句柄值作为命令行参数传给子进程,也可以用其他进程间通信技术将继承的内核对象句柄值从父进程传入子进程;
3.3.2 改变句柄的标志
//调用SetHandleInformation函数来改变内核对象句柄的继承标志,以控制哪些子进程能继承内核对象句柄
BOOL SetHandleInformation(
HANDLE hObject, //标识了一个有效句柄
DWORD dwMask, //想要更改哪个或哪些标志
DWORD dwFlags); //希望把标志设为什么
//每个句柄都关联了2个标志
#define HANDLE_FLAG_INHERIT 0x00000001
#define HANDLE_FLAG_PROTECT_FROM_CLOSE 0x00000002 //此标志告诉系统不允许关闭句柄
SetHandleInformation(hObj,HANDLE_FLAG_INHERIT,HANDLE_FLAG_INHERIT); //打开内核对象句柄的继承标志
SetHandleInformation(hObj,HANDLE_FLAG_INHERIT,0); //关闭这个标志
//返回指定句柄的当前标志
BOOL GetHandleInformation(HANDLE hObject,PDWORD pdwFlags);
3.3.3 为对象命名
//以下所有Creat*函数都可以创建命名的内核对象
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 CreateWaittableTimer(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);
调用Creat*函数和Open*函数的主要区别在于,如果对象不存在,Create*函数会创建它,Open*函数则返回调用失败;
3.3.4 终端服务命名空间
HANDLE CreateEvent(NULL,FALSE,FALSE,TEXT("Global\\MyName")); //加上前缀”Global\",把命名对象强制放入全局命名空间
HANDLE CreateEvent(NULL,FALSE,FALSE,TEXT("Local\\MyName")); //加上前缀”Local\",把内核对象显式放入当前会话命名空间
3.3.5 专有命名空间
3.3.6 复制对象句柄
//使用DuplicateHandle函数,跨边界共享内核对象
BOOL DuplicateHandle(
HANDLE hSourceProcessHandle,
HANDLE hSourceHandle, //不与调用该函数的进程相关
HANDLE hTargetProcessHandle,
PHANDLE phTargetHandle,
DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwOptions); //可以为0或DUPLICATE_SAME_ACCESS和DUPLICATE_CLOSE_SOURCE
//最后3个参数,用于指定句柄表项应使用何种访问掩码和继承标志
启动一个新的进程,系统就会创建一个进程内核对象;
一个进程S想把一个内核对象的访问权授予另一个进程T,可以这样调用DuplicateHandle函数:
//下述的所有代码都由进程s执行
HANDLE hObjInProcessS = CreateMutex(NULL,FALSE,NULL); //创建一个能被进程S访问的互斥量内核对象
HANDLE hProcessT = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwProcessIdT); //从进程T的内核对象获取句柄值
HANDLE hObjInProcessT; //与进程T相关未初始化的句柄
DuplicateHandle(GetCurrentProcess(),hObjInProcessS,hProcessT,&hObjInProcessT,0,FALSE,DUPLICATE_SAME_ACCESS);
//给予进程T访问互斥量内核对象的权限
CloseHandle(hProcessT); //不再需要与进程T通信
CloseHandle(hObjInProcessS); //进程S不再使用互斥量,关闭它
//调用GetCurrentProcess函数会返回伪句柄,该句柄始终标识主调进程(本例是进程S)