一、何为内核对象
- 内核对象是用于管理进程、线程和文件等诸多种类大量资源的数据结构。
- 常见的内核对象有:事件对象、文件对象、互斥量对象、进程对象、线程对象、线程池工厂对象、管道对象、信号量对象等。
- 每个内核对象都只是一个内存块(这个内存块是一个数据结构,其成员维护着与对象相关的信息),它由操作系统内核分配,并只能由操作系统内核访问。
二、应用程序如何操作内核对象
- 内核对象的数据结构只能由操作系统内核访问,应用程序不能在内存中定位这些数据结构并直接更改其内容。
- 只有通过调用Windows API创建内核对象,再通过函数返回的
HANDLE
(句柄)来间接操作内核对象。
三、内核对象的生命周期
- 虽然内核对象与进程相关,但内核对象的所有者是操作系统内核,而非进程,所以进程结束,内核对象不一定被销毁。假如其他进程正在使用本进程创建的内核对象,本进程终止运行,内核对象在其他进程终止运行之前,它不会被销毁。
- 系统内核根据内核对象的使用计数来判断是否销毁内核对象。所有内核对象都包含一个数据成员:使用计算。内核对象被创建时,其使用计数被设为1,另一个进程获得该内核对象的访问后,使用计数+1,进程终止运行后,使用计数-1。一旦使用计数变成0,操作系统内核就会销毁该内核对象。
四、内核对象的安全性
- 内核对象可以用一个安全描述符(SECURITY_ATTRIBUTES)来保护。
- 安全描述符描述了谁拥有对象。
- 如果想对创建的内核对象加以访问限制,就必须创建一个安全描述符(
SECURITY_ATTRIBUTES
结构体),然后初始化结构体
五、用户对象/GDI对象
- 用户对象:用户创建的对象,用户具有使用权。
- GDI对象:即图形对象,用户具有使用权。
六、进程内核对象句柄表
- 一个进程在初始化时,系统将为它分配一个句柄表(为空),句柄表仅供内核对象使用,不适用于用户对象或GDI对象。
- 用于创建内核对象的任何函数都会返回一个与进程相关的句柄,这个句柄可由同一个进程中运行的所有线程使用。
- 系统用索引来表示内核对象的信息保存在进程句柄表中的具体位置。
七、关闭内核对象
- 调用
CloseHandle
向系统表明结束使用内核对象。 - 关闭内核对象不代表会销毁内核对象,只会让使用计数-1。
- 调用
CloseHandle
后,还应同时将保存内核对象的变量设为NULL
,否则可能会出现意外错误。
HANDLE m_hThread;
BOOL CloseHandle(m_hThread);
m_hThread = NULL;
八、跨进程边界共享内核对象
允许进程共享内核对象的方法:①使用对象句柄继承;②为对象命名;③复制对象句柄。
1. 使用对象句柄继承
①使用条件:要求进程之间有父-子关系
②使用方法:步骤如下
A.设置对象句柄可继承。父进程创建一个内核对象时,父进程必须向系统指出该对象的句柄是可以继承的。
B.通过CreatepProcess
函数,让父进程生成子进程
//A.设置对象句柄可继承
SECURITY_ATTRIBUTES sa; //安全描述符
sa.nLength = sizeof(sa); //用于版本控制
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE; //指定该内核对象句柄是可继承的
//B.生成子进程
CreatepProcess(...);//将bInheritHandles设为TRUE
2. 为对象命名
①使用条件:只针对可进行命名的内核对象。比如:
HANDLE CreateMutex(
PSECURITY_ATTRIBUTES psa;
BOOL bInitialOwner;
PCTSTR pszName;
②使用方法:不同进程创建内核对象时,使用同一个对象名称,比如:
//进程A
HANDLE hMutexProcessA = CreateMutex(NULL,FALSE,TEXT("JeffMutex"));
//进程B
HANDLE hMutexProcessB = CreateMutex(NULL,FALSE,TEXT("JeffMutex"));
③优势:进程B不一定是进程A的子进程
④缺点:难保证唯一性
⭐重要用途:可利用命名对象来防止运行一个应用程序的多个实例。只需在_tmain 或 _tWinMain函数中调用一个Create*函数来创建一个命名对象,再调用GetLastError,如果返回ERROR_ALREADY_EXISTS
,则表明应用程序的另一个实例正在运行。比如:
int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PRSTR pszCmdLine, int nCmdShow)
{
HANDLE h = CreateMutex(NULL, FALSE,
TEXT("fa531cc1"));
if(GetLastError() == ERROR_ALREADY_EXISTS)
{
//该程序已有实例在运行
CloseHandle(h);
return(0);
}
CloseHandle(h);
return(0);
}
3. 复制对象句柄
①使用方法:调用DuplicateHandle
函数
BOOL DuplicateHandle(
HANDLE hSourceProcessHandle,//源进程内核对象句柄
HANDLE hSourceHandle, //任意内核对象句柄,须与hSourceProcessHandle中的进程相关
HANDLE hTargetProcessHandle,//目标进程内核对象句柄
PHANDLE phTargetHandle, //接收复制得到的HANDLE值
DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwOptions
);