作为一个软件开发者,你会创建,打开,操作内核对象。系统会创建和操作多种类型的内核对象,例如access token 对象, event 对象,file对象,file-mapping对象,I/O完成端口对象,job对象,mailslot对象,mutex对象,pipe对象,process对象,semaphore对象,thread对象,waitable time对象,和threa pool worker factory对象.有一个自由的WinObj工具允许你看到所有的内核对象列表。(地址为:http://www.microsoft.com/technet/sysinternals/utilities/winobj.mspx)
注意你必须在Administrator下运行他,通过Windows Explorer你能看到它的列表。
这些对象通过调用各式各样的函数来创建,对象名不必映射到kernel级别上使用的对象的类型上。例如:CreateFileMapping函数使系统创建了一个与Section对象相关的文件映射,正如你在WinObj中看到的。每个内核对象只是被内核分配的一块内存块。但只有内核可以访问。这个内存块是一个成员包括对象的主要信息的数据结构。一些成员(安全描述,引用计数,等等)对所有的对象类型来说是一样的。但是大多数是针对专门的类型。例如,一个process对象有有一个process ID,一个基本的优先级,和一个结束码,在什么地方有字节偏移,共享模式,打开模式。
因为内核对象数据结构仅仅可以被内核访问,所以一个应用程序就无法在内存中定位这些数据结构以及直接改变它的内容。Microsoft 强制使用这种限制是为了使内核对象结构保持一致性。这种限制也允许Microsoft增加,移除和改变这些结构的成员而不用破坏任何应用程序。
如果我们不能直接改变这些结构,那么我们的应用程序该怎么控制这些内核对象呢?回答是Windows提供了一套函数用来控制这些结构。这些内核对象总是经过这些函数访问。当你调用一个函数创建一个内核对象的时候,函数返回了一个标识这个对象的句柄。设想这个句柄可以作为一个透明值而被进程里的所有线程使用。句柄是在32位的Windows系统中是32位的值,在64位的系统中是64位的值。你传递这个句柄给各个Windows函数以便系统知道哪一个内核对象你想去控制。
为了使操作系统更健壮,这些句柄的值是进程相关的。所以如果你传递这个句柄到另外一个进程内的线程(用一些进程间通信的方式),别的进程使用这个进程句柄可能失败,更糟糕的是,他们会创建一个完全不同的内核对象的引用。
使用计数
内核对象被内核拥有,而不是被进程拥有。也就是说,如果你的进程创建了一个调用内核对象的函数然后你的进程结束,内核对象不必销毁,在大多数的环境里,对象将被销毁;但是如果另一个进程正在使用你的进程创建的内核对象,内核会等到另外的进程停止使用它,然后再销毁它。需要记住的是内核对象比创建它的进程更持久。
内核知道有多少进程正在使用一个内核对象是因为每个对象包含一个引用计数。引用计数通常是所有的内核对象的一个数据成员。当一个对象第一次被创建,它的引用计数被设为一。当另一个进程访问一个已经存在的内核对象时,使用计数被增加。当一个进程结束时,内核自动减少所有进程仍然打开的内核对象的引用计数。如果对象的引用计数变为0,内核销毁这个对象。这确保没有进程引用对象的时候系统中就不会有内核对象。
安全
内核对象可以用一个安全描述符来表示,安全描述符描述谁拥有对象(通常是他的创建者),哪个组或用户能有权访问或者使用对象,哪个组或用户无权访问对象。在写服务程序的时候通常要用到安全描述。然而,在Microsoft Windows Vista中,对拥有私有命名空间的客户端应用程序,这个特征变的更加明显。
尽管所有的函数创建内核对象的时候都有一个指向SECURITY_ATTRIBUTES结构的参数,就象下面这个函数显示的:
HANDLE CreateFileMapping(
HANDLE hFile,
PSECURITY_ATTRIBUTES psa,
DWORD flProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
PCTSTR pszName);
大多数的应用程序只是简单的传递一个NULL到这个参数,这样对象通过默认的安全创建,这个安全是建立在当前的进程安全符上。然而,你可以分配一个SECURITY_ATTRIBUTES结构,初始化它,并且传递这个结构的地址,一个SECURITY_ATTRIBUTES结构看起来象下面一样:
typedef struct _SECURITY_ATTRIBUTES { DWORD nLength; LPVOID lpSecurityDescriptor; BOOL bInheritHandle; } SECURITY_ATTRIBUTES;
尽管这个结构被叫作SECURITY_ATTRIBUTES,它实际上只包括一个成员去做与安全相关的所有事情:lpSecurityDescriptor.如果你想要限定对你创建的内核的访问,你必须创建一个安全描述并且初始化SECURITY_ATTRIBUTES 结构:
SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(sa); // Used for versioning sa.lpSecurityDescriptor = pSD; // Address of an initialized SD sa.bInheritHandle = FALSE; // Discussed later HANDLE hFileMapping = CreateFileMapping(INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE, 0, 1024, TEXT("MyFileMapping"));
当你想要访问已存在的内核对象时,你必须指定在对象上视图执行的操作。例如,如果我想要获得一个访问已存在的filemapping内核对象的权限去从它那读取数据,我可以调用OpenFileMapping,
HANDLE hFileMapping = OpenFileMapping(FILE_MAP_READ, FALSE, TEXT("MyFileMapping"));
通过传递FILE_MAP_READ 作为第一个参数,我能指出在我获得权限后我要从文件映射中读。OpenFileMapping函数在返回句柄之前首先要进行一个安全检测。如果我被允许访问已经存在的文件映射内核对象,OpenFileMapping 返回一个安全句柄。然而,如果我没有权限,OpenFileMapping 返回NULL并且从GetLastError会返回(ERROR_ACCESS_DENIED).不要忘了如果返回的句柄被用来调用需要与FILE_MAP_READ不同权限的API,同样会产生禁止访问的错误。
尽管许多应用程序不需要包含安全,但是许多Windows函数需要你传递访问信息的安全描述。许多为以前版本设计的应用程序不能在Windows Vista下工作的原因就是当应用程序执行的时候缺少一些安全信息。
例如,假设一个应用程序启动,读取一些注册键的数据。这时候你的代码就需要调用RegOpenKeyEx,并且传递KEY_QUERY_VALUE到访问描述。
然而,许多的应用程序最初都是为早期版本的Windows2000操作系统开发的,没有考虑任何安全。一些软件开发者可能调用RegOpenKeyEx的时候,传递的是KEY_ALL_ACCESS作为访问描述开发者使用它的原因是解决起来更简单,并且不需要开发者真正的去考虑要进行那种访问。问题是注册子键,例如HKLM可能是只读的,不可以写,对于非Administrator用户来说。当这样一个应用程序运行在Windows Vista上时,调用RegOpenKeyEx时用KEY_ALL_ACCESS会失败。并且没有恰当的错误检测,可能会产生不可预知的结果。
如果开发者考虑到安全,只需要简单的将KEY_ALL_ACCESS改为KEY_AUERY_VALUE(这个例子中必须这样),那么就能在所有的操作系统下运行。
忽略合适的安全访问标识是开发中的最大错误。使用合适的标识将使他更容易兼容各种版本的Windows系统。然而,你也需要认识到每个新的Windows版本带来的新的约束限制。例如,在Windows Vista,你需要小心User Account Control(UAC)特征,UAC强迫应用程序运行在一个受限的安全级别下,尽管当前的用户是Administrator组的一部分。
另外,除了使用内核对象外,你的应用程序可能要使用其他的对象类型,例如menus,windows,光标,画刷和字体,这些对象是用户对象或者图形用户接口,而不是内核对象。当你第一回使用Windows编写程序的时候,你可能无法区别开用户对象和内核对象。例如,icon是一个用户对象还是一个内核对象?识别一个对象是内核对象的最简单的方法是检测创建对向的函数。几乎所有创建内核对象的函数都有一个参数来允许你设定安全属性信息,就像CreateFileMapping函数在前面展示的。
没有一个创建用户和GDI对象的函数有SECURITY_ATTRIBUTES参数。例如,看下面的CreateIcon函数:
HICON CreateIcon( HINSTANCE hinst, int nWidth, int nHeight, BYTE cPlanes, BYTE cBitsPixel, CONST BYTE *pbANDbits, CONST BYTE *pbXORbits);