windows内核对象

来源:互联网 发布:马丽 知乎 编辑:程序博客网 时间:2024/05/17 22:08
一、什么是内核对象
每个内核对象只是内核分配的一个内存块,并且只能由该内核访问。该内存块是一种数据结构,它的成员负责维护该对象的各种信息。有些数据成员(如安全性描述符、使用计数等)在所有对象类型中是相同的,但大多数数据成员属于特定的对象类型。例如,进程对象有一个进程I D、一个基本优先级和一个退出代码,而文件对象则拥有一个字节位移、一个共享模式和一个打开模式。
内核对象的数据结构只能被内核访问,因此应用程序无法在内存中找到这些数据结构并直接改变它们的内容。
Wi n d o w s提供了一组函数,以便用定义得很好的方法来对这些结构进行操作。这些内核对象始终都可以通过这些函数进行访问。当调用一个用于创建内核对象的函数时,该函数就返回一个用于标识该对象的句柄。该句柄可以被视为一个不透明值,你的进程中的任何线程都可以使用这个值。将这个句柄传递给Wi n d o w s的各个函数,这样,系统就能知道你想操作哪个内核对象。
包括:

1).符号对象  2).事件对象  3).文件对象   4).文件影象对象   5).I/O完成对象  6).作业对象  7).信箱对象 

8).互斥对象  9).管道对象  10).进程对象  11).信标对象     12).线程对象    13).计时器对象 

二:内核对象的特征:
内核对象的使用计数
内核对象由内核所拥有,而不是由进程所拥有。如果你的进程调用了一个创建内核对象的函数,然后你的进程终止运行,那么内核对象不一定被撤消。内核对象的存在时间可以比创建该对象的进程长。
内核通过内核对象的使用计数知道有多少进程正在使用某个内核对象。当一个对象刚刚创建时,它的使用计数被置为1。然后,当另一个进程访问一个现有的内核对象时,使用计数就递增1。当进程终止运行时,内核就自动确定该进程仍然打开的所有内核对象的使用计数。如果内核对象的使用计数降为0,内核就撤消该对象。

安全性
内核对象能够得到安全描述符的保护。安全描述符用于描述谁创建了该对象,谁能够访问或使用该对象,谁无权访问该对象。安全描述符通常在编写服务器应用程序时使用,如果你编写客户机端的应用程序,那么可以忽略内核对象的这个特性。

当你想要获得对相应的一个内核对象的访问权(而不是创建一个新对象)时,必须设定要对该对象执行什么操作。例如,如果想要访问一个现有的文件映射内核对象,以便读取它的数据,那么应该调用下面这个O p e n f i l e M a p p i n g函数:
HANDLE hFileMapping = OpenFileMapping(FILE_MAP_READ,FALSE,”MyFileMapping”);

若要确定一个对象是否属于内核对象,最容易的方法是观察创建该对象所用的函数。创建内核对象的所有函数几乎都有一个参数,你可以用来设定安全属性的信息。

进程的内核对象句柄表
当一个进程被初始化时,系统要为它分配一个句柄表。该句柄表只用于内核对象,不用于用户对象或G D I对象。它只是个数据结构的数组。每个结构都包含一个指向内核对象的指针、一个访问屏蔽和一些标志。

三、创建内核对象
当进程初次被初始化时,它的句柄表是空的。然后,当进程中的线程调用创建内核对象的函数时,比如C r e a t e F i l e M a p p i n g,内核就为该对象分配一个内存块,并对它初始化。
用于创建内核对象的所有函数均返回与进程相关的句柄,这些句柄可以被在相同进程中运行的任何或所有线程成功地加以使用。该句柄值实际上是放入进程的句柄表中的索引,它用于标识内核对象的信息存放的位置。

