内核对象包括符号对象,事件对象,文件对象,文件映射对象,I/O完成端口对象,作业对象,信箱对象,互斥对象,管道对象,进程对象,信标对象,线程对象和等待计算器对象等。
每个内核对象只是一个内存块,只能由该内核访问。该内存是一个数据结构,其成员负责维护该对象的各种信息。有些是所有内核对象都有的,比如安全标识符,使用计数;有些是特有的,比如进程对象有一个进程ID,一个基本优先级和一个退出代码,而一个文件对象则拥有一个字节位移,一个共享模式和一个打开模式。
Windows编程中使用句柄来标识内核对象,并提供一些方法来对内核对象进行操作。为了使操作系统变得更加健壮,这些句柄值是与进程密切相关的。因此,如果将该句柄值传递给另一个进程中的一个线程(使用某种形式的进程间的通信)那么这另一个进程使用你的进程的句柄值所作的调用就会失败。
1.1使用计数
用于记录使用该内核对象的进程数。
内核对象建立时,其使用计数置为1,另一个进程访问该对象时,计数增加1。调用CloseHandle时,计数减一。
当进程终止运行时,系统会自动扫描进程的句柄表。如果该表拥有任何无效项目(即在终止进程运行前没有关闭的对象),系统将关闭这些对象句柄。如果这些对象中的任何对象的使用计数降为0,那么内核便撤消该对象。
应用程序在运行时有可能泄漏内核对象,但是当进程终止运行时,系统将能确保所有内容均被正确地清除。另外,这个情况适用于所有对象、资源和内存块,也就是说,当进程终止运行时,系统将保证进程不会留下任何对象。
调用CreateThread可使系统创建一个线程内核对象。该对象的初始使用计数是2(在线程停止运行和从CreateThread返回的句柄关闭之前,线程内核对象不会被撤销)。
1.2安全性
通常用于服务器端编程,不去深究。
数据结构
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");
2进程的内核对象句柄表
进程初始化时,系统为进程创建一个句柄表,该句柄表只能用于内核对象。
表3 - 1 显示了进程的句柄表的样子。可以看到,它只是个数据结构的数组。每个结构都包含一个指向内核对象的指针、一个访问屏蔽和一些标志。
表3-1 进程的句柄结构
索引 | 内核对象内存块的指针 | 访问屏蔽(标志位的D W O R D ) | 标志(标志位的D W O R D ) |
1 | 0 x ? ? ? ? ? ? ? ? | 0 x ? ? ? ? ? ? ? ? | 0 x ? ? ? ? ? ? ? ? |
2 | 0 x ? ? ? ? ? ? ? ? | 0 x ? ? ? ? ? ? ? ? | 0 x ? ? ? ? ? ? ? ? |
... | ... | ... | ... |
2.1创建内核对象
进程初始化后,创建如表3—1的内存块。指针为该内核对象在内存中的地址,访问屏蔽置为全部访问权。
用于创建内核对象的函数返回的句柄实际上就是表3-1的句柄结构的索引。注意,句柄的含义并没有记入文档资料,并且可能随时变更。实际上在Windows 2000 中,返回的值用于标识放入进程的句柄表 的该对象的字节数,而不是索引号本身。
每当调用一个一个以内核对象句柄为参数的函数时,从内部来说,该函数要访问进程的内核对象句柄表,获取要生成的内核对象的地址,然后生成该对象的数据结构。
如果传递了一个无效索引(句柄),该函数便返回失败,而G e t L a s t E r r o r 则返回6(E R R O R _ I N VA L I D _ H A N D L E )。由 于句柄值实际上是放入进程句柄表的索引,因此这些句柄是与进程相关的,并且不能由其他进程成功地使用。
如果调用一个函数以便创建内核对象,但是调用失败了,那么返回的句柄值通常是0(N U L L )。发生这种情况是因为系统的内存非常短缺,或者 遇到了安全方面的问题。不过有少数函数在运行失败时返回的句柄值是-1 (I N VA L I D _ H A N D L E _ VA L U E )。例如,如果C r e a t e F i l e未能打开指定的文件,那么它将返回I N VA L I D _ H A N D L E _ VA L U E ,而不是返回N U L L 。当查看创建内核对象的函数返回值 时,必须格外小心。特别要注意的是,只有当调用C r e a t e F i l e 函数时,才能将该值与I N VA L I D _ H A N D L E _ VA L U E 进行比较 。
2.2关闭内核对象
BOOL CloseHandle(HANDLE hobj);
在进程运行时,进程有可能泄漏资源(如 内核对象)。但是,当进程终止运行时,操作系统能够确保该进程使用的任何资源或全部资源均被释放,这是有保证的。对于内核对象来说,系统将 执行下列操作:当进程终止运行时,系统会自动扫描进程的句柄表。如果该表拥有任何无效项目(即在终止进程运行前没有关闭的对象),系统将关 闭这些对象句柄。如果这些对象中的任何对象的使用计数降为0 ,那么内核便撤消该对象。
因此,应用程序在运行时有可能泄漏内核对象,但是当进程终止运行时,系统将能确保所有内容均被正确地清除。另外,这个情况适用于所有对象、 资源和内存块,也就是说,当进程终止运行时,系统将保证进程不会留下任何对象。
3跨进程共享内核对象
3.1继承性
创建能继承的句柄,父进程必须指定一个S E C U R I T Y _ AT T R I B U T E S 结构并对它进行初始化,然后将该结构的地址传递给特定的C r 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);
该代码对一个S E C U R I T Y _ AT T R I B U T E S 结构进行初始化,指明该对象应该使用默认安全性(在Windows 98 中该安全性被忽略)来创 建,并且返回的句柄应该是可继承的。
使用对象句柄继承性时要执行的下一个步骤是让父进程生成子进程。
BOOL CreateProcess(
PCTSTR pszApplicationName,
PTSTR pszCommandLine,
PSECURITY_ATTRIBUTES psaProcess,
PSECURITY_ATTRIBUTES psaThread,
BOOL bInheritHandles,//若该参数为true,子进程就可以继承父进程的可继承句柄值。
DWORD fdwCreale,
PVOIO pvEnvironment,
PCTSTR pszCurDir,
PSTARTUPINFO psiStartInfo,
PPROCESS_INFORMATION ppiProcInfo);
对象句柄的继承性只有在生成子进程的时候才能使用。如果父进程准备创建带有可继承句柄的新内核对象,.
那么已经在运行的子进程将无法继承这些新句柄。
3.2命名对象
创建内核对象的函数的最后一个参数pszName为对象的名字。若要按名字共享对象,必须为对象赋予一个名字。
注:所有这些对象都共享单个名空间。
调用C r e a t e *函数与调用O p e n *函数之间的主要差别是,如果对象并不存在,那么C r e a t e *函数将创建该对象,
而O p e n *函数则运行失败。