第三章 内核对象
3.1 什么是内核对象
每个内核对象只是内核分配的一个内存块,并且只能由该内核访问。该内存块是一种数据结构,它的成员负责维护该对象的各种信息。有些数据成员(如安全性描述符、使用计数等)在所有对象类型中是相同的,但大多数数据成员属于特定的对象类型。
内核对象只能被内核访问,应用程序无法在内存中找到这些数据结构并直接改变它们的内容
操作这些内核对象必须使用Windows提供的一组函数。
如果将该内核对象句柄传递给另一个进程(使用某种形式的进程间的 通信)那么这另一个进程所作的调用就会失败。(需使用“跨越进程边界共享内核对象”机制)
3.1.1内核对象的使用计数
内核对象只能由内核操作。当一个对象刚刚 创建时,它的使用计数被置为1 。然后,当另一个进程访问一个现有的内核对象时,使用计数就递增1 。。当进程终止运行时,内核就自动确定该进程 仍然打开的所有内核对象的使用计数。如果内核对象的使用计数降为0 ,内核就撤消该对象。
3.1.2 安全性
安全描述符用于描述谁创建了该对象,谁能够访问或使用该对象,谁无权访问该对象。用于创建内核对象的函数几乎都有一个指向SECURITY_ATTRIBUTES结构的指针作为其参数。
HANDLE CreateFileMapping(
HANDLE hFile.
PSECURITY_ATTRIBUTES psa,
DWORD flProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximuniSizeLow,
PCTSTR pszNarne);
传递NULL代表默认安全性。也可以这样初始化该结构。
typedef struct _SECURITY_ATTRIBUTES
{
DWORD nLength,
LPVOID lpSecurityDescriptor;
BOOL bInherttHandle;
} SECURITY_ATTRIBUTES;
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa); //Used for versioning
sa.lpSecuntyDescriptor = pSD, //Address of an initialized SD
sa.bInheritHandle = FALSE; //Discussed later
HANDLE hFileMapping = CreateFileMapping(INVALID_HANDLE_VALUE,
&sa, PAGE_REAOWRITE, 0, 1024, "MyFileMapping");
当你想要获得对相应的一个内核对象的访问权(而不是创建一个新对象)时,必须设定要对该对象执行什么操作。例如,如果想要访问一个现有的文件映射内核对象,以便读取它的数据:
HANDLE hFileMapping = OpenFileMapping(FILE_MAP_READ, FALSE, "MyFileMapping");
FILE_MAP_READ指明在获得访问权后读取该文件,在函数返回句柄前先进行安全检查,如果允许访问返回句柄,否则返回NULL,GetLastError返回ERROR_ACCESS_DENINED。
除内核对象有句柄外,其他类型的很多对象也有句柄例如菜单,窗口,字体等,称为用户对象,应该如何区分?
内核对象创建时都传入安全性参数PSECURITY_ATTRIBUTES,用户对象没有该参数。
3.2 进程的内核对象句柄表
当一个进程被初始化时,系统要为它分配一个保存内核对象的句柄表。
索引 | 内核对象内存块的指针 | 访问屏蔽(标志位的D W O R D ) | 标志(标志位的D W O R D ) |
---|---|---|---|
1 | 0 x ? ? ? ? ? ? ? ? | 0 x ? ? ? ? ? ? ? ? | 0 x ? ? ? ? ? ? ? ? |
2 | 0 x ? ? ? ? ? ? ? ? | 0 x ? ? ? ? ? ? ? ? | 0 x ? ? ? ? ? ? ? ? |
… | … | … | … |
3.2.1 创建内核对象
创建内核对象的函数都返回与进程相关的句柄,这些句柄在当前进程中的所有线程中都可以使用。该句柄值实际上是句柄表的索引。
当创建内核对象失败时,返回值通常是NULL,但有例外比如CreateFile未能打开指定的文件时,返回INVALID_HANDLE_VALE。
3.2.2 关闭内核对象
BOOL CloseHandle(HANDLE hobj);
关闭对象,如计数为0,内核将从内存中撤销内核对象。如忘记调用CloseHandle,当进程终止时,系统会自动扫描进程的句柄表,如存在没有关闭的对象,系统将自动关闭这些对象句柄。
因此,程序运行时可能泄露内核对象,但进程终止时,系统能保证所有内容都能被正确的清除。
3.3 跨越进程边界共享内核对象
文件映射对象可在两个进程之间共享数据块。邮箱和管道能在联网的不同机器上进行进程之间发送数据块。互斥对象、信标和事件能够同步它们的连续运行。
3.3.1 对象句柄的继承性
把SECURITY_ATTRIBUTES 结构体的变量bInheritHandle设为TRUE,表明子进程可以继承父进程的对象句柄。
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecuntyDescriptor = NULL;
//Make the returned handle inheritable.
sa.bInheritHandle =- TRUE;
HANDLE hMutex = CreateMutex(&sa, FALSE, NULL);
再调用CreateProcess时,需要再把CreateProcess函数的参数bInheritHandles设为TRUE才能继承。
如CreateProcess设置为TRUE,操作系统在创建子进程时会把父进程的句柄表准确的拷贝到子进程的句柄表中,以保证子进程中对象句柄的值与在父进程中的相同。
出了拷贝句柄表,还要增加内核对象的使用计数,因为子进程也在使用共享的内核对象。(子进程使用父进程的内核对象句柄,应该只是使用了指向内核对象的指针,没有拷贝内核对象,所以计数要加一,指明这个内核对象的内存还在使用,不能delete。)
一般情况下,子进程并不知道继承的句柄的值是多少,最常用的办法就是通过命令行方式把句柄的值直接传给子进程。
3.3.2 改变句柄的标志
BOOL SetHandleInformation(
HANDLE hObject,
DWORD dwMask,
DWORD dwFlags);
设置继承性
SetHandleInformation(hobj, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
关闭继承性
SetHandleInformation(hobj, HANDLE_FLAG_INHERIT, 0);
3.3.3 命名对象
进程间共享内核对象的第二种方法是给对象命名。如果名字传入NULL,则表明想创建无名的内核对象,无名的内核对象可通过继承性共享。
HANDLE hMutex = CreateMutex(NULL. FALSE, "JeffObj");
HANDLE hSem = CreateSemaphore(NULL, 1, 1, "JeffObj");
DWORD dwErrorCode = GetLastError();
系统中的内核对象共享同一个名字空间,创建同名的不同类型的内核对象时返回代码是ERROR_INVALID_HANDLE。
进程A调用以下函数:
HANDLE hMutexPronessA = CreateMutex(NULL, FALSE, "JeffMutex");
进程B调用以下函数:
HANDLE hMutexProcessB = CreateMutex(NULL, FALSE, "JeffMutex");
在进程B调用函数时系统执行流程如下:
3.3.4 终端服务器的名字空间
终端服务器有多个名字空间。
全局名字空间:
HANDLE h = CreateEvenL(NULL, FALSE, FALSE, "Global\\MyName");
会话名字空间:
HANDLE h = CreateEvent(NULL, FALSE, FALSE, "Local\\MyName");
3.3.5 复制对象句柄
不知道有啥用,看着实在无聊,暂不学习。