如果调用一个函数以便创建内核对象,但是调用失败了,那么返回的句柄值通常是 0(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,而不是返回N U L L。

四、关闭内核对象
通过调用C l o s e H a n d l e来结束对该对象的操作:
BOOL CloseHandle (HANDLE hobj);

如果该索引是有效的,那么系统就可以获得内核对象的数据结构的地址,并可确定该结构中的使用计数的数据成员。如果使用计数是0,该内核便从内存中撤消该内核对象。无论内核对象是否已经撤消,都会发生清除操作。当调用C l o s e H a n d l e函数之后,将不再拥有对内核对象的访问权。

当进程终止运行时,系统会自动扫描进程的句柄表。如果该表拥有任何无效项目(即在终止进程运行前没有关闭的对象),系统将关闭这些对象句柄。如果这些对象中的任何对象的使用计数降为0,那么内核便撤消该对象。

五、跨越进程边界共享内核对象的方法
1、对象句柄的继承性
只有当进程具有父子关系时,才能使用对象句柄的继承性。在这种情况下,父进程可以使用一个或多个内核对象句柄,并且该父进程可以决定生成一个子进程,为子进程赋予对父进程的内核对象的访问权。创建子进程时系统要进行另一项操作,即它要遍历父进程的句柄表,对于它找到的包含有效的可继承句柄的每个项目,系统会将该项目准确地拷贝到子进程的句柄表中。该项目拷贝到子进程的句柄表中的位置将与父进程的句柄表中的位置完全相同。父进程可以立即关闭对象的句柄,而不影响子进程对该对象进行操作的能力。

2、为对象命名
注:Windows没有提供任何专门的机制来保证为内核对象指定的名称是唯一的。

3、复制句柄对象
六:几种内核对象的介绍:

互斥对象(Mutex)

互斥对象包含一个使用计数(与所有内核对象一样),,一个线程I D和一个递归计数器。I D用于标识系统中的哪个线程当前拥有互斥对象,递归计数器用于指明该线程拥有互斥对象的次数。

互斥对象的使用规则如下:

• 如果线程I D是0(这是个无效I D),互斥对象不被任何线程所拥有,并且发出该互斥对象的通知信号。

• 如果I D是个非0数字,那么一个线程就拥有互斥对象,并且不发出该互斥对象的通知信号。

• 与所有其他内核对象不同,互斥对象在操作系统中拥有特殊的代码,允许它们违反正常的规则。

对于互斥对象来说,正常的内核对象的已通知和未通知规则存在一个特殊的异常情况。比如说,一个线程试图等待一个未通知的互斥对象。在这种情况下,该线程通常被置于等待状态。然而,系统要查看试图获取互斥对象的线程的I D是否与互斥对象中记录的线程I D相同。如果两个线程I D相同,即使互斥对象处于未通知状态,系统也允许该线程保持可调度状态。我们不认为该“异常”行为特性适用于系统中的任何地方的其他内核对象。每当线程成功地等待互斥对象时,该对象的递归计数器就递增。若要使递归计数器的值大于1,唯一的方法是线程多次等待相同的互斥对象,以便利用这个异常规则。

一旦线程成功地等待到一个互斥对象,该线程就知道它已经拥有对受保护资源的独占访问权。试图访问该资源的任何其他线程(通过等待相同的互斥对象)均被置于等待状态中。当目前拥有对资源的访问权的线程不再需要它的访问权时,它必须调用R e l e a s e M u t e x函数来释放该互斥对象:

BOOL ReleaseMutex(HANDLE hMutex);

该函数将对象的递归计数器递减1。如果线程多次成功地等待一个互斥对象,在互斥对象的递归计数器变成0之前,该线程必须以同样的次数调用R e l e a s e M u t e x函数。当递归计数器到达0时,该线程I D也被置为0,同时该对象变为已通知状态。

互斥对象不同于所有其他内核对象,因为互斥对象有一个“线程所有权”的概念。其他内核对象中,没有一种对象能够记住哪个线程成功地等待到该对象,只有互斥对象能够对此保持跟踪。互斥对象的线程所有权概念是互斥对象为什么会拥有特殊异常规则的原因,这个异常规则使得线程能够获取该互斥对象,尽管它没有发出通知。

这个异常规则不仅适用于试图获取互斥对象的线程,而且适用于试图释放互斥对象的线程。当一个线程调用R e l e a s e M u t e x函数时,该函数要查看调用线程的I D是否与互斥对象中的线程I D相匹配。如果两个I D相匹配,递归计数器就会像前面介绍的那样递减。如果两个线程的I D不匹配,那么R e l e a s e M u t e x函数将不进行任何操作,而是将FA L S E(表示失败)返回给调用者。此时调用G e t L a s t E r r o r,将返回E R R O R _ N O T _ O W N E R(试图释放不是调用者拥有的互斥对象)。

因此,如果在释放互斥对象之前,拥有互斥对象的线程终止运行(使用E x i t T h r e a d、Te r m i n a t e T h r e a d、E x i t P r o c e s s或Te r m i n a t e P r o c e s s函数),那么互斥对象和正在等待互斥对象的其他线程将会发生什么情况呢?答案是,系统将把该互斥对象视为已经被放弃——拥有互斥对象的线程决不会释放它,因为该线程已经终止运行。

由于系统保持对所有互斥对象和线程内核对象的跟踪,因此它能准确的知道互斥对象何时被放弃。当一个互斥对象被放弃时,系统将自动把互斥对象的I D复置为0,并将它的递归计数器复置为0。然后,系统要查看目前是否有任何线程正在等待该互斥对象。如果有,系统将“公平地”选定一个等待线程,将I D设置为选定的线程的I D,并将递归计数器设置为1,同时,选定的线程变为可调度线程。

这与前面的情况相同,差别在于等待函数并不将通常的WA I T _ O B J E C T _ 0值返回给线程。相反,等待函数返回的是特殊的WA I T _ A B A N D O N E D值。这个特殊的返回值(它只适用于互斥对象)用于指明线程正在等待的互斥对象是由另一个线程拥有的,而这另一个线程已经在它完成对共享资源的使用前终止运行。这不是可以进入的最佳情况。新调度的线程不知道目前资源处于何种状态,也许该资源已经完全被破坏了。在这种情况下必须自己决定应用程序应该怎么办。

事件对象(Event)

在所有的内核对象中,事件内核对象是个最基本的对象。它们包含一个使用计数(与所有内核对象一样),一个用于指明该事件是个自动重置的事件还是一个人工重置的事件的布尔值,另一个用于指明该事件处于已通知状态还是未通知状态的布尔值。

M i c r o s o f t为自动重置的事件定义了应该成功等待的副作用规则,即当线程成功地等待到该对象时,自动重置的事件就会自动重置到未通知状态。这就是自动重置的事件如何获得它们的名字的方法。通常没有必要为自动重置的事件调用R e s e t E v e n t函数,因为系统会自动对事件进行重置。但是, M i c r o s o f t没有为人工重置的事件定义成功等待的副作用。

BOOL SetEvent(HANDLE hEvent); //将事件改为已通知状态

BOOL ResetEvent(HANDLE hEvent); //将该事件改为未通知状态 
BOOL PulseEvent(HANDLE hEvent); //使得事件变为已通知状态,然后立即又变为未通知状态,这就像在调用S e t E v e n t后又立即调用R e s e t E v e n t函数一样。如果在人工重置的事件上调用P u l s e E v e n t函数,那么在发出该事件时,等待该事件的任何一个线程或所有线程将变为可调度线程。如果在自动重置事件上调用P u l s e E v e n t函数,那么只有一个等待该事件的线程变为可调度线程。如果在发出事件时没有任何线程在等待该事件,那么将不起任何作用。 
信标 
信标的使用规则如下 
如果当前资源的数量大于0,则发出信标信号。 
• 如果当前资源数量是0,则不发出信标信号。 
• 系统决不允许当前资源的数量为负值。 
• 当前资源数量决不能大于最大资源数量。 
当使用信标时,不要将信标对象的使用数量与它的当前资源数量混为一谈。 
HANDLE CreateSemaphore( PSECURITY_ATTRIBUTE psa, LONG lInitialCount, LONG lMaximumCount, PCTSTR pszName);
HANDLE OpenSemaphore( DWORD fdwAccess, BOOL bInheritHandle, PCTSTR pszName);
BOOL ReleaseSemaphore( HANDLE hsem, LONG lReleaseCount, PLONG plPreviousCount);

等待定时器

        等待定时器是在某个时间或按规定的间隔时间发出自己的信号通知的内核对象。它们通常用来在某个时间执行某个操作。 

等待定时器与用户定时器(用S e t Ti m e r函数进行设置)的比较:它们之间的最大差别是,用户定时器需要在应用程序中设置许多附加的用户界面结构,这使定时器变得资源更加密集。另外,等待定时器属于内核对象,这意味着它们可以供多个线程共享,并且是安全的。

等待定时器与用户定时器(用S e t Ti m e r函数进行设置)进行比较。它们之间的最大差别是,用户定时器需要在应用程序中设置许多附加的用户界面结构,这使定时器变得资源更加密集。另外,等待定时器属于内核对象,这意味着它们可以供多个线程共享,并且是安全的。

可以将定时器的句柄传递给Wa i t F o r S i n g l e O b j e c t或Wa i t F o r M u l t i p l e O b j e c t s函数,以便等待定时器报时。M i c r o s o f t还允许定时器给在定时器得到通知信号时调用S e t Wa i t a b l e Ti m e r函数的线程的异步过程调用(A P C)进行排队。

一般来说,当调用S e t Wa i t a b l e Ti m e r函数时,你将同时为p f n C o m p l e t i o n R o u t i n e和p v A rg C o m p l e t i o n R o u t i n e参数传递N U L L。当S e t Wa i t a b l e Ti m e函数看到这些参数的N U L L时,它就知道,当规定的时间到来时,就向定时器发出通知信号。但是,如果到了规定的时间,你愿意让定时器给一个A P C排队,那么你必须传递定时器A P C例程的地址,而这个例程是你必须实现的。该函数可以在定时器报时的时候由调用S e t Wa i t a b l e Ti m e r函数的同一个线程来调用,但是只有在调用线程处于待命状态下才能调用。换句话说,该线程必须正在S l e e p E x , Wa i t F o r S n g l e O b j e c t E x,Wa i t F o r M u l t i p l e O b j e c t s E x,M s g Wa i t F o r M u l t i p l e O b j e c t s E x或S i n g l e O b j e c t - A n d Wa i t等函数的调用中等待。如果该线程不在这些函数中的某个函数中等待,系统将不给定时器A P C例程排队。这可以防止线程的A P C队列中塞满定时器A P C通知,这会浪费系统中的大量内存。当定时器报时的时候,如果你的线程处于待命的等待状态中,系统就使你的线程调用回调例程。