(1)什么是内核对象
每个内核对象都只是一块内存块,它由操作系统内核分配,并只能由操作系统内核访问。内存块是一个数据结构,其成员维护着与对象的相关信息。少数的成员(安全描述符和使用计数)是所有内核对象所都有的。
大多数成员都是不同类型的对象所特有的。
应用程序不能直接修改内存中的成员,只能使用windows提供的一组函数,以恰当的方式来操作这些结构。
创建一个内核对象以后,会返回一个句柄,它标示了创建的句柄。在32位的windows系统中,句柄是一个32位的值,在64位的windows系统中,句柄是一个64位的值。使用此句柄来操作内核对象。
(2)内核对象的使用计数
内核对象是所有操作系统的内核,而非进程。若进程A创建了一个内核对象,则内核对象的使用计数会加1。此时若另一个进程B使用了这个内核对象,则内核对象的使用计数会再加1。
当进程A终止或者关闭内核对象时,会将内核对象的使用计数减一,若此时内核对象的使用计数为0,则操作系统内核会销毁此内核对象。若进程B仍然在使用内核对象,则进程A终止了,内核对象仍是存在的。
只有当内核对象的使用计数减为0时,操作系统内核对象才会对内核对象执行销毁操作。
(3)内核对象的安全性
windows系统除了内核对象,还有很多其他的对象,如:窗口、菜单、鼠标、GDI对象等等。
要想判断一个对象是不是内核对象,最简单的方式是查看创建这个对象的函数,几乎多有的内核对象的创建,都需要有一个安全描述符结构体的对象。
(4)进程内核对象句柄表 (问题1:句柄表这块内存是创建的进程的4GB的内存内吗?若是的话,进程退出后4GB的空间也会释放掉,那内核对象怎样让其他进程来使用呢?看样子应该不是在进程内的内存,估计由操作系统的内核维护的一块内存,这样子说所有的进程都在使用同一个进程咯(我猜想),最后结论:每一个进程有一个进程内核对象句柄表。)
句柄表只保存内核对象,用户对象/GDI对象不会保存在内。进程初始化的时候句柄表为空,当一个线程调用一个创建内核对象的函数时,系统内核将会为这个对象分配并初始化一个内存块,
然后扫描句柄表,找到一个空白的的记录项,并对其进行初始化。
(5)创建内核对象(列出一些)
(6)关闭内核对象
在CloseHandle()函数返回之前,它会清除进程句柄表中对应的记录项---此时句柄已无效,不可再使用。无论内核对象是否销毁,这个清除过程都会执行,当前进程就不能再使用此句柄。
如果内核对象的使用计数还没有减到0,内核对象时不会销毁的。
(问题2:若按句柄表为所有进程所共享同一个,那这时要是A进程清空句柄表,则B进程岂不是不能使用咯? 此处何解? 每个进程一个内核对象句柄表,内核对象的内存块是操作系统的内核维护的,ok。 明白了)
(7)跨进程边界访问共享内核对象
(a)使用对象句柄继承(使用在父子进程间,且是继承句柄,无法继承对象)
为了是子进程能够继承父进程的内核对象句柄,做以下操作:
1,创建内核对象的时候,使用安全描述符结构体,指明为可继承的。
若在子进程内,使用CreateProcess创建孙进程时,若bInheritHandles为TRUE,则孙进程仍然会继承父进程从他爷爷那继承的内核对象句柄以及孙进程的父进程内创建的可以允许继承的内核对象的句柄(我擦,形容的好晕)
且被继承的内核对象的使用计数会再加1.
改变句柄的标志(实质是修改进程内核对象句柄表内的属性)
A进程创建命名内核对象以后,B进程可以使用这个名字进行打开此内核对象,函数如下:
这些函数会在同一个内核对象命名空间搜索,以查找一个匹配的内核对象。若这些函数返回NULL,表示打开内核对象失败,使用GetLastError()获取错误码查看。
同样,若打开一个内核对象成功,则内核对象的使用计数会加1
(c)从句柄表复制内核对象句柄(这个不解释了,贴一个MSDN的例子)
上班时间,干点私事,O(∩_∩)O~
每个内核对象都只是一块内存块,它由操作系统内核分配,并只能由操作系统内核访问。内存块是一个数据结构,其成员维护着与对象的相关信息。少数的成员(安全描述符和使用计数)是所有内核对象所都有的。
大多数成员都是不同类型的对象所特有的。
应用程序不能直接修改内存中的成员,只能使用windows提供的一组函数,以恰当的方式来操作这些结构。
创建一个内核对象以后,会返回一个句柄,它标示了创建的句柄。在32位的windows系统中,句柄是一个32位的值,在64位的windows系统中,句柄是一个64位的值。使用此句柄来操作内核对象。
(2)内核对象的使用计数
内核对象是所有操作系统的内核,而非进程。若进程A创建了一个内核对象,则内核对象的使用计数会加1。此时若另一个进程B使用了这个内核对象,则内核对象的使用计数会再加1。
当进程A终止或者关闭内核对象时,会将内核对象的使用计数减一,若此时内核对象的使用计数为0,则操作系统内核会销毁此内核对象。若进程B仍然在使用内核对象,则进程A终止了,内核对象仍是存在的。
只有当内核对象的使用计数减为0时,操作系统内核对象才会对内核对象执行销毁操作。
(3)内核对象的安全性
在创建内核对象的时候,都有一个安全结构体的参数,如下:
typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength;
LPVOID lpSecurityDescriptor;
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
//可以像下面这样初始化:
SECURITY_DESCRIPTOR SecDesc;
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = &SecDesc;
sa.bInheritHandle = FALSE;
注:权限问题导致的访问失败,若对某一个内核对象只允许使用读操作来使用,若使用读写或者所有权限的时候是会失败的。
windows系统除了内核对象,还有很多其他的对象,如:窗口、菜单、鼠标、GDI对象等等。
要想判断一个对象是不是内核对象,最简单的方式是查看创建这个对象的函数,几乎多有的内核对象的创建,都需要有一个安全描述符结构体的对象。
(4)进程内核对象句柄表 (问题1:句柄表这块内存是创建的进程的4GB的内存内吗?若是的话,进程退出后4GB的空间也会释放掉,那内核对象怎样让其他进程来使用呢?看样子应该不是在进程内的内存,估计由操作系统的内核维护的一块内存,这样子说所有的进程都在使用同一个进程咯(我猜想),最后结论:每一个进程有一个进程内核对象句柄表。)
句柄表只保存内核对象,用户对象/GDI对象不会保存在内。进程初始化的时候句柄表为空,当一个线程调用一个创建内核对象的函数时,系统内核将会为这个对象分配并初始化一个内存块,
然后扫描句柄表,找到一个空白的的记录项,并对其进行初始化。
(5)创建内核对象(列出一些)
CreateFile();
CreateThread();
CreateEvent();
CreateMutex();
CreateSemaphore();
CreateWaitableTimer();
CreateJobObject();
CreateFileMapping();
创建成功后,会返回一个HANDLE类型的句柄,即内核对象的句柄。若创建失败,有的返回为0(NULL),有的返回时-1(INVALIDE_HANDLE_VALUE)
(6)关闭内核对象
CloseHandle();
在使用之前,先判断句柄是否还是有效的句柄。
在CloseHandle()函数返回之前,它会清除进程句柄表中对应的记录项---此时句柄已无效,不可再使用。无论内核对象是否销毁,这个清除过程都会执行,当前进程就不能再使用此句柄。
如果内核对象的使用计数还没有减到0,内核对象时不会销毁的。
(问题2:若按句柄表为所有进程所共享同一个,那这时要是A进程清空句柄表,则B进程岂不是不能使用咯? 此处何解? 每个进程一个内核对象句柄表,内核对象的内存块是操作系统的内核维护的,ok。 明白了)
(7)跨进程边界访问共享内核对象
(a)使用对象句柄继承(使用在父子进程间,且是继承句柄,无法继承对象)
为了是子进程能够继承父进程的内核对象句柄,做以下操作:
1,创建内核对象的时候,使用安全描述符结构体,指明为可继承的。
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
HANDLE hdl = CreateEvent(&sa,FALSE,FALSE,NULL);
2,使用CreateProcess创建子进程时,参数表明可以继承父进程的内核对象的句柄。
BOOL WINAPI CreateProcess(
__in LPCTSTR lpApplicationName,
__in_out LPTSTR lpCommandLine,
__in LPSECURITY_ATTRIBUTES lpProcessAttributes,
__in LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in BOOL bInheritHandles,
__in DWORD dwCreationFlags,
__in LPVOID lpEnvironment,
__in LPCTSTR lpCurrentDirectory,
__in LPSTARTUPINFO lpStartupInfo,
__out LPPROCESS_INFORMATION lpProcessInformation
);
bInheritHandles为TRUE时,子进程会继承父进程的所有允许继承的内核对象的句柄,同时会在子进程内创建一个内核对象句柄表,且所有继承的内核对象的使用计数都进行加1.
若在子进程内,使用CreateProcess创建孙进程时,若bInheritHandles为TRUE,则孙进程仍然会继承父进程从他爷爷那继承的内核对象句柄以及孙进程的父进程内创建的可以允许继承的内核对象的句柄(我擦,形容的好晕)
且被继承的内核对象的使用计数会再加1.
改变句柄的标志(实质是修改进程内核对象句柄表内的属性)
BOOL WINAPI GetHandleInformation(
__in HANDLE hObject,
__out LPDWORD lpdwFlags
);
BOOL WINAPI SetHandleInformation(
__in HANDLE hObject,
__in DWORD dwMask,
__in DWORD dwFlags
);
(b)使用命名的内核对象(许多(并不是全部)内核对象都可以命名)
A进程创建命名内核对象以后,B进程可以使用这个名字进行打开此内核对象,函数如下:
OpenFile();
OpenEvent();
OpenMutex();
OpenMutex();
OpenSemaphore();
OpenThread();
OpenWaitableTimer();
OpenJobObject();
OpenThreadToken();
最后一个参数__in LPCTSTR lpName 是一个以'\0'结束的字符串,标示是内核对象的名字,此参数不可以为NULL。
这些函数会在同一个内核对象命名空间搜索,以查找一个匹配的内核对象。若这些函数返回NULL,表示打开内核对象失败,使用GetLastError()获取错误码查看。
同样,若打开一个内核对象成功,则内核对象的使用计数会加1
(c)从句柄表复制内核对象句柄(这个不解释了,贴一个MSDN的例子)
#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;
}
上班时间,干点私事,O(∩_∩)O~