Windows内核之内核对象
1内核对象定义:
1.1:每个内 核对象只是内核分配的一个内存块,并且只能由该内核访问。
1.2:该内存块是一种数据结构,它的成员负责维护该对象的各种信息。
有些数据成员(如安全性描述符、使用计数等)在所有对象类型中是相同的,但大多数数据成员属于特定的对象类型。例如,进程对象有一个进程ID 、一个基 本优先级和一个退出代码,而文件对象则拥有一个字节位移、一个共享模式和一个打开模式。
2内核对象种类:
比如存取符号对象、 事件对象、文件对象、文件映射对象、I / O 完成端口对象、作业对象、信箱对象、互斥对象、管道对象、进程对象、信标对象、线程对象和等待计 时器对象等。
3内核对象拥有者:
内核对象由内核所拥有,而不是由进程所拥有。如果你的进程调用了一个创建内核对象的函数,然后你的进程终止运行,那么内核对象不 一定被撤消。在大多数情况下,对象将被撤消,但是如果另一个进程正在使用你的进程创建的内核对象,那么该内核知道,在另一个进程停止使用该 对象前不要撤消该对象,必须记住的是,内核对象的存在时间可以比创建该对象的进程长。
4内核对象使用计数:
内核对象悲哀创建时候,计数设置为1,当有进程访问该内核对象的时候,计数加1,当进程终止运行的时候,内核会判断该对象技术是否为0,是0的话就撤销该内核对象。
5进程对象内核句柄表:
当一个进程被初始化时,系统要为它分配一个句柄表。该句柄表只用于内核对象,不用于用户对象或G D I 对象。
当进程初次被初始化时,它的句柄表是空的。然后,当进程中的线程调用创建内核对象的函数时,比如Cr e a t e F i l e M a p p i n g ,内核 就为该对象分配一个内存块,并对它初始化。
用于创建内核对象的所有函数均返回与进程相关的句柄,这些句柄可以被在相同进程中运行的任何或所有线程成功地加以使用。
6内核对象的关闭和撤销的区别:
内核对象的关闭是指一个进程利用CloseHandle(Handle hObj)函数把内核对象在该进程中的句柄表中清除掉,因而该进程不能再访问该内核对象。
内核对象的撤销试着内核把内核对象从内存中清除,此时的内核对象计数为0.
两者的区别:内核对象关闭是进程操作的,关闭内核对象不一定会撤销对象;撤销对象是内核操作的,当撤销对象的时候,对象一定会关闭的。
7 共享内核对象
7.1 通过对象句柄的继承性
只有当进程具有父子关系时,才能使用对象句柄的继承性。在这种情况下,父进程可以使用一个或多个内核对象句柄,并且该父进程可以决定生成一 个子进程,为子进程赋予对父进程的内核对象的访问权。
实施步骤:
7.1.1:父进程必须指定一个S E C U R I T Y _ AT T R I B U TE S 结构并对它进行初始化,然后将该结构的地址传递给特定的Cr e a t e 函数。
SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(sa); sa.lpSecuntyDescriptor = NULL; /Make the returned handle inheritable. sa.bInheritHandle =- TRUE; HANDLE hMutex = CreateMutex(&sa,FALSE, NULL);
7.1.2:父进程生成子进程
BOOL CreateProcess( PCTSTR pszApplicationName, PTSTR pszCommandLine, PSECURITY_ATTRIBUTES psaProcess, PSECURITY_ATTRIBUTES psaThread, BOOL bInheritHandles, DWORD fdwCreale, PVOIO pvEnvironment, PCTSTR pszCurDir, PSTARTUPINFO psiStartInfo, PPROCESS_INFORMATION ppiProcInfo);
要把bInheritHandles设置为True,表示是继承了父进程。
7.1.3子进程确定继承内核对象的方法
子进程为了确定它期望的内核对象的句柄值,最常用的方法是将句柄值作为一个命令行参数传递给子进程。
7.1.3 改变句柄标志
当父父进程想选择不同的子进程具有不同的句柄继承权的时候,可以通过设置句柄的标志来实现。通过该函数来实现设置句柄的标志
BOOL SetHandleInformation( HANDLE hObject, DWORD dwMask, //标志位 DWORD dwFlags); //标识值
7.2 命名对象
7.2.1创建命名内核对象
HANDLE CreateJobObject(
PSECURITY_ATTRIBUTES psa,
PCTSTR pszName);
通过观察创建内核对象的函数,我们知道其中的最后一个参数是一个字符串,这个字符串表示内核对象的名字。当然如果我们不想给它起名字的话,我们可以设置其为NULL。该名字的长度最多可以达到MAX_PATH(定义为260 )个字符。
HANDLE hMutexPronessA = CreateMutex(NULL, FALSE,"JeffMutex"); HANDLE hMutexProcessB = CreateMutex(NULL, FALSE,"JeffMutex");
此时第一次调用该函数时候,创建一个名字为JeffMutex的内核对象,第二次调用该创建内核对象函数时候,由于待创建的内核对象的名字以及类型都一样,再加上如果权限符合的话,此时在进程B中就不会再次创建内核对象,而是引用了进程A中同名同类型的对象JeffMutex。同时内核对象的计数就会加1.
警告:如果命名的内核对象已经存在,那么再次创建该名字的内核对象的时候,将会忽略第二次创建时传递给函数的参数并且不再创建此内核对象,否则会创建一个按照第二次指定参数的新的内核对象。
7.2.2 打开命名内核对象
可以利用Open函数来打开已经有的命名内核对象,而不是去用Create***来实现。两个的区别是:当没有该内核对象的时候,Open函数会运行失败,而Create***函数则会创建一个新的内核对象。例如:
HANDLE OpenFileMapping( DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName);
7.3 复制进程
把进程A中的某个内核对象的句柄复制到进程B 中的句柄表中。
BOOL DuplicateHandle( HANDLE hSourceProcessHandle, HANDLE hSourceHandle, HANDLEhTargetProcessHandle, PHANDLE phTargetHandle, DWORD dwDesiredAccess, //访问屏蔽值 BOOL bInheritHandle, //继承标志 DWORD dwOptions); //是否继承源进程的访问屏蔽值
复制内核对象的一个实例
从例子中可以看到同一个进程的句柄表中可以有两个句柄,它们欲访问的内核对象值相同,但是一些访问标志以及继承权限可以不同。
#include <windows.h> DWORD CALLBACK ThreadProc(PVOID pvParam); int main() { HANDLE hMutex = CreateMutex(NULL, FALSE, NULL); HANDLE hMutexDup, hThread; DWORD dwThreadId; DuplicateHandle(GetCurrentProcess(), hMutex, GetCurrentProcess(), &hMutexDup, 0, FALSE, DUPLICATE_SAME_ACCESS); hThread = CreateThread(NULL, 0, ThreadProc, (LPVOID) hMutexDup, 0, &dwThreadId); // Perform work here, closing the handle when finished with the // mutex. If the reference count is zero, the object is destroyed. CloseHandle(hMutex); // Wait for the worker thread to terminate and clean up. WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); return 0; } DWORD CALLBACK ThreadProc(PVOID pvParam) { HANDLE hMutex = (HANDLE)pvParam; // Perform work here, closing the handle when finished with the // mutex. If the reference count is zero, the object is destroyed. CloseHandle(hMutex); return 0; }