读书频道 > 系统 > windows > Windows内核原理与实现
6.2.3 文件对象
2013-05-18 16:35:13     我来说两句 
收藏    我要投稿   

本文所属图书 > Windows内核原理与实现

本书从操作系统原理的角度,详细解析了Windows如何实现现代操作系统的各个关键部件,包括进程、线程、物理内存和虚拟内存的管理,Windows中的同步和并发性支持,以及Windows的I/O模型。在介绍这些关键部件时,本...  立即去当当网订购

上一小节介绍的驱动程序对象和设备对象都是内核中的对象。在Windows 中,所有的I/O 请求都是针对设备对象的,然而,应用程序并不能直接访问设备对象,尽管它们可以通过符号名称来指定已命名的设备对象。Windows I/O系统提供给应用程序的I/O 操作的目标对象是文件对象(File Object )。文件对象代表了设备对象的已打开实例,也就是说,内核或应用程序每打开(open )一个设备对象,就将得到一个文件对象。文件对象也是内核对象,但用户模式代码可以通过句柄来引用文件对象。驱动程序对象、设备对象和文件对象之间的关系如图6.6所示。


 

文件对象是I/O 操作的基本抽象,它将应用程序对设备的操作抽象成了对文件数据的读或写动作。文件对象也是对象管理器中的对象,其类型为IoFileObjectType。文件对象作为设备对象的已打开实例,不仅可以代表块设备(比如磁盘)中的文件,还可以代表任何其他设备,包括逻辑设备上的操作实例。因此,内核中的文件对象比通常意义上的磁盘文件的概念要宽泛得多。例如,Windows 中用于进程间通信的命名管道对象和邮件槽对象实际上也是通过文件对象来描述的。

如同对象管理器中的其他对象一样,进程对文件对象的访问要经过系统的安全引用监视器(SRM,Security Reference Monitor ,参见 2.5.4节的介绍)的检查。应用程序调用CreateFile函数(或者通过 C 运行库的fopen 函数)可以创建一个文件对象,并得到该文件对象的一个句柄。在内核中,这是通过系统服务 NtCreateFile函数来完成的。以后,应用程序通过ReadFile 或ReadFileEx ,以及WriteFile 或WriteFileEx 函数来读写此文件对象。

当应用程序要结束对文件对象的操作时,只需调用 CloseHandle函数即可。文件对象通过引用计数来维护其自身的生命周期。 文件对象代表了设备对象的已打开实例,它并不承担设备对象的数据存储和状态变迁的能力。真正的文件数据位于设备对象而非文件对象中。多个文件对象可以指向同一个设备对象,因而它们共享同样的设备对象。对文件对象的操作有必要进行同步。譬如,如果一个线程要对一个文件进行写操作,那么它在打开该文件时,必须指定它要对文件进行互斥写访问,以防止其他的线程或进程同时执行写操作。

在介绍文件对象的打开和读写操作以前,我们先看一下Windows 中文件对象的定义,如下所示(见base\ntos\inc\io.h文件):
typedef struct _FILE_OBJECT {
    CSHORT Type;
    CSHORT Size;
    PDEVICE_OBJECT DeviceObject;  // 指向文件所在的设备对象
    PVPB Vpb;  //  指向文件对象所在卷的卷参数块(VPB )
    PVOID FsContext;   //  指向驱动程序为该文件对象维护的状态信息
    PVOID FsContext2;  //  指向驱动程序为该文件对象维护的额外状态信息
    PSECTION_OBJECT_POINTERS SectionObjectPointer;   //  文件对象的内存区对象指针
    PVOID PrivateCacheMap;  //  文件对象的私有缓存表
    NTSTATUS FinalStatus;   //  文件对象I/O 请求的最终状态
    struct _FILE_OBJECT *RelatedFileObject;// 相关的文件对象
    BOOLEAN LockOperation;  //  是否已在文件对象上执行了锁(lock)操作
    BOOLEAN DeletePending;  //  正在执行一个删除与文件对象关联的文件的操作
    BOOLEAN ReadAccess;  //  以读访问方式打开该文件
    BOOLEAN WriteAccess;  //  以写访问方式打开该文件
    BOOLEAN DeleteAccess;   //  以删除访问方式打开该文件
    BOOLEAN SharedRead;  //  以读共享访问方式打开该文件
    BOOLEAN SharedWrite;  //  以写共享访问方式打开该文件
    BOOLEAN SharedDelete;   //  以删除共享访问方式打开该文件
    ULONG Flags;  //  标志,以FO_ 作为前缀定义的一组常量,可以组合
    UNICODE_STRING FileName;  //  文件名,仅在IRP_MJ_CREATE 请求中有效
    LARGE_INTEGER CurrentByteOffset;   //  文件中的当前偏移位置,以字节为单位
    ULONG Waiters;  //  有多少个线程在等待该文件对象,以进行同步访问
    ULONG Busy;   //  当前是否有线程在以同步方式访问该文件对象
    PVOID LastLock;  //  指向上一个应用在该文件对象上的字节范围锁
    KEVENT Lock;  //  文件对象锁,用于同步访问该文件对象
    KEVENT Event;   //  文件对象锁,用于 I/O 请求的完成通知
    PIO_COMPLETION_CONTEXT CompletionContext;    //  指向与文件对象关联的完成端口信息
} FILE_OBJECT;
typedef struct _FILE_OBJECT *PFILE_OBJECT;

