当你需要创建、打开和操作各种内核对象的时候,系统要创建和操作若干类型的内核对象,强如存取符号对象、事件对象、文件对象、文件映射对象、I/O完成端口对象、作业对象、信箱对象、互斥对象、管道对象、进程对象、信标对象、线程对象和等待计时器对象等等。这些对象都是通过调用函数来创建的。例如:CreateFileMapping函数可使系统能够创建一个文件映射对象。
每个内核对象只是内核分配的一个内存块。 并且只能由该内核访问。该内存块是一种数据结构,它的尵负责维护该对象的各种信息。有些数据成员(如安全描述符、使用计数器等)在所有的对象类型中都是相同的,但大多数数据成员属于特定的对象类型。例如,进程对象有一个进程ID、一本基本优先级和一个退出代码,而文件对象则拥有一个字节位移、一个共享模式和一个打开模式。
MS规定,内核对象只能被内核访问,因此应该程序无法在内存中找到这些数据结构并直接改变他们的内容。其目的是为了确保内核对象结构保持状态的一致。这个限制也使得用户可以在不破坏任何应用程序的情况下在这些结构中添、删除和修改数据成员。
我们不能直接修改,但我们又要修改。于是对象句柄便出现了。 当创建一个内核对象的时候,会返回一个它的句柄。这是一个不透明的值,你只需要知道它能标志你创建的这个对象就行了。 而当需要修改内核对象时,只需将这个句柄传入MS为你提供的API,这个API自然知道你需要修改哪个对象。值得注意的是,这些句柄值是与进程密切相关的。也就是说,A进程自己创建的句柄只能在他自己的进程中,以及它所有的线程中使用。如果其它进程试图访问这个句柄,就会调用失败。当然MS也提供了几种进程间共享内核对象的方法。
内核对象有一个引用计数,标志着你的内核对象被引用了多少次。 因为内核对象是内核所有,所以内核必须知道什么时候外部不需要使用对象了。 引用计数就是这个目的。 内核对象在第一次创建的时候,计数为1,当另一个进程访问它的时候,便会加1。当有进程调用CloseHandle并传入它的句柄时,它的引用计数就减1。当内核检测到此引用计数为0的时候,便会找个合适的时机释放掉这个对象。值得说明的是,就算没有调用CloseHandle,当所有使用这个内核对象的进程退出时,内核也会释放掉这个对象。因此,可以确保在没有进程引用该对象的时候,系统中不保留任何内核对象。当然,如果不释放,则会在程序运行过程中造成内存占用量大的可能。
对于内核对象,我们除了关注其句柄外,还应该关注它的安全描述符,虽然这个安全描述符在普通的应用程序(如普通的小软件,客户端应用程序等)中不使用。但对于某些场合,如服务器。或者是对用户身份有要求的应用程序中却要使用。 它标志着访问此应用程序的权限。
在创建内核对象时,会传入一个 PSECURITY_ATTRIBUTES 参数 如:
HANDLE CreateFileMapping(
HANDLE hFile,
PSECURITY_ATTRIBUTES psa,
DWORD flProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
PCTSTR pszName);
大多数应用程序传递给psa的是NULL,这样对象管理组中的任何成员和对象创建者有着同样的全部访问权限。
PSECURITY_ATTRIBUTES定义大概如下:
typedef struct _PSECURITY_ATTRIBUTES{
DWORD nLength
LPVOID lpSecurityDescriptor;//安全描述符。
BOOL bInheritHandle;
} PSECURITY_ATTRIBUTES;
而对于对象的安全描述,则只有第二个成员lpSecurityDescriptor有用。 比如我们想要打开一个文件映射,则可以像如下操作。
HANDLE hMapping = OpenFileMapping(FILE_MAP_READ,FASE,"MyFileMapping");
传入FILE_MAP_READ则表示着你需要打开此文件,并在打开后读取这个文件。 此时内核就会进行安全检查,看是否你有读取该映射文件的权限。而这个检查则是由你先前创建此文件映射时传入的安全描述符而决定。