系列文章目录
文章目录
- 系列文章目录
- 4.1 对象与对象目录
- OBJECT_HEADER
- ObpLookupEntryDirectory()
- NtCreateTimer()
4.1 对象与对象目录
“对象(Object)”这个词现在大家都已耳熟能详了,但是对象到底是什么呢?广义地说,对象就是“目标”,行为的目标,或者说行为的受体。所以,广义地说,任何客观存在着的物体都可以成为对象。但是,在计算机技术里,“对象”是个专有名词,其定义是:一个(或一组)数据结构和定义于其上的(若干)操作。这里所谓操作是指实现某种功能的函数,也称为“方法”。这个定义中其实还隐含着一个意思,就是除了所定义的操作之外,就再也没有别的操作了:只要想对其有所行为,就必须通过所定义的操作之一,别无他途。所谓“基于对象的程序设计(Obiect-BasedProgramming)”和“面向对象的程序设计(Object-Oriented Programming)”的概念和技术都是由此而来。在计算机技术的历史上,“对象”的概念出现于UNIX之后,所以在UNIX以及 Linux 里面并没有明确的对象概念,也没有统一的对象管理,而只是对于具体的对象(事物)类别分别加以管理例如对于文件和设备的管理、对于进程的管理、对于进程间通信手段的管理等。但是 UNIX 把设备看做特殊文件,这应该说是认识上的一次飞跃,因为在此之前的操作系统技术中不是那样。后来,到了微软开始设计研发 Windows 操作系统的时候,“对象”这个概念不但已经相当成熟,而且在当时正是炙手可热很热门的话题之一,所以在 Windows中采用对象这个概念就是顺理成章的事了。所以我们不妨这样概括:UNIX把设备看做特殊文件,而Windows把文件和设备都看做特殊对象。
既然我们所说的对象是数据结构加操作(函数),那就有(存在于内核之中的)内核对象和(用户空间的)用户对象之分。从操作系统的角度看,我们关心的当然是内核对象。另一方面,如果个内核对象只在内核中使用,而不向用户空间开放,对于用户空间的程序而言是不可见、不可操作的,那么这只是关系到如何实现内核、在内核的实现上采用何种程序设计方法、导致何种结构的问题,而并不关系到内核的功能和外部表现,那就意义不是很大了,至少对于应用软件而言就没有意义了。因此我们所特别关心的是对用户程序开放、可以为用户程序所用的内核对象。所谓向用户程序开放,是说为用户程序提供了对其数据结构实行操作的“方法”,即系统调用。
那么 Windows内核向用户提供了哪些对象呢?下面是个不完整的列表(取自《UndocumentedWindows 2000Secrets》一书),表中列出了 Windows 2000的对象类型、用来创建/打开这些对象的这些对象类型大都为对象的创建和打开配备了专门的系统调用,例如对象类型Key就有NtCreateKey()和NtOpenKey()。但是其中Mailslot和NamedPipe 却又有些特殊,这两个对象类型有专门用于“创建”的系统调用NtCreateMailslotFile()和NtCreateNamedPipeFile(),但是却没有专门用于“打开”的系统调用,而只是借用NtOpenFile(),其读/写操作则借用NtReadFile()、NtWriteFile()。
除上列这些对象类型之外,还有Device、Driver、Adapler等用于设备驱动的对象类型,这些对象类型并没有专用的系统调用,而是借用NOpenFile()作为创建/打开的方法。此外,对象类型并不是一个封闭的集合,内核可安装模块(.sys模块)可以创建新的对象类型,这些新的对象类型也只能借用 NOpenFie()作为创建/打开的方法。事实上,NtOpenFile0是创建/打开对象的通用方法,凡是没有专门为其配备系统调用的对象类型,就都以penFile()作为创建/打开的方法,并借用NiReadFile()、NtWriteFile()、NtDeviceloControlFile()等本来用于文件操作的方法作为各种操作的方法,这就又有些像 UNIX/Linux了
不管是什么对象,不管是以什么方法创建/打开的对象,关闭对象的方法(对于用户程序而言)是共同的,那就是NtClose()。
这里要说明,尽管都用NtClose()作为关闭对象的方法,尽管用户程序所看到的是同样的系统调用NCoseO,在内核中实际执行的函数则因对象类型的不同而异,这就是“界面”和“实现”的区别。其他的系统调用,如NtOpenFile()、NtReadFile()、NtWriteFile()等也都如此。
对于几乎所有的对象,Windows内核都提供一个统一的操作模式,就是先通过系统调用“打开”或创建目标对象,让当前进程与目标对象之间建立起连接,然后再通过别的系统调用进行操作,最后通过系统调用“关闭”对象,实际上是关闭当前进程与目标对象的联系。在打开对象的时候,方面要说明对象的类别,另一方面要说明怎样找到具体的目标对象,即提供目标对象的“路径"显然,“打开”对象也是“定义于其上的操作”之一,但又是一种特殊的操作,其实质在于让当前进程与目标对象之间建立起连接。
建立连接是维持对于目标对象进行操作的上下文所必需的。例如对于文件和设备的操作一般都是需要维持上下文的,所以需要事先建立连接,即打开目标对象。反之,如果是无须上下文的一次性操作,就不一定需要事先打开。例如删除一个文件,就是只要给定文件(路径)名就可以进行的-次性的操作,因而不需要维持上下文,事实上Windows的系统调用NtDeleteFileO)就不要求事先打开。但是,在 Windows的对象管理机制中,除少数例外以外,对于几乎所有对象的所有操作都必须遵循先打开后操作再关闭的统一模式。
内核在为当前进程打开目标对象之后,即在当前进程与目标对象之间建立起联系之后,就返回-个称为“Handle”的识别号,作为后续操作的依据。“Handle”这个词,一般都翻译成“句柄”,笔者觉得这个译名有些牵强,没有接触过编译原理的人根本就不知句柄为何物,可是却又想不出更好的替代,所以只好跟着用“句柄”。
那么,在Windows内核中“对象”究竟是什么呢?事实上Windows内核中的“对象”与“面向对象的程序设计”中所说的对象还是有很大的不同的。首先这里面没有“封装”、“继承”、“多态(Polymorphism)”这些基本要素,所以最多也只能称为“基于对象的程序设计(Obiect-BasedProgramming)”,而不能说是“面向对象的程序设计(Obiect-Orented Programming)”。其次,即使就“对象”这个概念而言,Windows内核中对象的“操作”与其数据结构的结合也是很松散的。所以,在Windows内核中,“对象”实质上就是数据结构,就是一些带有“对象头(Object Head)”的特殊数据结构。
任何对象的数据结构都由对象头和具体对象类型的数据结构两部分构成。但是,由于对象头的结构较为特殊,其中有些成分是可选的,所以实际上分成了三个部分。其一是对象头的OBJECT_HEADER数据结构,其二是具体对象类型的数据结构本身,例如KEVENT、FILE_OBJECT等,最后是几个作为可选项的附加信息,包括:
关于创建者的信息OBJECT_HEADER_CREATORINFO数据结构,用来将所创建的对象挂入其创建者的对象队列:
载有对象名和目录节点指针的OBJECT_HEADER_NAME_INFO数据结构:
关于句柄的信息OBJECT_HEADER_HANDLE_INFO 数据结构:
关于耗用内存配额的信息。
这些附加信息是可选项,可以有也可以没有。
三个部分按特殊的方式连成一片。按地址由低到高,首先当然是OBJECT_HEADER,它的上方是具体对象类型的数据结构本身;然而附加信息不是在具体对象类型的数据结构上方,也不是在这二者之间,而是在OBJECT_HEADER的下面。我们来看OBJECT_HEADER的定义:
OBJECT_HEADER
//
// Object Header
//
typedef struct _OBJECT_HEADER
{LONG PointerCount;union{LONG HandleCount;volatile PVOID NextToFree;};POBJECT_TYPE Type;UCHAR NameInfoOffset;UCHAR HandleInfoOffset;UCHAR QuotaInfoOffset;UCHAR Flags;union{POBJECT_CREATE_INFORMATION ObjectCreateInfo;PVOID QuotaBlockCharged;};PSECURITY_DESCRIPTOR SecurityDescriptor;QUAD Body;
} OBJECT_HEADER, *POBJECT_HEADER;
显然,Body就是具体对象类型的数据结构本身,在这里它的类型为QUAD即长整数,实际上则是一个数据结构的起点。字段名NamelnfoOfset 等是位移量而不是指针,这说明其所指向的数据结构是与OBJECT_HEADER连成一片的。其实,从这几个字段的类型UCHAR也可以看出这一点,因为8位数据所能表达的位移是很有限的。相比之下,SecurityDescriptor和ObiectCreatelnfo 就是指针,它们所指向的数据结构就是独立存在的了。所以对象的数据结构有着如下图所述的形态。
这样的安排也许有点道理,因为附加信息都是可选项,而具体对象类型的数据结构又大小不定把OBJECTHEADERNAMEINFO等放在OBJECTHEADER下面,用8位字节表示位移量就够了,否则就得要16位,因为具体类型的数据结构很可能大于8位字节所能表达的范围:而若放在当中,则对于指针的换算(指向对象本身或指向对象头)带来麻烦。注意BJECTHEADER里面没有字段说明OBJECT_HEADER_CREATOR_INFO的位移,这是因为这个结构的位移一定是sizeof(OBJECT HEADER CREATOR INFO)。
在实际使用中,常常因需要访问其中的不同成分而要换算它们的起始地址,所以ReactOs的代码中为此定义了一些宏操作,下面是其中的几个:
#defne OBJECT_TO_OBJECT HEADER(O)\CONTAINING RECORD((O),OBJECT HEADER, Body)
CONTAINING_RECORD是个常用的宏操作,用来将指向某个结构成分的指针换算成指向其所在数据结构的指针:
#defne CONTAINING_RECORD(address, type, field)\
((type *)(((ULONG_PTR)address) - (ULONG_PTR)(&(((type *)0)->field)))
这样,以 OBJECT TO_OBJECT HEADER 为例,给定结构成分Body 的地址 o,就可以从中减去Body在 OBJECTHEADER中的位移而算得其所在 OBJECT HEADER 数据结构的起始地址。而结构成分Body的地址实际上就是对象(数据结构)本身的地址。
再看从OBJECT_HEADER指针怎样算得指向OBJECTHEADER_NAME_INFO数据结构的指针:
#dcfine OBJECT HEADER TO NAME INFO()((POBJECT HEADER NAME INFO)(!(h)->NamelnfoOffset ?NULL: ((PCHAR)(h) - (h)->NamelnfoOffset)))
如果字段NamelnfoOfset非0,就从OBJECT_HEADER 数据结构的起始地址中减去这个字段的值即位移。由此也可以看出,OBJECTHEADERNAMEINFO是在OBJECTHEADER的下方
但是OBJECT HEADER CREATOR INFO 又是特殊的:
#deñne OBJECT HEADER TO CREATOR INFO(oh )\
((POBJECT HEADER CREATOR INFO)\
(((oh)->Fags&OB FLAG CREATOR INFO)==0?\NULL:((PCHAR)(oh)-sizeOf(OBJECT HEADER CREATOR INFO))))
可见,OBJECT_HEADER_CREATOR_INFO数据结构,如果有(字段Fags中的相应标志位非0)的话,必须是紧贴在 OBJECT HEADER下方的第一个数据结构。
-个对象的数据结构的大小,是该对象所属类型的数据结构本身、OBJECT_HEADER数据结构,以及4个可选数据结构(如果选用的话)大小的总和。
值得注意的是,在OBJECTHEADER 数据结构中没有用来将其挂入某个队列的队列头或指针,在 OBJECT_HEADER_QUOTA_INFO、OBJECT_HEADER_HANDLE_INFO 和 OBJECT_HEADER_TO_NAME_INFO中也没有这样的成分。唯一的例外是在 OBJECT HEADERTOCREATOR_INFO 数据结检中有个队列头TypeLis,但这只是用来将同一类型的对象链接在一起。由此可见,对象自身并没有提供构成对象目录的手段,下面我们将看到,构成对象目录需要有相当于螺丝钉之类的辅助数据结构的配合。
创建了一个对象并返回句柄之后,创建该对象的进程就可以通过使用句柄访问它,这样的对象可以是无名的对象。
然而,在许多情况下需要使用对象名,这样的情况有:
。一个进程创建了一个对象之后,别的进程需要共享这个对象。可是别的进程并不知道这个对象在其创建者进程里的句柄,即使知道也没有办法共享,因为旬柄是属于特定进程而不是全局的。为了共享,别的对象需要“打开”同一个对象,这时候就需要有对象名了。
同一个进程可能需要有访问同一个对象的多个上下文(例如文件的读/写位置)。所谓“创建”对象,是在创建一个目标对象的同时创建一个上下文,此后每当需要增加一个新的上下文时就得再“打开”一次同一个目标对象,此时往往也需要使用对象名。
有些对象的内容是永久性(Persistant)的,例如文件就是这样,这一次写入的数据可能要到将来的某个时候再来读出。
所以,一般而言对象是需要命名的。那么,如果系统中存在着许多命名对象,又应怎样加以组织和管理呢?这得靠“对象目录”。显然,只有命名的对象才可以、才有必要进入对象目录。
对象目录是多个“节点”连接而成的树状结构(如果考虑到“符号连接”则可以是网状结构,但符号连接只是偶尔使用,所以主要还是树状结构),树的根是一个“目录”对象,即类型为OBJECT_DIRECTORY的对象。树中的每个节点都是对象,所以“节点名”就是“对象名”。除根节点之外,树中的所有中间节点都必须是目录对象或“符号连接”对象(即类型为OBJECT_SYMBOLIC_LINK的对象),而普通的对象则只能成为“叶节点”。对于对象目录中的任何节点,如果从根节点或某个中间节点开始逐节向此节点前进,记下沿途各个节点的节点名,并以分隔符“\”加以分隔,就形成一个“路径名"。如果路径名中的第一个节点是根节点,就是全路径名或称绝对路径名,否则就是相对路径名,即相对于某个中间节点的路径名。根节点的节点名为“\”内核中有个全局的指针 ObpRootDirectoryObiect指向对象目录的根节点(的数据结构)。
显然,目录节点是对象目录的骨干,没有目录节点就形不成对象目录。另一方面,目录节点本身也是一种对象,其数据结构为OBJECTDIRECTORY。这个数据结构的定义因版本的不同而有所不同,但在这里我们只关心其主体也是其中的第一个成分:
typedef struct _OBJECT DIRECTORY
stnct OBJECT DIRECTORY ENTRY *HashBuckets[NUMBER HASH BUCKETS]:
}OBJECT DIRECTORY,*POBJECT DIRECTORY;
其中的第一个成分 HashBuckets[]是个OBJECTDIRECTORY_ENTRY结构指针数组。这是个散列表,数组中的每个指针都可以用来维持一个“(对象)目录项"即OBJECTDIRECTORYENTRY结构的队列。目录项结构本身并非对象,但是除根节点以外的所有节点都要靠目录项结构才能插入目录,所以这种结构起着类似于螺丝钉的作用,其定义为:
typedef struct OBJECT DIRECTORY ENTRY
struct OBJECT DIRECTORY ENTRY *ChainLink;PVOID Object;#f(NTDDI VERSION >= NTDDI WS03)
ULONG HashValue;#endif
}OBJECT DIRECTORY ENTRY;
指针 ChainLink用来构成队列,而指针 Object 则指向其所连接的对象除根目录节点以外,对象目录中的每个节点(对象)都必须挂在某个目录节点的某个散列队列中,具体挂在哪一个队列中则取决于节点名(对象名)的Hash值。
下面是一张对象目录的示意图。
先看根节点,这是个目录对象,其主体是个Hash表,这里只画出了Hash表中的两个队列。上面这个队列里有4个节点,这4个节点的对象名都有相同的Hash值,所以挂在同一个队列中。每个节点都是一个对象,但是需要有个目录项作为连接件才能进入对象目录:对象本身的数据结构和对象头中都没有用于该项目的队列头或指针。之所以这样设计,估计是因为考虑到有许多对象是无名的,因而不需要进入对象目录。在这4个节点中,对象“M”是个目录节点,但是此刻这个目录还是空的,其余3个节点则都是普通对象,所以这4个节点都是叶节点。注意在节点A和H之间没有层次的关系,它们属于同一层次,都是根目录下的节点,余类推。
根节点的下面这个队列中有两个节点,即“」”和“D"。其中“」”又是个目录节点,而且该目录已经不再空白,所以这是个中间节点。
如前所述,只有两种对象可以充任中间节点,一种是目录,另一种是符号连接。图中的节点“Y”就是个符号连接对象,这个对象指向了上面的目录节点“M"。不过,符号连接对象的数据结构中其实并没有这样的指针,而只是用“M”的全路径名来说明连接的目标。符号连接的目标可以是目录节点,也可以是叶节点,还可以是另一个符号连接节点。所以,从某种意义上说,符号连接的作用是为一个路径名起了一个别名。
注意图中有两个节点都名为“D",但这是在不同的目录节点下面,它们的全路径名不同,所以不会混淆。如前所述,根节点的节点名是“\”,所以其中之一的全路径名为“\D”,而另一个则为“W\D”再看节点“M”,由于符号连接的存在,这个节点有两个全路径名,一个是“\M”,另一个是“U\Y”
为了更好地理解对象目录的结构,我们来看一下函数ObpLookupEntryDirectory()的代码。给定-个节点名Name,这个函数在给定的目录节点Directory中寻找同名的节点。
ObpLookupEntryDirectory()
/*++
* @name ObpLookupEntryDirectory
*
* The ObpLookupEntryDirectory routine <FILLMEIN>.
*
* @param Directory
* <FILLMEIN>.
*
* @param Name
* <FILLMEIN>.
*
* @param Attributes
* <FILLMEIN>.
*
* @param SearchShadow
* <FILLMEIN>.
*
* @param Context
* <FILLMEIN>.
*
* @return Pointer to the object which was found, or NULL otherwise.
*
* @remarks None.
*
*--*/
PVOID
NTAPI
ObpLookupEntryDirectory(IN POBJECT_DIRECTORY Directory,IN PUNICODE_STRING Name,IN ULONG Attributes,IN UCHAR SearchShadow,IN POBP_LOOKUP_CONTEXT Context)
{BOOLEAN CaseInsensitive = FALSE;POBJECT_HEADER_NAME_INFO HeaderNameInfo;POBJECT_HEADER ObjectHeader;ULONG HashValue;ULONG HashIndex;LONG TotalChars;WCHAR CurrentChar;POBJECT_DIRECTORY_ENTRY *AllocatedEntry;POBJECT_DIRECTORY_ENTRY *LookupBucket;POBJECT_DIRECTORY_ENTRY CurrentEntry;PVOID FoundObject = NULL;PWSTR Buffer;PAGED_CODE();/* Check if we should search the shadow directory */if (!ObpLUIDDeviceMapsEnabled) SearchShadow = FALSE;/* Fail if we don't have a directory or name */if (!(Directory) || !(Name)) goto Quickie;/* Get name information */TotalChars = Name->Length / sizeof(WCHAR);Buffer = Name->Buffer;/* Set up case-sensitivity */if (Attributes & OBJ_CASE_INSENSITIVE) CaseInsensitive = TRUE;/* Fail if the name is empty */if (!(Buffer) || !(TotalChars)) goto Quickie;/* Create the Hash */for (HashValue = 0; TotalChars; TotalChars--){/* Go to the next Character */CurrentChar = *Buffer++;/* Prepare the Hash */HashValue += (HashValue << 1) + (HashValue >> 1);/* Create the rest based on the name */if (CurrentChar < 'a') HashValue += CurrentChar;else if (CurrentChar > 'z') HashValue += RtlUpcaseUnicodeChar(CurrentChar);else HashValue += (CurrentChar - ('a'-'A'));}/* Merge it with our number of hash buckets */HashIndex = HashValue % 37;/* Save the result */Context->HashValue = HashValue;Context->HashIndex = (USHORT)HashIndex;/* Get the root entry and set it as our lookup bucket */AllocatedEntry = &Directory->HashBuckets[HashIndex];LookupBucket = AllocatedEntry;/* Check if the directory is already locked */if (!Context->DirectoryLocked){/* Lock it */ObpAcquireDirectoryLockShared(Directory, Context);}/* Start looping */while ((CurrentEntry = *AllocatedEntry)){/* Do the hashes match? */if (CurrentEntry->HashValue == HashValue){/* Make sure that it has a name */ObjectHeader = OBJECT_TO_OBJECT_HEADER(CurrentEntry->Object);/* Get the name information */ASSERT(ObjectHeader->NameInfoOffset != 0);HeaderNameInfo = OBJECT_HEADER_TO_NAME_INFO(ObjectHeader);/* Do the names match? */if ((Name->Length == HeaderNameInfo->Name.Length) &&(RtlEqualUnicodeString(Name, &HeaderNameInfo->Name, CaseInsensitive))){break;}}/* Move to the next entry */AllocatedEntry = &CurrentEntry->ChainLink;}/* Check if we still have an entry */if (CurrentEntry){/* Set this entry as the first, to speed up incoming insertion */if (AllocatedEntry != LookupBucket){/* Check if the directory was locked or convert the lock */if ((Context->DirectoryLocked) ||(ExConvertPushLockSharedToExclusive(&Directory->Lock))){/* Set the Current Entry */*AllocatedEntry = CurrentEntry->ChainLink;/* Link to the old Hash Entry */CurrentEntry->ChainLink = *LookupBucket;/* Set the new Hash Entry */*LookupBucket = CurrentEntry;}}/* Save the found object */FoundObject = CurrentEntry->Object;goto Quickie;}else{/* Check if the directory was locked */if (!Context->DirectoryLocked){/* Release the lock */ObpReleaseDirectoryLock(Directory, Context);}/* Check if we should scan the shadow directory */if ((SearchShadow) && (Directory->DeviceMap)){/* FIXME: We don't support this yet */KEBUGCHECK(0);}}Quickie:/* Check if we inserted an object */if (FoundObject){/* Get the object name information */ObjectHeader = OBJECT_TO_OBJECT_HEADER(FoundObject);ObpAcquireNameInformation(ObjectHeader);/* Reference the object being looked up */ObReferenceObject(FoundObject);/* Check if the directory was locked */if (!Context->DirectoryLocked){/* Release the lock */ObpReleaseDirectoryLock(Directory, Context);}}/* Check if we found an object already */if (Context->Object){/* We already did a lookup, so remove this object's query reference */ObjectHeader = OBJECT_TO_OBJECT_HEADER(Context->Object);HeaderNameInfo = OBJECT_HEADER_TO_NAME_INFO(ObjectHeader);ObpReleaseNameInformation(HeaderNameInfo);/* Also dereference the object itself */ObDereferenceObject(Context->Object);}/* Return the object we found */Context->Object = FoundObject;return FoundObject;
}
代码中已加了注释,读者可以自己阅读。整个流程其实也很简单,先计算对象名的Hash值,Hash值对 37取余就是Hash 队列数组的下标。找到具体的队列之后就扫描这个队列,逐个节点比较对象名的 Unicode 字符串。如果找到了目标节点就将其移至队列的前面,以加速(很可能会发生的)对同一节点的下一次寻找。这里还要注意,参数Context的性质说是IN,但实际是IN和 OUT,因为函数返回时 Context 内部的指针 Object 指向目标对象或为NULL。另一方面,如果找到了目标对象,则该对象及其对象名信息均已被引用,其引用计数已被递增。这样,如果连续两次调用bpLookupEntryDirectory0),则有可能前一次的应用尚未被释放,所以这里要检査 Context->Object是否非空,如果非空就要在改变指针 Context->Object 的值之前先将老的引用释放。
下面以定时器(Timer)对象为例,粗略看一下对象的创建过程。定时器对象的创建通过NiCreateTimer0)完成,许多用于创建对象的系统调用都有大致相同的程序结构,只是具体对象的类型不同而已。
NtCreateTimer()
NTSTATUS
NTAPI
NtCreateTimer(OUT PHANDLE TimerHandle,IN ACCESS_MASK DesiredAccess,IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,IN TIMER_TYPE TimerType)
{PETIMER Timer;HANDLE hTimer;KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();NTSTATUS Status = STATUS_SUCCESS;PAGED_CODE();/* Check for correct timer type */if ((TimerType != NotificationTimer) &&(TimerType != SynchronizationTimer)){/* Fail */return STATUS_INVALID_PARAMETER_4;}/* Check Parameter Validity */if (PreviousMode != KernelMode){_SEH_TRY{ProbeForWriteHandle(TimerHandle);}_SEH_EXCEPT(_SEH_ExSystemExceptionFilter){Status = _SEH_GetExceptionCode();}_SEH_END;if(!NT_SUCCESS(Status)) return Status;}/* Create the Object */Status = ObCreateObject(PreviousMode,ExTimerType,ObjectAttributes,PreviousMode,NULL,sizeof(ETIMER),0,0,(PVOID*)&Timer);if (NT_SUCCESS(Status)){/* Initialize the DPC */KeInitializeDpc(&Timer->TimerDpc, ExpTimerDpcRoutine, Timer);/* Initialize the Kernel Timer */KeInitializeTimerEx(&Timer->KeTimer, TimerType);/* Initialize the timer fields */KeInitializeSpinLock(&Timer->Lock);Timer->ApcAssociated = FALSE;Timer->WakeTimer = FALSE;Timer->WakeTimerListEntry.Flink = NULL;/* Insert the Timer */Status = ObInsertObject((PVOID)Timer,NULL,DesiredAccess,0,NULL,&hTimer);/* Check for success */if (NT_SUCCESS(Status)){/* Make sure it's safe to write to the handle */_SEH_TRY{*TimerHandle = hTimer;}_SEH_EXCEPT(_SEH_ExSystemExceptionFilter){}_SEH_END;}}/* Return to Caller */return Status;
}
这里先通过 ObCreateObject0创建一个类型为ExTimerType的对象。这个的数的第二个参数是个“对象类型”指针,指向定义了Timer 对象的数据结构。在这里,就是OBJECT_TYPE 指针ExTimerType,所指向的就是定义了Timer对象的OBJECT_TYPE 数据结构,这个数据结构中有着很多必要的信息。
通过ObCreateObject()创建了新的ETIMER对象并进行必要的初始化之后,还要通过ObnserObject()将其插入对象目录和本进程的句柄表,并返回相应的句柄。这个函数的两大功能之一就是为给定的对象(连同其头部)分配一个目录项结构并进行初始化,将该对象命名为给定的“对象名”,然后借助目录项结构将其挂入指定的对象目录。后面我们还将阅读这个函数的代码。