文件对象数据结构已被文档化,并包含在Windows 的驱动程序开发包中。设备对象是系统范围内共享的真正实体对象,而文件对象只是代表了它的一个已打开实例,因此,FILE_OBJECT 数据结构只需维护一个访问实例所需要的信息即可。在以上定义中,我们可以看到,FILE_OBJECT 包含了指向设备对象的指针、由驱动程序为文件对象维护的状态环境、当前位置信息、访问方式和文件对象标志,以及当多个线程访问同一个文件对象时所需要的各种锁。

文件对象经由 IoCreateFile 函数来创建,三个系统服务函数NtCreateFile、NtCreateNamedPipeFile 和NtCreateMailslotFile 都把创建普通文件对象、命名管道或邮件槽对象的工作交给IoCreateFile函数。IoCreateFile 进一步调用IopCreateFile 函数来创建文件对象。IopCreateFile 函数的代码位于 base\ntos\io\iomgr\iosubs.c 文件中,除了要对传递进来的参数进行各种检查和预处理以外,它并不是简单地调用ObCreateObject 函数来创建一个IoFileObjectType 类型的对象,再执行文件对象的初始化工作;相反地,IopCreateFile 函数调用对象管理器的 ObOpenObjectByName 函数来创建文件对象。ObOpenObjectByName的结果是一个指向所创建对象的句柄,只要ObOpenObjectByName 返回成功,IopCreateFile函数即成功返回。

在2.5.1 节介绍对象管理器时,我们曾经解释过ObOpenObjectByName利用对象管理器的另一个函数 ObpLookupObjectName 来打开一个对象,并且也介绍了 ObpLookupObjectName函数的工作流程。ObpLookupObjectName 从指定的根目录或者系统全局根目录开始,调用ObpLookupDirectoryEntry 函数,一层一层地进入子目录,直至解析完毕,或者碰到实现了Parse方法的对象,从而把余下的路径名称交给该对象进一步解析。

因此,我们可以想象,文件对象是在此递进过程中,由最后一层的子目录对象或者负责解析最后一部分名称串的对象的Parse方法来创建的。而且,在此过程中,有可能会生成多个文件对象,这些文件对象形成了上下层的依赖关系,由此也可以理解FILE_OBJECT结构中的RelatedFileObject成员的意义。为便于理解,我们通过一个例子来说明文件对象的创建过程。在这个例子中,待打开的文件对象的名称是“\Device\MyDevice ”,打开文件的过程如图6.7所示。


 

在ObpLookupObjectName 函数中,它从对象管理器的根目录,即全局变量ObpRootDirectoryObject(见ObInitSystem 函数中的代码)中查找“Device ”目录对象。“\Device”目录是系统在阶段 1 初始化过程中调用 CreateSystemRootLink函数创建的,见base\ntos\init\initos.c 文件中的代码。目录对象的类型为 ObpDirectoryObjectType,其 Parse方法,即创建该类型对象时在 OBJECT_TYPE_INITIALIZER 参数中指定的 ParseProcedure成员为NULL,所以,在ObpLookupObjectName 函数中,进入while循环的下一次迭代。

ObpLookupObjectName第二次调用ObpLookupDirectoryEntry,这一次从“ \Device”目录中查找“MyDevice”对象。这次调用得到的是设备对象“MyDevice”,其类型为IoDeviceObjectType 。类型对象 IoDeviceObjectType 是在I/O 系统初始化时调用IopCreateObjectTypes 函数创建的,它的 Parse 方法为IopParseDevice 函数,参见base\ntos\io\iomgr\ioinit.c 文件中的代码。然后,ObpLookupObjectName调用设备对象的Parse方法,即IopParseDevice 函数,并将名称“\Device\MyDevice ”传递进去。

IopParseDevice 函数调用ObCreateObject 来创建一个 IoFileObjectType 类型的对象,该文件对象的RelatedFileObject成员为NULL,DeviceObject 成员指向“MyDevice”设备对象。然后,它调用 IoCallDriver 函数,向驱动程序发送一个“IRP_MJ_CREATE ”I/O 请求,即通知驱动程序要创建该设备对象的一个文件对象。IopParseDevice 函数的代码位于base\ntos\io\iomgr\parse.c 文件的 303~1 896 行,这里不再详细解释。

由以上例子可以看出,文件对象的创建是对象管理器与设备驱动程序协作完成的结果。实际上,对象管理器提供的名称目录结构是系统内核名字空间的切入点,该目录中的对象的类型对象所提供的Parse例程将此目录结构进一步扩充到设备对象的名字空间中,从而全局目录结构与设备私有的目录结构无缝地结合起来。例如,文件系统驱动程序扩展了内核的目录结构,它们在磁盘卷上构建出延伸的文件名字空间。

文件对象一旦被创建,调用者即获得一个指向该文件对象的句柄,以后,它利用该句柄,通过 I/O 管理器提供的系统服务,比如 NtWriteFile 、NtReadFile 等,可读写设备中的数据。关于I/O 请求的处理过程,参见6.6节。

点击复制链接 与好友分享!回本站首页
分享到: 更多
您对本文章有什么意见或着疑问吗?请到论坛讨论您的关注和建议是我们前行的参考和动力  
上一篇:6.2.2 驱动程序对象和设备对象
下一篇:6.2.4 对象生命周期管理
相关文章
图文推荐
3.4.4 进程生命期管
3.4.2 Windows应用商
3.4.1 Windows应用商
3.4 进程生命期管理
排行
热门
文章
下载
读书

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训
版权所有: 红黑联盟--致力于做最好的IT技术学习网站