国外PDM与国产PDM的对比分析

八月 16th, 2008

    1 引言
    PDM(Product Data Management)是以产品数据为中心,集成并管理所有与产品相关的信息、过程、人与组织的大型管理软件。目前,国外典型的PDM软件主要有UGS公司的TeamCenter、PTC公司的Windchill、MatrixOne公司的e-Matrix、IBM公司的SmarTeam、SAP公司的 mySAP等,这些软件基本代表了现今PDM技术的最高水平。经过多年的发展,国产PDM软件得到了长足的进步,但与国外PDM软件仍存在一定的差距,主要表现在体系架构、信息模型、功能模块、解决方案和实施方案学等几个方面。

    2 技术分析
    
     2.1 体系架构

    软件的体系架构决定了软件的可应用性、可扩展性等重要特性,不同的企业选择体系结构适合自身特点的PDM系统才能最大限度地确保实施的成功。
   
     随着Web技术、面向对象技术的不断发展和应用,国外PDM的体系结构日趋先进,已经从传统的客户机/服务器结构转向基于Web应用、J2EE技术、 C/B/S三段式结构的多层体系框架。通常,PDM系统的体系结构整体上分五层:底层平台层、PDM核心服务层、PDM应用组件层、应用工具层和实施理念层,这里主要讨论PDM核心服务层。
   
    PDM软件产品一般指的是核心服务层和应用组件层。在C/S结构下,核心服务层一般就是服务器端,客户端软件就属于PDM的应用组件,在C/B/S结构下,二者都运行于服务器端,但安装有所不同,核心服务是必须的,而应用组件是选用的,如TeamCenter的对象管理框架、Windchill的 Windchill Foundation、e-Matrix的AEF(Application Exchange Framework)都是典型的核心服务。

    核心服务层向下连接并操纵数据库,向上为应用组件服务,如Web处理机制、API应用集成接口等。核心服务层的核心是信息模型,它也是PDM的技术核心。

    相比而言,国产PDM在Web应用、J2EE技术等方面的发展比较慢,主流PDM软件普遍还停留在客户机/服务器模式,只有少数几家发布了支持Web应用的PLM产品,如清软英泰、上海同捷;在对象模型及多层结构方面,国产PDM正逐步朝建立核心服务层、应用组件层的方向努力,如开目PDM的对象模型。

    总之,受各种条件的限制,国产PDM软件可能在PDM体系结构五个层次的某个层次上有所进步和突破,但总体上与国外PDM软件还有一定距离。

    2.2 信息模型

   信息模型是PDM核心服务层的基础,在PDM体系结构中占有重要地位,包括对象模型和过程模型两方面。
   
    2.2.1 对象模型

    国外PDM系统已普遍采用了面向对象的数据建模方法。随着面向对象的方法、分布式技术的发展,国外PDM系统的对象模型已逐渐发展为面向对象、支持分布式管理的单一产品数据源的对象模型,如TeamCenter、Windchill、e-Matrix、SmartTeam等。

    在国内,各PDM厂商越来越意识到数据建模技术所提供的可扩展性对支持用户复杂业务和个性需求的重要性,已经从基于扩展数据表或字段的配置技术提升为使用建模工具进行数据建模的技术。面向对象的建模技术的研究也取得了一系列研究成果,并在一些国产的商品化PDM系统中得到了应用。国内PDM关于数据建模技术的一些现状和特点如下:

    * 部分国内厂商已经提供了面向对象的对象模型支持,如武汉开目和CAXA采用面向对象的对象模型;
    * 各厂商提供的对象模型各有特点,如清软英泰提供了基于语义网络的对象模型;
    * 对建模工具的图形化支持还不够,面向对象的能力支持不够,需要进一步提高性能,并扩展模型对PDM的支持;
    * 部分厂商提供了分布式对象模型支持,但分布式对象模型应用有多个层次,绝大多数国内PDM厂商并不支持分布式对象模型或只提供较简单的应用模式;
    * 分布式对象模型的用户一般是大型企业,往往选择国外知名的PDM系统,而绝大多数国内PDM厂商定位在中小型企业,所以关于分布式对象模型研究进展缓慢。

    2.2.2 过程模型

    国外的主流PDM系统均提供了较为强大的过程模型支持。
 
    在对象的全生命周期管理方面,国外的研究已基本成熟,并已在实际的软件系统中得到应用,如Smarteam、Windchill、TeamCenter和 e-Matrix等系统均支持对象的全生命周期管理。在工作流管理方面,国外开展的研究比较早,也比较深入。1995年,工作流管理联盟根据研究成果和应用实践制定了工作流管理系统的相关术语、体系结构及应用接口等方面的一系列标准,并提出了相应的过程流参考模型,目前,国外的主流PLM相关产品的工作流模型均支持此标准。

    在对象的全生命周期管理与工作流程的集成方面,国外PDM系统主要关注在面向对象的工作流应用方面,如Windchill和TeamCenter的变更管理。

    在国内,结合数据建模技术,各PDM厂商对面向对象的过程建模技术进行了一些研究,并对已有的PDM系统进行了改进,取得到了很好的应用效果。国内PDM关于过程建模方面的一些现状和特点如下:

    *过程模型的框架差异较大,采用的技术差别也较大;
    *过程模型提供的建模能力差异很大,现有的过程建模工具不足以支持用户复杂的业务过程;
    * 过程模型和对象模型集成不够理想;
    * 支持跨企业、跨地域的分布式应用的过程建模技术与国外相比有较大的差距;
    * 对支持符合业务需要的业务模型的研究不足;
    * 部分厂商提供了对WFMC的支持,但并不普遍,对相关国际标准的支持不够。

    2.3 功能模块

    系统功能模块实际上就是由调用核心基础服务的一组程序(界面)组成并能够完成一定应用功能的应用组件。国外PDM软件都在不断丰富自己的功能模块,根据 AMR Research的研究报告,国外PDM软件支持新产品的定义和投放市场的工程,市场,操作和原料等63种功能需求。应该说,国外各个PDM系统的功能模块已基本覆盖从产品概念设计到应用集成的各个业务层面。

    国外各个PDM系统之间的功能模块基本相同,但是存在一定差异。如TeamCenter提供了产品配置管理、生命周期管理等具体的模块,部分基础功能满足了制造业的基本需求,实施时利用现有模块和基础功能比较方便,在应用于制造业时有一定优势,而e-Matrix提供了较基础的系统构建框架,更加开放,但应用时配置工作量更大。
   
    相比而言,国产PDM的功能模块虽然已经基本覆盖PDM的功能边界,但在业务解决方案和细节方面还有待完善,功能模块的层次划分也不够清晰。
 
    常见的PDM系统功能模块有:文档管理、生命周期管理、更改控制管理、产品结构管理、产品配置管理、零部件族管理、产品可视化管理、工作流管理、项目管理、应用集成接口以及Web服务等。下面仅就项目管理和应用集成方面比较国产PDM与国外PDM之间的差异。

    2.3.1 项目管理

    从国外PDM产品的发展趋势来看,项目管理技术已从原来面向组织和功能的传统管理模式转变到面向项目的现代管理方式,通过项目将人员、流程以及各应用功能联结起来,从而构建面向业务的应用系统。如TeamCenter推出的TeamCenter Project项目协同模块,主要包括项目进度管理、项目资源管理、项目协同发布、项目跟踪与汇报、项目工作区安全等功能,支持将复杂的项目细化为明确的任务,并将这些任务分配给指定的资源,同时管理每个资源所承受的工作负荷;支持项目团队协作,促使产品价值链上项目团队的任务和调度同步,优化配置资源,降低开发成本。mySAP PLM提供的计划和项目管理模块,能够协助企业对产品开发流程进行规划、管理和控制,如控制项目的结构、日程计划、成本和资源等。Winchill解决方案中推出的项目协同管理Project Link,旨在将不同的产品开发以项目的形式进行管理,并通过Internet连接企业内外的供应链。

     国产PDM的项目管理普遍与文档签审流程紧密集成,而项目的资源管理、协同发布等项目管理的基本功能相对较弱,部分供应商通过与MS Project的简单集成实现项目的资源管理,但在工作协同方面则没有研究和应用。

    2.3.2 应用集成

    作为比较成熟的PDM产品,国外PDM系统都提供开放的软件接口,也具有较强大的集成组件。相比而言,受各种条件的限制,国内PDM普遍未提供开放的软件接口,各个PDM软件虽然与某个应用软件的集成性比较优秀,但从整体而言,应用集成性还处于比较低的层次。

    应用集成包括与单元应用软件、系统管理软件等各种软件之间的集成,下面主要讨论PDM与CAD、CAPP和管理软件之间的集成。

    2.3.2.1 与CAD的集成

    PDM与CAD的集成主要是与主流3D软件的集成。由于国内制造企业的3DCAD的普及比较晚,因而国产PDM与3DCAD的应用集成研究相对较晚,与国外PDM软件相比有很大的距离,主要表现在数据集成、浏览/圈阅工具、协同可视化工具等方面。
   
    国外主流的PDM软件基本能够支持15种独立CAD软件包,基于较完善的对象模型,基本与3DCAD实现了无缝集成;而国内PDM软件则主要支持几种主流的3D软件,如UG/ProE/SolidEdge/SolidWorks等,在数据集成的深度方面也很有限,仅处于数据提取的水平。

    在浏览和圈阅方面,国外PDM基本采用两种策略。

    以UGS、Windchill为代表的供应商采取自身开发浏览器的策略,并发布统一的3D浏览/圈阅工具(或控件),如UGS的JT2GO;

    以MatrixOne、Smarteam、Agile为代表的供应商则采取集成专业浏览器实现与3DCAD的集成,浏览器主要是业界领先的Cimmetry公司的AutoVue浏览器。

    受各种条件限制,国产PDM在3DCAD的浏览/圈阅方面还处于摸索阶段,部分厂商提供了与AutoVue浏览器的集成。

    2.3.2.2 与CAPP的集成

    基于CAPP软件的工艺解决方案是中国企业的独特需求,一直以来,国外PDM软件并不重视PDM与CAPP的集成,相比而言,国产PDM始终致力于国内企业的信息化工作,重视PDM与CAPP的集成工作,对CAPP软件的支持比国外PDM做的好。
 
    近年来,随着国内企业对PDM与CAPP集成的需求越来越强烈,国外PDM供应商也逐渐开始对国产CAPP软件提供支持,如Windchill提供了与开目CAPP的集成。

    2.3.2.3 与ERP/CRM的集成

    经过多年积累,国外PDM软件与ERP/CRM的集成已经非常成熟,在提供集成接口的基础上,对国外主流ERP/CRM,如SAP R3、Oracle Manufacture还提供专用的集成组件,如Smarteam、TeamCenter、Windchill等。而国产PDM与ERP/CRM的集成水平相对较低,基本停留在中间交换文件的水平上,没有与ERP/CRM的专用集成接口。

    3 实施分析

    3.1 解决方案

    国外软件供应商普遍将PDM软件作为PLM完整解决方案的一个组成部分,处理整个产品生命周期中的主要业务过程。如TeamCenter的协同平台(Collaboration Foundtion)、Windchill的CPC方案、MatrixOne的智能协同业务(Intelligent C-Commerce)等。

    同时,针对不同的行业,各个公司专门提供了行业解决方案。如TeamCenter公司针对汽车行业,Windchill公司针对船舶行业;Smarteam针对电子行业都提出了专门的行业解决方案。

    受各种条件的限制,国内还没有一家软件供应商提出并发布真正意义上的PLM完整解决方案或产品开发信息化的完整解决方案,往往以PDM为主体简单集成相关管理软件,达到实现产品数据全生命周期管理的目的,相比而言,无论完整性还是应用性都存在欠缺。

    3.2 实施方法学

    国外PDM供应商非常重视软件的实施,在大量成功范例的基础上,总结了众多的成功经验,形成适合本公司特点的实施方法学。这些实施方法学的主要特点如下:

     * 具有完整的实施模型和阶段划分,能覆盖实施的全生命周期;
     * 把需求收集、分析阶段作为整个实施过程的重点,切实了解用户需求;
     * 在关键阶段设置质量检验点,可进行全面质量控制;
     * 建立在大量的成功范例基础上,包含业界经验与Know-How技术;
     * 注重与用户的交流,容易被用户接受;
     * 提供了大量的实施文档模板,有效地引导和规范了实施人员的工作。

     结合现代项目管理理论,经过不断的摸索和实践,国内PDM供应商已经逐渐摸索出一套适合中国国情、行之有效的实施方法,并在具体的项目实施中得到良好应用,如CAXA和武汉开目的分阶段实施理论。

     4 结束语

     从体系架构、信息模型、功能模块、解决方案、实施方法学等几个方面的比较可以看出,国产PDM软件与国外PDM软件相比还有一定距离,特别是体系结构和解决方案方面尤其如此。随着国内企业信息化工程的不断深入,国内PDM软件供应商的投入不断加大,相信在体系结构、信息模型、功能模块等基础研究获得突破,并在项目实践中得到验证并发展后,国产PDM软件的水平会有一个大的发展。

北京奥运会开幕式图片

八月 11th, 2008

Read the rest of this entry »

分布式文件系统

七月 29th, 2008

分布式文件系统(Distributed File System)是指文件系统管理的物理存储资源不一定直接连接在本地节点上,而是通过计算机网络与节点相连。

下面简单介绍分布式文件系统的历史与现状:

对象存储突破了高性能计算环境中存储系统的瓶颈,由此也引发了人们对分布式文件系统的关注。最初的分布式文件系统应用发生在20世纪70年代,之后逐渐扩展到各个领域。从早期的NFS到现在的StorageTank,分布式文件系统在体系结构、系统规模、性能、可扩展性、可用性等方面经历了较大的变化。

文件系统是操作系统的一个重要组成部分,通过对操作系统所管理的存储空间的抽象,向用户提供统一的、对象化的访问接口,屏蔽对物理设备的直接操作和资源管理。

根据计算环境和所提供功能的不同,文件系统可划分为四个层次,从低到高依次是:单处理器单用户的本地文件系统,如DOS的文件系统;多处理器单用户的本地文件系统,如OS/2的文件系统;多处理器多用户的文件系统,如Unix的本地文件系统;多处理器多用户的分布式文件系统。

本地文件系统(Local File System)是指文件系统管理的物理存储资源直接连接在本地节点上,处理器通过系统总线可以直接访问。分布式文件系统(Distributed File System)是指文件系统管理的物理存储资源不一定直接连接在本地节点上,而是通过计算机网络与节点相连。上述按照层次的分类中,高层次的文件系统都是以低层次的文件系统为基础,实现了更高级的功能。比如多处理器单用户的本地文件系统需要比单处理器单用户的本地文件系统多考虑并发控制(Concurrency Control),因为可能存在多个处理器同时访问文件系统的情况;多处理器多用户的文件系统需要比多处理器单用户的本地文件系统多考虑数据安全访问方面的设计,因为多个用户存在于同一个系统中,保证数据的授权访问是一个关键;多处理器多用户的分布式文件系统需要比多处理器多用户的文件系统多考虑分布式体系结构带来的诸多问题,比如同步访问、缓冲一致性等。

随着层次的提高,文件系统在设计和实现方面的难度也会成倍提高。但是,现在的分布式文件系统一般还是保持与最基本的本地文件系统几乎相同的访问接口和对象模型,这主要是为了向用户提供向后的兼容性,同时保持原来的简单对象模型和访问接口。但这并不说明文件系统设计和实现的难度没有增加。正是由于对用户透明地改变了结构,满足用户的需求,以掩盖分布式文件操作的复杂性,才大大增加了分布式文件系统的实现难度。

在计算机性能不断提升的同时,计算机部件的平均价格却在不断下降。用户可以用更低的成本,购买更好、更快、更稳定的设备。存储系统、文件系统面临的新挑战也随之而来:如何管理更多的设备,提供更好的性能,更加有效地降低管理成本等。各种新的存储技术和分布式文件技术层出不穷,以满足用户日益增长的需求。因此,有必要简要回顾分布式文件系统发展的历史,分析对比当前主流的分布式文件系统在体系结构、缓存一致性、安全等方面的长处和不足。

文件系统最初是用来管理本地磁盘,提供用户访问接口的。某些数据的集合叫做一个文件(File),并赋予每个文件一定的属性,以标识该数据集合的某些属性。文件按照树(Tree)结构层次进行管理和检索。最初的文件系统只能管理本地磁盘空间。主机之间的文件共享与传输则通过文件传输协议(FTP,File Transfer Protocol)实现。但FTP没有提供与本地文件系统一致的访问接口和对象模型。

随着计算机应用范围的扩展,通过文件访问接口在不同主机之间共享文件的需求日益增强。下面分为几个阶段介绍分布式文件系统的发展过程。

1

1980~1990年

早期的分布式文件系统一般以提供标准接口的远程文件访问为目的,更多地关注访问的性能和数据的可靠性。

早期的文件系统以NFS和AFS(Andrew File System)最具代表性,它们对以后的文件系统设计也具有十分重要的影响。

NFS从1985年出现至今,已经经历了四个版本的更新,被移植到了几乎所有主流的操作系统中,成为分布式文件系统事实上的标准。NFS利用Unix系统中的虚拟文件系统(Virtual File System,VFS)机制,将客户机对文件系统的请求,通过规范的文件访问协议和远程过程调用,转发到服务器端进行处理;服务器端在VFS之上,通过本地文件系统完成文件的处理,实现了全局的分布式文件系统。Sun公司公开了NFS的实施规范,互联网工程任务组(The Internet Engineering Task Force,IETF)将其列为征求意见稿(RFC-Request for Comments),这很大程度上促使NFS的很多设计实现方法成为标准,也促进了NFS的流行。NFS不断发展,在第四版中提供了基于租赁(Lease)的同步锁和基于会话(Session)语义的一致性等。

Carnegie Mellon大学在1983年设计开发的AFS将分布式文件系统的可扩展性放在了设计和实现的首要位置,并且着重考虑了在不安全的网络中实现安全访问的需求。因此,它在位置透明、用户迁移、与已有系统的兼容性等方面进行了特别设计。AFS具有很好的扩展性,能够很容易地支持数百个节点,甚至数千个节点的分布式环境。同时,在大规模的分布式文件系统中,AFS利用本地存储作为分布式文件的缓存,在远程文件无法访问时,依然可以部分工作,提高了系统可用性。后来的Coda File System、Inter-mezzo File System都受到AFS的影响,更加注重文件系统的高可用性(High Availability)和安全性,特别是Coda,在支持移动计算方面做了很多的研究工作。

Sprite File System也是早期比较有特色的分布式文件系统。它是Sprite Network Operation System的组成部分,为分布式计算环境提供全局文件访问。与NFS相比,Sprite File System在服务器端和客户端都设置缓存,大大提高了系统性能。它通过简单的读写锁保证整个系统的缓存一致性,并且通过和虚拟存储部分交互,尽量多地缓存数据。

早期的分布式文件系统一般以提供标准接口的远程文件访问为目的,在受网络环境、本地磁盘、处理器速度等方面限制的情况下,更多地关注访问的性能和数据的可靠性。AFS在系统结构方面进行了有意义的探索。它们所采用的协议和相关技术,为后来的分布式文件系统设计提供了很多借鉴。

PanFS的系统结构图

2
1990~1995年

20世纪90年代初,面对广域网和大容量存储需求,加利福尼亚大学设计开发的xFS借鉴了当时先进的高性能对称多处理器的设计思想。

20世纪90年代初,面对广域网和大容量存储应用的需求,借鉴当时先进的高性能对称多处理器的设计思想,加利福尼亚大学设计开发的xFS,克服了以前的分布式文件系统一般都运行在局域网(LAN)上的弱点,很好地解决了在广域网上进行缓存,以减少网络流量的难题。它所采用的多层次结构很好地利用了文件系统的局部访问的特性,无效写回(Invalidation-based Write Back)缓存一致性协议,减少了网络负载。对本地主机和本地存储空间的有效利用,使它具有较好的性能。

Tiger Shark并行文件系统是针对大规模实时多媒体应用设计的。它采用了多种技术策略保证多媒体传输的实时性和稳定性:采用资源预留和优化的调度手段,保证数据实时访问性能;通过加大文件系统数据块的大小,最大限度地发挥磁盘的传输效率;通过将大文件分片存储在多个存储设备中,取得尽量大的并行吞吐率;通过复制文件系统元数据和文件数据,克服单点故障,提高系统可用性。

基于虚拟共享磁盘Petal的Frangipani分布式文件系统,采用了一种新颖的系统结构—分层次的存储系统。Petal提供一个可以全局统一访问的磁盘空间。Frangipani基于Petal的特性提供文件系统的服务。这种分层结构使两者的设计实现都得到了简化。在Frangipani中,每个客户端也是文件系统服务器,参与文件系统的管理,可以平等地访问Petal提供的虚拟磁盘系统,并通过分布式锁实现同步访问控制。分层结构使系统具有很好的扩展性,可以在线动态地添加存储设备,增加新用户、备份等,同时系统具有很好的机制来处理节点失效、网络失效等故障,提高了系统的可用性。

Slice File System(SFS)考虑标准的NFS在容量、性能方面存在的限制,采用在客户机和服务器之间架设一个μproxy中间转发器,以提高性能和可扩展性。它将客户端的访问分为小文件、元数据服务、大文件数据三类请求。通过μproxy将前两种请求转发到不同的文件服务器上,将后者直接发送到存储服务器上。这样SFS系统就可以支持多个存储服务器,提高整个系统的容量和性能。μproxy根据请求内容的转发是静态的,对于整个系统中负载的变化难以做出及时反应。

3

1995~2000年

网络技术的发展和普及应用极大地推动了网络存储技术的发展,基于光纤通道的SAN、NAS得到了广泛应用。这也推动了分布式文件系统的研究。

Cluster FS的Lustre系统结构图

在这个阶段,计算机技术和网络技术有了突飞猛进的发展,单位存储的成本大幅降低。而数据总线带宽、磁盘速度的增长无法满足应用对数据带宽的需求,存储子系统成为计算机系统发展的瓶颈。

网络技术的发展和普及应用极大地推动了网络存储技术的发展,基于光纤通道的SAN、NAS得到了广泛应用。这也推动了分布式文件系统的研究。这个阶段,出现了多种体系结构,充分利用了网络技术。

Global File System(GFS)吸取了对称多处理器(SMP)系统设计和实现的原理,将系统中的每一个客户机类比于SMP中的一个处理器。客户机间没有任何区别,可以平等地访问系统中的所有存储设备,就像处理器可以机会均等地访问主存一样。这样的设计可以更好地利用系统中的资源,消除单个服务器带来的性能瓶颈和单点失效问题。客户端之间无需通信,因此可以很好地消除客户机失效带来的威胁。GFS采用特殊设计的DLOCK锁机制,同步多个客户机对同一设备的访问,具有很高的效率。

General Parallel File System(GPFS)是从Tiger Shark发展过来的,是目前应用范围较广的一个系统。GPFS在系统设计中采用了多项先进技术。它是一个共享磁盘(Shared-disk)的分布式并行文件系统,客户端采用基于光纤通道或者iSCSI与存储设备相连,也可以通过通用网络相连。GPFS的磁盘数据结构可以支持大容量的文件系统和大文件,通过采用分片存储、较大的文件系统块、数据预读等方法获得了较高的数据吞吐率;采用扩展哈希(Extensible Hashing)技术支持含有大量文件和子目录的大目录,提高文件的查找和检索效率。GPFS采用分布式锁解决系统中的并发访问和数据同步问题:字节范围的锁用于用户数据的同步,动态选择元数据节点(Metanode)进行元数据的集中管理;分布式锁管理整个系统的空间分配等。GPFS采用日志技术对系统进行在线灾难恢复。每个节点都有各自独立的日志,且单个节点失效时,系统中的其他节点可以代替失效节点检查文件系统日志,进行元数据恢复操作。

惠普的DiFFS和SGI公司的CXFS都是基于SAN的分布式文件系统。DiFFS通过将存储系统划分成不同的区域,把对资源的共享访问冲突限制在各个区域内部,以解决机群文件系统的可扩展性问题。DiFFS采用了动态分配策略和文件级的负载平衡等多项技术。CXFS是在XFS的基础上开发的,实现了元数据服务器内置的失效接替和恢复功能;采用快速元数据算法,提高元数据的访问性能。

此外,还有多种体系结构,如EMC的HighRoad、Sun的qFS、XNFS等。数据容量、性能和共享的需求使得这一时期的分布式文件系统管理的系统规模更大、系统更复杂,对物理设备的直接访问、磁盘布局和检索效率的优化、元数据的集中管理等都反映了对性能和容量的追求。规模的扩展使得系统的动态性,如在线增减设备、缓存的一致性、系统可靠性的需求逐渐增强,更多的先进技术应用到系统实现中,如分布式锁、缓存管理技术、SoftUpdates技术、文件级的负载平衡等。
4

2000年以后

随着SAN和NAS两种结构逐渐成熟,研究人员开始考虑如何将两种结构结合起来。网格的研究成果等也推动了分布式文件系统体系结构的发展。

随着SAN和NAS两种体系结构逐渐成熟,研究人员开始考虑如何将两种体系结构结合起来,以充分利用两者的优势。另一方面,基于多种分布式文件系统的研究成果,人们对体系结构的认识不断深入,网格的研究成果等也推动了分布式文件系统体系结构的发展。这一时期,IBM的StorageTank、Cluster的Lustre、Panasas的PanFS、蓝鲸文件系统(BWFS)等是这种体系结构的代表。各种应用对存储系统提出了更多的需求:

大容量—现在的数据量比以前任何时期更多,生成的速度更快;

高性能—数据访问需要更高的带宽;

高可用性—不仅要保证数据的高可用性,还要保证服务的高可用性;

可扩展性—应用在不断变化,系统规模也在不断变化,这就要求系统提供很好的扩展性,并在容量、性能、管理等方面都能适应应用的变化;

可管理性—随着数据量的飞速增长,存储的规模越来越庞大,存储系统本身也越来越复杂,这给系统的管理、运行带来了很高的维护成本;

按需服务—能够按照应用需求的不同提供不同的服务,如不同的应用、不同的客户端环境、不同的性能等。

IBM公司在GPFS的基础上发展进化来的Storage Tank,以及基于Storage Tank的TotalStorage SAN File System,又将分布式文件系统的设计理念和系统架构向前推进了一步。它们除了具有一般的分布式文件系统的特性之外,还采用SAN作为整个文件系统的数据存储和传输路径。它们采用带外(out-of-band)结构,将文件系统元数据在高速以太网上传输,由专门的元数据服务器来处理和存储。文件系统元数据和文件数据的分离管理和存储,可以更好地利用各自存储设备和传输网络的特性,提高系统的性能,有效降低系统的成本。在TotalStorage中,块虚拟层将整个SAN的存储进行统一的虚拟管理,为文件系统提供统一的存储空间。SAN File System采用了基于策略的文件数据位置选择方法,能有效地利用系统的资源,提高性能,降低成本。

处于这个阶段的系统都在研究中,但从中也可以看出一些发展趋势:体系结构的研究逐渐成熟,表现在不同文件系统的体系结构趋于一致;系统设计的策略基本一致,如采用专用服务器方式等;每个系统在设计的细节上各自采用了很多特有的先进技术,也都取得了很好的性能和扩展性。另外,在协议方面的探索也是研究的热点之一,如Direct Access File System利用了远程内存直接访问的特性,借鉴了NFS第四版本和Common Internet File System等协议,设计了一套新的网络文件访问协议。

摄影基础知识:拍人像如何摆POSE?

七月 28th, 2008

 

 

 

 

 

 

 

    再提供一组线条姿势:

 

 

 

 

 
 

 
 

SEAL加密算法源代码(C)

六月 27th, 2008
/* seal.c - SEAL encryption algorithm */

#undef SEAL_DEBUG

#define ALG_OK 0
#define ALG_NOTOK 1

struct SealKey {
unsigned long t[520]; /* 512 rounded up to a multiple of 5 + 5*/
unsigned long s[265]; /* 256 rounded up to a multiple of 5 + 5*/
unsigned long r[20]; /* 16 rounded up to multiple of 5 */
};

#define ROT2(x) (((x) >> 2) | ((x) << 30))
#define ROT9(x) (((x) >> 9) | ((x) << 23))
#define ROT8(x) (((x) >> 8) | ((x) << 24))
#define ROT16(x) (((x) >> 16) | ((x) << 16))
#define ROT24(x) (((x) >> 24) | ((x) << 8))
#define ROT27(x) (((x) >> 27) | ((x) << 5))

#define WORD(cp) ((cp[0] << 24)|(cp[1] << 16)|(cp[2] << 8)|(cp[3]))

#define F1(x, y, z) (((x) & (y)) | ((~(x)) & (z)))
#define F2(x, y, z) ((x)^(y)^(z))
#define F3(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z)))
#define F4(x, y, z) ((x)^(y)^(z))

int g(in, i, h)
unsigned char *in;
int i;
unsigned long *h;
{
unsigned long h0;
unsigned long h1;
unsigned long h2;
unsigned long h3;
unsigned long h4;
unsigned long a;
unsigned long b;
unsigned long c;
unsigned long d;
unsigned long e;
unsigned char *kp;
unsigned long w[80];
unsigned long temp;

kp = in;
h0 = WORD(kp); kp += 4;
h1 = WORD(kp); kp += 4;
h2 = WORD(kp); kp += 4;
h3 = WORD(kp); kp += 4;
h4 = WORD(kp); kp += 4;

w[0] = i;
for (i=1;i<16;i++)
w[i] = 0;
for (i=16;i<80;i++)
w[i] = w[i-3]^w[i-8]^w[i-14]^w[i-16];

a = h0;
b = h1;
c = h2;
d = h3;
e = h4;

for (i=0;i<20;i++)
{
temp = ROT27(a) + F1(b, c, d) + e + w[i] + 0x5a827999;
e = d;
d = c;
c = ROT2(b);
b = a;
a = temp;
}
for (i=20;i<40;i++)
{
temp = ROT27(a) + F2(b, c, d) + e + w[i] + 0x6ed9eba1;
e = d;
d = c;
c = ROT2(b);
b = a;
a = temp;
}
for (i=40;i<60;i++)
{
temp = ROT27(a) + F3(b, c, d) + e + w[i] + 0x8f1bbcdc;
e = d;
d = c;
c = ROT2(b);
b = a;
a = temp;
}
for (i=60;i<80;i++)
{
temp = ROT27(a) + F4(b, c, d) + e + w[i] + 0xca62c1d6;
e = d;
d = c;
c = ROT2(b);
b = a;
a = temp;
}
h[0] = h0+a;
h[1] = h1+b;
h[2] = h2+c;
h[3] = h3+d;
h[4] = h4+e;

return (ALG_OK);
}

unsigned long gamma(a, i)
unsigned char *a;
int i;
{
unsigned long h[5];

(void) g(a, i/5, h);
return h[i % 5];
}

int seal_init(key, ks)
unsigned char *key;
struct SealKey **ks;
{
struct SealKey *result;
int i;
unsigned long h[5];

result = (struct SealKey *) calloc(1, sizeof(*result));
if (result == (struct SealKey *) 0)
return (ALG_NOTOK);

for (i=0;i<510;i+=5)
g(key, i/5, &(result->t[i]));
/* horrible special case for the end */
g(key, 510/5, h);
for (i=510;i<512;i++)
result->t[i] = h[i-510];
/* 0x1000 mod 5 is +1, so have horrible special case for the start */
g(key, (-1+0x1000)/5, h);
for (i=0;i<4;i++)
result->s[i] = h[i+1];
for (i=4;i<254;i+=5)
g(key, (i+0x1000)/5, &(result->s[i]));
/* horrible special case for the end */
g(key, (254+0x1000)/5, h);
for (i=254;i<256;i++)
result->s[i] = h[i-254];
/* 0x2000 mod 5 is +2, so have horrible special case at the start */
g(key, (-2+0x2000)/5, h);
for (i=0;i<3;i++)
result->r[i] = h[i+2];
for (i=3;i<13;i+=5)
g(key, (i+0x2000)/5, &(result->r[i]));
/* horrible special case for the end */
g(key, (13+0x2000)/5, h);
for (i=13;i<16;i++)
result->r[i] = h[i-13];
#ifdef SEAL_DEBUG
printf("T:\n");
for (i=0;i<8;i++)
printf("%08x ", result->t[i]);
printf("\n");
for (i=0;i<8;i++)
printf("%08x ", result->t[i+8]);
printf("\n");
printf("...\n");
for (i=0;i<8;i++)
printf("%08x ", result->t[i+504]);
printf("\n");
printf("S:\n");
for (i=0;i<8;i++)
printf("%08x ", result->s[i]);
printf("\n");
for (i=0;i<8;i++)
printf("%08x ", result->s[i+8]);
printf("\n");
printf("...\n");
for (i=0;i<8;i++)
printf("%08x ", result->s[i+248]);
printf("\n");
printf("R:\n");
for (i=0;i<8;i++)
printf("%08x ", result->r[i]);
printf("\n");
for (i=0;i<8;i++)
printf("%08x ", result->r[i+8]);
printf("\n");
#endif /* SEAL_DEBUG */
*ks = result;
return (ALG_OK);
}

int seal(key, in, out)
struct SealKey *key;
unsigned long in;
unsigned long *out;
{
int i;
int j;
int l;
unsigned long a;
unsigned long b;
unsigned long c;
unsigned long d;
unsigned short p;
unsigned short q;
unsigned long n1;
unsigned long n2;
unsigned long n3;
unsigned long n4;
unsigned long *wp;

wp = out;

for (l=0;l<4;l++)
{
a = in ^ key->r[4*l];
b = ROT8(in) ^ key->r[4*l+1];
c = ROT16(in) ^ key->r[4*l+2];
d = ROT24(in) ^ key->r[4*l+3];

for (j=0;j<2;j++)
{
p = a & 0x7fc;
b += key->t[p/4];
a = ROT9(a);

p = b & 0x7fc;
c += key->t[p/4];
b = ROT9(b);

p = c & 0x7fc;
d += key->t[p/4];
c = ROT9(c);

p = d & 0x7fc;
a += key->t[p/4];
d = ROT9(d);

}
n1 = d;
n2 = b;
n3 = a;
n4 = c;

p = a & 0x7fc;
b += key->t[p/4];
a = ROT9(a);

p = b & 0x7fc;
c += key->t[p/4];
b = ROT9(b);

p = c & 0x7fc;
d += key->t[p/4];
c = ROT9(c);

p = d & 0x7fc;
a += key->t[p/4];
d = ROT9(d);

for (i=0;i<64;i++)
{
p = a & 0x7fc;
b += key->t[p/4];
a = ROT9(a);
b ^= a;

q = b & 0x7fc;
c ^= key->t[q/4];
b = ROT9(b);
c += b;

p = (p+c) & 0x7fc;
d += key->t[p/4];
c = ROT9(c);
d ^= c;

q = (q+d) & 0x7fc;
a ^= key->t[q/4];
d = ROT9(d);
a += d;

p = (p+a) & 0x7fc;
b ^= key->t[p/4];
a = ROT9(a);

q = (q+b) & 0x7fc;
c += key->t[q/4];
b = ROT9(b);

p = (p+c) & 0x7fc;
d ^= key->t[p/4];
c = ROT9(c);

q = (q+d) & 0x7fc;
a += key->t[q/4];
d = ROT9(d);

*wp = b + key->s[4*i];
wp++;
*wp = c ^ key->s[4*i+1];
wp++;
*wp = d + key->s[4*i+2];
wp++;
*wp = a ^ key->s[4*i+3];
wp++;

if (i & 1)
{
a += n3;
c += n4;
}
else
{
a += n1;
c += n2;
}
}
}
return (ALG_OK);
}
/* end of file */

列出本机安装的所有硬件设备

六月 20th, 2008

以下代码可以把你机器上已安装的硬件设备全部列出来。

#include 
#include 
#include 
#include 
#include 
#pragma comment(lib, "Setupapi.lib")

int main( int argc, char *argv[ ], char *envp[ ] )
{
    HDEVINFO hDevInfo;
    SP_DEVINFO_DATA DeviceInfoData;
    DWORD i;

    // Create a HDEVINFO with all present devices.
    hDevInfo = SetupDiGetClassDevs(NULL,
        0, // Enumerator
        0,
        DIGCF_PRESENT | DIGCF_ALLCLASSES );

    if (hDevInfo == INVALID_HANDLE_VALUE)
    {
        // Insert error handling here.
        return 1;
    }

    // Enumerate through all devices in Set.

    DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
    for (i=0;SetupDiEnumDeviceInfo(hDevInfo,i,
        &DeviceInfoData);i++)
    {
        DWORD DataT;
        LPTSTR buffer = NULL;
        DWORD buffersize = 0;

        //
        // Call function with null to begin with,
        // then use the returned buffer size
        // to Alloc the buffer. Keep calling until
        // success or an unknown failure.
        //
        while (!SetupDiGetDeviceRegistryProperty(
            hDevInfo,
            &DeviceInfoData,
            SPDRP_DEVICEDESC,
            &DataT,
            (PBYTE)buffer,
            buffersize,
            &buffersize))
        {
            if (GetLastError() ==
                ERROR_INSUFFICIENT_BUFFER)
            {
                // Change the buffer size.
                if (buffer)
     LocalFree(buffer);
                buffer = (char *)LocalAlloc(LPTR,buffersize);
            }
            else
            {
                // Insert error handling here.
                break;
            }
        }

        printf("Result:[%s]\n",buffer);

        if (buffer) LocalFree(buffer);
    }

    if ( GetLastError()!=NO_ERROR &&
         GetLastError()!=ERROR_NO_MORE_ITEMS )
    {
        // Insert error handling here.
        return 1;
    }

    //  Cleanup
    SetupDiDestroyDeviceInfoList(hDevInfo);

    return 0;
}

彻底弄明白IRP

六月 20th, 2008

微软的东东,多数是把聪明人搞晕,把晕的人搞残,把残的人搞死!
    一直以来,想弄明白驱动和IRP到底是怎么回事,在驱网上也查了无数的资料,到头来,手脚抽筋也没弄明白。

    从网上查到一段说明,可以说是迄今最后的解释,在家里看了N天,似同天书。

引用:

1.    子系统调用NT的IO系统服务打开命名文件。

2.    NT的IO管理器调用对象管理器,查询命名文件,并且帮助解决文件对象的符号连接。同时调用安全参照监视器,检查子系统是否具备打开文件句柄的正确权限。

3.    如果NT文件系统不认识文件对象,IO管理器挂起请求。调用多个文件系统直到识别出文件对象才继续请求。

4.    IO管理器负责为打开的请求分配内存和初始化IRP。对于NT驱动,打开请求等同于创建请求。

5.    IO管理器调用文件系统驱动,将IRP传递给它们。文件系统存取它们的IRP中本地IO栈,决定必须进行哪一种操作。检查参数,确定请求文件是否在缓存中。如果不是,设置下一个IRP中驱动的IO栈。

6.    无论是驱动处理IRP还是完成IO请求操作。都调用IO管理器和其它NT元素提供的核心态例程。

7.    驱动设置返回给IO管理器的IRP中的IO状态块表示请求操作是成功还是失败。

8.    IO管理器通过获取IRP中的IO状态,将信息同过保护子系统返回给原始调用者。

9.    IO管理器释放已完成的IRP。

10.    如果打开操作成功,IO管理器返回文件句柄给子系统。反之返回错误状态。

晕晕乎乎中,似乎有所顿悟,何不用一个具体的类比,也解释一下IRP到底是怎么回事,所以喝了二两二锅头,斗胆把上面的10段话作如下的翻译,有翻译不妥当之处,望大大们指正:

引用:

1.    客人(IRP)来到大厦(驱动程序)外,该大厦有一个按人名造册的接待系统(IO管理器)。

2.    客人(IRP)先到大厅,查花名册,按人名(设备对象DeviceObject,符号连接SymbolicLink)查。同时查安全薄,看这个人是不是恐怖分子、台独分子(STATUS_INVALID_DEVICE_REQUEST, SECURITY_CLIENT_CONTEXT),不是则被允许进入。如是,则不允许则禁入。

3.    如果查不到客人名,则拒绝进入。调用多本花名册(文件系统,FSD),直到识别出来人,才继续。

4.    接待系统(IO管理器)为客人(IRP)分配房间、会议室,餐饮,一应俱全(RtlZeroMemory ,RtlCopyMemory),并打扫一遍(初始化, KeSetEvent, KeWaitForSingleObject, IoMarkIrpPending, InitializeObjectAttributes)。

5.    接待系统(IO管理器)调用管理人员,将客人的信息传递给下面各部门。各部门管理人员根据他们的子系统情况,分配资源队列(本地IO栈),并决定必须进行哪一种操作(IoGetCurrentIrpStackLocation, Irp->AssociatedIrp.SystemBuffer)。客人是否在不同级别的客人清单中。如果没有这个级别,则新建一个级别 (DeviceObject->DeviceExtension),并进行登记。比如是国宾级,则一定要准备一个总统套间。

6.    不论是对于客人(Irp),还是客人的级别档次要求(IO栈),都使用模块化的规范流程。

7.    向大厦总经理报告人员进入、级别招待的请求是成功还是失败。

8.    获取人员、招待的状态,将是不是超过招待能力的信息(过保护状态)返回给新来的客人。如果超出接待能力,则挂牌“客满,恕不接待”。

9.    人员离开,则释放客人占有的所有资源(分配房间、会议室等)。IoCompleteRequest, DriverObject->DriverUnload,IoDeleteDevice, ZwClose

10.    招待成功,返回OK或返回错误(Irp->IoStatus.Status = STATUS_SUCCESS,return ntStatus)

深入了解文件系统Cache管理器

六月 20th, 2008

译者序

本文仅用于学习交流,不负版权责任。翻译者楚狂人,有问题可与我联系,qq16191935。MSN walled_river@hotmail.com.

我以前不大明白Windows的文件系统和缓冲管理器之间的关系。仅仅知道Cc开头的系列调用是缓冲管理器提供的,文件系统中可以调用。也知道缓冲对于文件系统的意义,在于缓冲读写操作,使磁盘得到高效的利用。

为了翻译了这些资料,我也大致明白了文件系统和缓冲管理器之间的互动。windows的文件系统和缓冲管理器之间是相互调用的关系,双向沟通。总的来说,是缓冲管理器提供一些调用给文件系统,文件系统注册一些回调函数给缓冲管理器,而且缓冲管理器会发IRP给文件系统来处理。

首先文件系统如果想要实现缓冲读写,那么肯定要调用缓冲管理器提供的调用。(如对于缓冲读,不直接自己读磁盘,而是调用CcCopyRead),但是缓冲管理器自然还是要通过文件系统来实际读磁盘。因此缓冲管理器会发出IRP来给文件系统进行处理。这样,文件系统就要分别缓冲管理器发来的请求和用户进程发来的请求分别处理了。

把不同的请求放在同一个例程中处理,这正是设计上最令人恼火的地方。不过理解了这一点之后,你也就大致明白缓冲管理器和文件系统是如何交互的了。

NT 文件系统缓冲管理器

本文中,我们的同缓冲管理器的运行例程的一个基础描述.此外,给出了一些例程的使用例子.也参考了MS IFS 中能找到的代码.

缓冲管理器概述

缓冲管理器是一个纯软件组件.和Windows NT的内存管理器紧密的结合为一个整体.同时使文件系统数据的缓冲和虚拟内存管理系统也结合在一起.一些操作系统单独实现他们的文件系统,所以他们有不同的数据缓冲方式.但由于物理内存的有限性,这样的缓冲一样必须合理的管理.而且内存被这样的缓冲用了,就不能在系统中再做其他的用途了.

所以Window NT 缓冲管理器的一个关键优点,就是允许我们在文件系统缓冲和程序运行使用物理内存之间保持一个平衡.当一个应用正在消耗内存,用来进行文件数据缓冲的内存可能被缩减到接近0.结果系统让物理内存得到了更好的使用,最终提供了更好的性能.

文件系统还有一个使用Cache管理器的关键原因.一个文件可能被标准的文件系统接口访问.比如读和写,也有可能通过内存管理器被做为内存映射文件访问.有时两种访问方式被用在同一个文件上.这是Cache管理器提供一个机制,建立这两种访问方式之间的桥梁,以确保数据的可靠性.

Cache管理器数据结构

文件系统和Cache管理器之间的接口是一种过程上的接口.所有的Cache管理器用到的数据结构,有必要通过一个文件联系起来.但是实际上这些内部接口对文件系统是透明的.在这里我们描述这些文件系统和Cahce管理器共享的关键的数据结构.

缓冲控制块(BCB,Buffer Control Block)

如果一个文件的一部分被映射到一个系统内存地址,Cahce管理器内部使用缓冲控制块来记录.有时文件系统进行某些临界操作的时候,需要在内存中锁定一些数据.所以这个结构必须暴露给文件系统使用:

缓冲控制块的大部分是部可见的.开头的一部分暴露给文件系统:

typedef struct _PUBLIC_BCB {
 CSHORT NodeTypeCode;
 CSHORT NodeByteSize;
 ULONG MappedLength;
 LARGE_INTEGER MappedFileOffset;
} PUBLIC_BCB,*PPUBLIC_BCB;

开始的两个域是Windows数据结构的标准域,说明数据结构的类型和长度.后边的两个域是文件系统感兴趣的,说明了这个文件被这个特殊的缓冲控制块所管理的内容范围.

文件大小信息

文件系统和内存管理器都维护文件大小的信息.当文件系统建立了一个文件的映射,它记录了文件的当前大小.以后的任何改动同样被提交给Cache管理器.

Cache管理器用三个值用来表示文件的大小:

typedef struct _CC_FILE_SIZES {
 LARGE_INTEGER AllocationSize;
 LARGE_INTEGER FileSize;
 LARGE_INTEGER ValidDataLength;
} CC_FILE_SIZES,*PCC_FILE_SIZES;

这些域的名字容易混淆.比如AllocationSize不是说给这个文件分配实际的物理空间,而是当前已经分配的空间中能容纳的数据总量.对一般的文件系统来说,上边两个数据当然应该是一样的.但是,对一个支持数据压缩或者扩展的文件系统而言,这个值表示当前控件中所能容纳的数据量(而不是空间大小).

AllocationSize被内存管理器用来得到section object.section object被用来觉得一个文件是如何映射到内存的,所以AllocationSize总是至少和文件一样大.缓冲管理器和内存管理起没有考虑当文件系统把AllocationSize设置得比file size小的情况.系统会因数据结构的混乱而崩溃.

FileSize表示了文件数据中最后一个有效的字节的位置,一般是文件结束标记.

缓冲管理器回调

文件系统和内存管理器之间的互动是通过一系列的回调实现的.这些回调函数被注册给Cache管理器之后被Cahce管理器用来确保数据结构在一个文件系统操作之前被\”锁定\”.

Windows NT假设资源如何被文件系统,Cahce管理器和内存管理器使用有一个严格的顺序.这个顺序确保死缩不会发生.反之则可能死锁.一般来说,文件系统首先得到资源.然后是Cache管理器,最后是内存管理器.

因此,这些回调被内存管理器用来维护它的层级:这些回调有:

typedef BOOLEAN (*PACQUIRE_FOR_LAZY_WRITE) ( IN PVOID Context, IN BOOLEAN Wait );
typedef VOID (*PRELEASE_FROM_LAZY_WRITE) ( IN PVOID Context );
typedef BOOLEAN (*PACQUIRE_FOR_READ_AHEAD) ( IN PVOID Context, IN BOOLEAN Wait, );
typedef VOID (*PRELEASE_FROM_READ_AHEAD ( IN PVOID Context ); 

typedef struct _CAHHE_MANAGER_CLALLBACKES {
 PACQUIRE_FOR_LAZY_WRITE AcquireForLazyWrite;
 PRELEASE_FROM_LAZY_WRITE ReleaseFromLazyWrite;
 PACQUIRE_FOR_READ_AHEAD AcquireForReadAhead;
 PRELEASE_FROM_READ_AHEAD ReleaseFromReadAhead; 

} CACHE_MANAGER_CALLBACKS,*PCACHE_MANAGER_CLALLBACKS;

注意这些回调分别被缓冲管理器的两个部分使用.首先是延迟写,负责把脏的(修改过的)缓冲数据写回文件系统.另一种是预读,预先的读取文件,给用户调用的读返回信息.

首先,在设计上很重要的是要理解你的文件系统应该避免那些情况,而哪些是不需要避免的.比如说,没有理由把用户应用的缓冲读操作和缓冲管理器的延迟写序列化.它们互无影响.但是,你需要避免用户调用的非缓冲写操作改变文件的长度和缓冲管理器延迟写冲突.

NT的文件系统使用两个资源结构来实现这个.所有这些资源可以被不同的操作系统组件来使用,通过使用共同的头,铁别是头中的Resource域和PageingIoResource域.缓冲管理器并不直接去获取这些资源,它通过调用文件系统中的回调函数来获取这些必要的资源.

请注意这些回调例程必须由你的文件系统提供,他们不是可选的.如果你不能提供,那么系统会崩溃.

微软的IFSKit中每个文件系统的例子都含有这些例程.比如延迟写的位置入下:

文件系统 文件名 例程
FAT resrcsup.c FatAcquireFcbForLazyWrite
CDFS resrcsup.c CdAcquireForCache
RDR2 rxce\\resrcsup.c RxAcquireFcbForLazyWrite

其他的例程基本上可以在同一个文件中找到.

下面的代码是一个回调例程的例子:

static BOOLEAN OwAcquireForLazyWrite(PVOID Conetext,BOOLEAN Wait)
{
POW_FCB fcb = (POW_FCB)Context;
BOOLEAN result;

// 打开文件上的锁
result = OwAcquireResourceExclusiveExp(&fcb->Resource,Wait);
if(!result) {
// 我们没有能获得资源
return result;
}

// 我们得到了资源,我们必须:
// (1)保存当前线程的线程id(用于释放)
// (2)设置顶级irp一个假的值.
// 无论如何,当前线程id在设置之前应该为0(必须是没有使用的)

OwAssert(!fcb->ResourceThread);
fcb->ResourceThread = OwGetCurrentResourceThread();
return (TRUE);
}

CcCanIWrite

因为一个应用程序可能修改内存中的数据的速度超过写磁盘到数据上的能力,导致虚拟内存系统可能被数据所\”饱和\”.这可能导致虚拟系统发生内存枯竭的情况.为了避免这点,文件系统必须和虚拟系统合作探测这种条件.缓冲管理器提供的一个关键操作之一就是CcCanIWrite,原型如下:

NTKERNELAPI BOOLEAN CcCanIWrite (
IN PFILE_OBJECT FileObject,
IN ULONG BytesToWrite,
IN BOOLEAN Wait,
IN BOOLEAN Retrying
);

如果返回了FALSE,那么文件系统必须延迟脏页面的写入来避免内存枯竭.典型的内存枯竭症状是一些调用返回NO_PAGES_AVAILABLE.

文件系统必须排列这些写操作的重试并发送.文件系统可以通过内部的发送机制来发送,也可以使用这个例程.

CcDeferWrite

你的文件系统可以调用FsRtlCopyWirte,这样就不必直接调缓冲管理器了.这样内部的延迟写实际上是用这个例程实现延迟写的.

CcCopyRead

只要一个文件系统建立了一个缓冲(通过CcInitializeCacheMap调用),它就可以使用FsRtl例程(比如FsRtlCopyRead)或者这个例程.一般FsRtlCopyRead用来实现Fast IO的读,而这个例程一般用来实现IRP_MJ_READ,原型如下:

NTKERNELAPI BOOLEAN CcCopyRead (
IN PFILE_OBJECT FileObject,
IN PLARGE_INTERGER FileOffset,
IN ULONG Length,
IN BOOLEAN Wait,
OUT PVOID Buffer,
OUT PIO_STATUS_BLOCK IoStatus
);

FileObject含有一个指向SectionObjectPointer的指针.当数据从缓冲拷贝到用户缓冲区间(也就是这里的Buffer所指的区域),SectionObjectPointer是被缓冲管理器所使用.当然这里假设缓冲已经实现初始化过了.

Wait说明调用这是否希望阻塞一段不定长的时间.比如可能要等待获取一个锁.这个参数应该被看成一个\”提示\”而不是一个\”确保\”.比如说,如果一个磁盘io有必要结束这个操作,这个操作可能继续运行.即使Wait的值是FALSE.

Buffer是调用者提供的缓冲.它不一定是一个有效的缓冲.失败的情况下,这个调用会抛出一个异常.一个文件系统驱动应该捕获这个异常并返回一个错误给应用程序.

IoStatus用来收集操作的完成状态,以及有多少个字节被成功的读取了.

请注意缓冲管理器的这个调用可能导致分页交换.这可能导致文件系统驱动处理实际的分页交换读写的操作的例程重入.

(译者注:假设文件系统中的读处理例程中调用了CcCopyRead从缓冲中读,这可能导致缓冲进行页面交换来读取硬盘.而读取硬盘的操作又是文件系统的读处理例程进行的,因此这个读处理例程是可能重入的.).

CcCopyWrite

当一个文件系统已经初始化了一个缓冲(使用CcInitializeCachedMap调用),它可以使用FsRtl例程(FsRtlCopyWrite)或者这个例程.一般的,FsRtlCopyWrite用来实现FastIo写.而这个例程用来实现普通的IRP_MJ_WRITE,原型如下:

NTKERNELAPI BOOLEAN CcCopyWrite (
IN PFILE_OBJECT FileObject,
IN PLARGE_INTEGER FileOffset,
IN ULONG Length,
IN BOOLEAN Wait,
IN PVOID Buffer
);

参数同上一函数.

请注意这个操作可能导致一个缓冲页面的部分内容被改写.这种情况下,这个页面的内容首先从磁盘上读出,然后被修改.因此,文件系统用来处理文件被修改导致的页面失败的例程有可能被重入.

CcDeferWrite

你的文件系统必须限制数据写入的最大流量.为了简化这个实现,缓冲管理器提供一个简单的机制来把这些写操作排队,直到虚拟内存系统能容纳它们.当CcCanIWrite调用返回失败的时候,这些由你的文件系统所注册的回调函数来完成.

这个回调函数的原型为:

Typedef VOID (*PCC_POST_DEFERRED_WRITE) (
IN PVOID Context1,
IN PVOID Context2
);

这些上下文指针由你的文件系统使用,座位建立延迟写过程的一部分.CcDeferWrite的原型如下:

NTKERNELAPI VOID CcDeferWrite (
IN PFILE_OBJECT FileObject,
IN PCC_POST_DEFERRED_WRITE PostRoutine,
IN PVOID Context1,
IN PVOID Context2,
IN ULONG BytesToWrite,
IN BOOLEAN Retrying);

FileObject标明要写的文件.

PostRoutine是文件系统驱动提供的回调函数,当虚拟内存系统已经发生了改变,附加的写操作可以被允许执行了,缓冲管理器会调用这个回调.

Context1和Context2指正是文件系统驱动所定义的.当写文件被允许的时候传给上面的回调函数.

BytesToWrite参数说明要写的字节数.虚拟内存系统用这个信息判断这个操作是否安全的(基于可用的页).

Retrying参数表示这是第一次尝试(Retrying是FALSE)还是一次延后的尝试(Retrying是TRUE).

CcGetDirtyPages

这个例程列出来仅仅为了完整性.这个用于文件系统利用windows nt的内部日志机制.文件系统中一般不使用.原型如下:

NTKERNELAPI LARGE_INTEGER CcGetDirtyPages (
IN PVOID LogHandle,
IN PDIRTY_PAGE_ROUTINE DirtyPageRoutine,
IN PVOID Context1,
IN PVOID Context2
);

CcGetFileObjectFromBcb

一个私有的缓存控制块包含一个FileObject的指针.虚拟内存系统用此来跟踪文件缓冲的信息.FileObject因此可以从一个BCB中获得.这是一个必要的调用.原型如下:

NTKERNELAPI PFILE_OBJECT CcGetFileObjectFromBcb (
IN PVOID Bcb
);

CcGetFileObjectFromSectionPtrs

一个文件建立缓冲时,缓冲管理器使用FileObject作为参数调用CcInitializeCacheMap来生成一个新的section object.这个对象用于缓冲文件数据.所以当缓冲管理器得到了这个文件的缓冲数据,原来的FileObject被虚拟内存系统用于各种不同的IO操作.

给定的任意一个FileObject的SectionObjectPointer,这个例程可以告诉文件系统实际被虚拟内存系统所使用的实际file object.原型如下:

NTKERNELAPI PFILE_OBJECT CcGetFileObjectFromSectionPtrs (
IN PSECTION_OBJECT_POINTERS SectionObjectPointer
);

文件过滤系统驱动开发Filemon学习笔记

六月 17th, 2008

WINDOWS文件过滤系统驱动开发,可用于硬盘还原,防病毒,文件安全防护,文件加密等诸多领域。而掌握核心层的理论及实践,对于成为一名优秀的开发人员不可或缺。

WINDOWS文件过滤系统驱动开发的两个经典例子,Filemon与SFilter,初学者在经过一定的理论积累后,对此两个例子代码的研究分析,会是步入驱动开发殿堂的重要一步,相信一定的理论积累以及贯穿剖析理解此两个例程后,就有能力开始进行文件过滤系统驱动开发的实际工作了。
对于SFilter例子的讲解,楚狂人的教程已经比较流行,而Filemon例子也许因框架结构相对明晰,易于剖析理解,无人贴出教程,本人在剖析Filemon的过程中积累的一些笔记资料,陆续贴出希望对初学者有所帮助,并通过和大家的交流而互相提高。

Filemon学习笔记 第一篇:

Filemon的大致架构为,在此驱动程序中,创建了两类设备对象。
一类设备对象用于和Filemon对应的exe程序通信,以接收用户输入信息,比如挂接或监控哪个分区,是否要挂接,是否要监控,监控何种操作等。此设备对象只创建了一个,在驱动程序的入口函数DriverEntry中。此类设备对象一般称为控制设备对象,并有名字,以方便应用层与其通信操作。
第二类设备对象用于挂接到所须监控的分区,比如c:,d:或e:,f:,以便拦截到引应用层对该分区所执行的读,写等操作。此类设备对象为安全起见,一般不予命名,可根据须监控多少分区而创建一个或多个。

驱动入口函数大致如下:

NTSTATUS
DriverEntry(
    IN PDRIVER_OBJECT DriverObject,
    IN PUNICODE_STRING RegistryPath
    )
{
    NTSTATUS                ntStatus;
    PDEVICE_OBJECT          guiDevice;
    WCHAR                   deviceNameBuffer[]  = L"\\Device\\Filemon";
    UNICODE_STRING          deviceNameUnicodeString;
    WCHAR                   deviceLinkBuffer[]  = L"\\DosDevices\\Filemon";
    UNICODE_STRING          deviceLinkUnicodeString;
    ULONG                   i;

    DbgPrint (("Filemon.SYS: entering DriverEntry\n"));
    FilemonDriver = DriverObject;

    //
    // Setup the device name
    //
    RtlInitUnicodeString (&deviceNameUnicodeString,
                          deviceNameBuffer );

    //
    // Create the device used for GUI communications
    //此设备对象用来和用户交互信息
    ntStatus = IoCreateDevice ( DriverObject,
                                sizeof(HOOK_EXTENSION),
                                &deviceNameUnicodeString,
                                FILE_DEVICE_FILEMON,
                                0,
                                TRUE,
                                &guiDevice );

    //
    // If successful, make a symbolic link that allows for the device
    // object's access from Win32 programs
    //
    if(NT_SUCCESS(ntStatus)) {

        //
        // Mark this as our GUI device
        //
        ((PHOOK_EXTENSION) guiDevice->DeviceExtension)->Type = GUIINTERFACE;

        //
        // Create a symbolic link that the GUI can specify to gain access
        // to this driver/device
        //
        RtlInitUnicodeString (&deviceLinkUnicodeString,
                              deviceLinkBuffer );
        ntStatus = IoCreateSymbolicLink (&deviceLinkUnicodeString,
                                         &deviceNameUnicodeString );
        if(!NT_SUCCESS(ntStatus)) {

            DbgPrint (("Filemon.SYS: IoCreateSymbolicLink failed\n"));
            IoDeleteDevice( guiDevice );
            return ntStatus;
        }

        //
        // Create dispatch points for all routines that must be handled.
        // All entry points are registered since we might filter a
        // file system that processes all of them.
        //
        for( i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++ ) {

            DriverObject->MajorFunction[i] = FilemonDispatch;
        }
#if DBG
        //
        // Driver unload is only set if we are debugging Filemon. This is
        // because unloading a filter is not really safe - threads could
        // be in our fastio routines (or about to enter them), for example,
        // and there is no way to tell. When debugging, we can risk the
        // occasional unload crash as a trade-off for not having to
        // reboot as often.
        //
        // DriverObject->DriverUnload = FilemonUnload;
#endif // DBG

        //
        // Set up the Fast I/O dispatch table
        //
        DriverObject->FastIoDispatch = &FastIOHook;

    } else {

        //
        // If something went wrong, cleanup the device object and don't load
        //
        DbgPrint(("Filemon: Failed to create our device!\n"));
        return ntStatus;
    }

    //
    // Initialize the name hash table
    //
    for(i = 0; i < NUMHASH; i++ ) HashTable[i] = NULL;

    //
    // Find the process name offset
    //
    ProcessNameOffset = FilemonGetProcessNameOffset();//为了得到当前进程名字

    //
    // Initialize the synchronization objects
    //
#if DBG
    KeInitializeSpinLock( &CountMutex );
#endif
    ExInitializeFastMutex( &LogMutex );
    ExInitializeResourceLite( &FilterResource );
    ExInitializeResourceLite( &HashResource );

    //
    // Initialize a lookaside for file names
    //
    ExInitializeNPagedLookasideList( &FullPathLookaside, NULL, NULL,
				     0, MAXPATHLEN, 'mliF', 256 );

    //
    // Allocate the first output buffer
    //
    CurrentLog = ExAllocatePool( NonPagedPool, sizeof(*CurrentLog) );
    if( !CurrentLog ) {

        //
        // Oops - we can't do anything without at least one buffer
        //
        IoDeleteSymbolicLink( &deviceLinkUnicodeString );
        IoDeleteDevice( guiDevice );
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // Set the buffer pointer to the start of the buffer just allocated
    //
    CurrentLog->Len  = 0;
    CurrentLog->Next = NULL;
    NumLog = 1;

    return STATUS_SUCCESS;
}

在此驱动入口点函数中,主要做了生成新的设备对象,此设备对象用来和应用层信息交互,比如应用层向驱动传递需要挂接或者监控的分区盘符,或者是否挂接盘符,是否监控操作等。
上面创建设备对象的代码为:

ntStatus = IoCreateDevice ( DriverObject,
                                sizeof(HOOK_EXTENSION),
                                &deviceNameUnicodeString,
                                FILE_DEVICE_FILEMON,
                                0,
                                TRUE,
                                &guiDevice );

    //
    // If successful, make a symbolic link that allows for the device
    // object's access from Win32 programs
    //
    if(NT_SUCCESS(ntStatus)) {

        //
        // Mark this as our GUI device
        //
        ((PHOOK_EXTENSION) guiDevice->DeviceExtension)->Type = GUIINTERFACE;

        //
        // Create a symbolic link that the GUI can specify to gain access
        // to this driver/device
        //
        RtlInitUnicodeString (&deviceLinkUnicodeString,
                              deviceLinkBuffer );
        ntStatus = IoCreateSymbolicLink (&deviceLinkUnicodeString,
                                         &deviceNameUnicodeString );
        if(!NT_SUCCESS(ntStatus)) {

            DbgPrint (("Filemon.SYS: IoCreateSymbolicLink failed\n"));
            IoDeleteDevice( guiDevice );
            return ntStatus;
        }

上面代码完成的功能为创建了用于与应用层交互的控制设备对象,名字在参数&deviceNameUnicodeString,中。设备对象创建成功后又调用IoCreateSymbolicLink创建了一个符号连接,以便于应用层交互。 在入口点函数DriverEntry代码中,还有一处代码: ProcessNameOffset = FilemonGetProcessNameOffset();//为了得到当前进程名字。此函数体如下:

ULONG
FilemonGetProcessNameOffset(
    VOID
    )
{
    PEPROCESS       curproc;
    int             i;

    curproc = PsGetCurrentProcess();//调用PsGetCurrentProcess取得KPEB基址

    //然后搜索KPEB,得到ProcessName相对KPEB的偏移量
    // Scan for 12KB, hoping the KPEB never grows that big!
    //
    for( i = 0; i < 3*PAGE_SIZE; i++ ) {

        if( !strncmp( SYSNAME, (PCHAR) curproc + i, strlen(SYSNAME) )) {

            return i;
        }
    }

    //
    // Name not found - oh, well
    //
    return 0;

这个函数通过查找KPEB (Kernel Process Environment Block),取得进程名,GetProcessNameOffset主要是调用PsGetCurrentProcess取得KPEB基址,然后搜索KPEB,得到ProcessName相对KPEB的偏移量,存放在全局变量ProcessNameOffset中,得到此偏移量的作用是:无论当前进程为哪个,其名字在KPEB中的偏移量不变,所以都可以通过此偏移量得到。而在入口点函数DriverEntry执行时,当前进程必为系统进程,所以在此函数中方便地根据系统进程名SYSNAME(#define SYSNAME “System”)得到此偏移量。

分发函数剖析:
在入口点函数中,通过代码:

for( i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++ ) {

            DriverObject->MajorFunction[i] = FilemonDispatch;
}

简单地把各个分发例程设置成了FilemonDispatch; 然后我们追踪其函数体:

NTSTATUS
FilemonDispatch(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
{
    //
    // Determine if its a request from the GUI to us, or one that is
    // directed at a file system driver that we've hooked
    //
    if( ((PHOOK_EXTENSION) DeviceObject->DeviceExtension)->Type == GUIINTERFACE ) {

        return FilemonDeviceRoutine( DeviceObject, Irp );

    } else {

        return FilemonHookRoutine( DeviceObject, Irp );
    }
}

函数体先判断需要处理IRP包的设备对象的类型,看是属于控制设备对象,还是属于用于挂接并监控文件读写操作的过滤设备对象。如果是属于后者 则进入:FilemonHookRoutine( DeviceObject, Irp )
此函数是拦截文件操作的中心,在其中获得了被操作的文件名字,并且根据操作类型,在
switch( currentIrpStack->MajorFunction ) {
}
中针对不同的MajorFunction,打印出相关操作信息。
因此函数体太长 不再全部列出。
其函数体总体框架为:得到被操作的文件名字,打印相关操作信息,然后下发IRP到底层驱动。
在下发IRP到底层驱动处理前,本层驱动必须负责设置下层IO堆栈的内容。这样下一层驱动调用IoGetCurrentIrpStackLocation()时能得到相应的数据。
设置下层IO堆栈的内容,一般用两个函数来实现:
IoCopyCurrentIrpStackLocationToNext( Irp )
此函数一般用在本驱动设置了完成例程时调用,把本层IO _STACK_LOCATION 中的参数copy到下层,但与完成例程相关的参数信息例外。因为本驱动设置的完成例程只对本层驱动有效。
IoSkipCurrentIrpStackLocationToNext(Irp)
此函数的作用是:直接把本层驱动IO堆栈的内容设置为下层驱动IO堆栈指针的指向。因两层驱动IO堆栈的内容完全一致,省却copy过程。

而在Filemon的处理中,它用了一个特别的办法,没有调用此两个函数,FilemonHookRoutine函数体里面有三句代码:

 

PIO_STACK_LOCATION  currentIrpStack = IoGetCurrentIrpStackLocation(Irp);
PIO_STACK_LOCATION  nextIrpStack    = IoGetNextIrpStackLocation(Irp);

*nextIrpStack = *currentIrpStack;//此步设置了下层驱动的IO_STACK_LOCATION
直接设置了下层驱动IO堆栈的值。

在FilemonHookRoutine函数里,用一个宏实现了复杂的获得拦截到的被操作文件的名字:

if( FilterOn && hookExt->Hooked ) {

        GETPATHNAME( createPath );
}

GETPATHNAME( createPath )宏展开为:

#define GETPATHNAME(_IsCreate)                                                  \
        fullPathName = ExAllocateFromNPagedLookasideList( &FullPathLookaside ); \
        if( fullPathName ) {                                                    \
            FilemonGetFullPath( _IsCreate, FileObject, hookExt, fullPathName ); \
        } else {                                                                \
            fullPathName = InsufficientResources;                               \
        }

在函数:FilemonGetFullPath( _IsCreate, FileObject, hookExt, fullPathName )中实现了获得被操作的文件名字,此函数代码较多,判断条件复杂,理解起来比较麻烦,下面重点讲解。
对函数FilemonGetFullPath的理解关键在于理顺结构,
此函数的功能就是获得文件名字,获得文件名字一般在三种状态下:
一:在打开文件请求中,但在打开文件前。
二:在打开文件请求中,但在打开文件后,通过在本层驱动中设置完成例程。在完成例程中获得。
三:在过滤到读写等操作时。
而在此函数中,它包含了第一种和第三种方法,通过一些烦琐的条件判断,先判断出目前是处于什么状态中,然后根据不同状态采取不同方法。

先分析当在第一种条件下,此函数的处理方法,可以精炼为如下:

VOID
FilemonGetFullPath(
    BOOLEAN createPath,
    PFILE_OBJECT fileObject,
    PHOOK_EXTENSION hookExt,
    PCHAR fullPathName
    )
{
	ULONG               pathLen, prefixLen, slashes;
    PCHAR               pathOffset, ptr;
    BOOLEAN             gotPath;
    PFILE_OBJECT        relatedFileObject;

    ANSI_STRING         fileName;
    ANSI_STRING         relatedName;

    UNICODE_STRING      fullUniName;

	prefixLen = 2; // "C:"

	if( !fileObject ) { 

        sprintf( fullPathName, "%C:", hookExt->LogicalDrive );
        return;
    }

    //
    // Initialize variables
    //
    fileName.Buffer = NULL;
    relatedName.Buffer = NULL;
    gotPath = FALSE;

	if( !fileObject->FileName.Buffer)
	{
		sprintf( fullPathName, "%C:", hookExt->LogicalDrive);
		return;
	}else
		DbgPrint("fileOjec->FileName:%s",fileObject->FileName);

	 if( !NT_SUCCESS( RtlUnicodeStringToAnsiString( &fileName, &fileObject->FileName, TRUE ))) {

            sprintf( fullPathName, "%C: <Out of Memory>", hookExt->LogicalDrive );
            return;
        }

        pathLen = fileName.Length + prefixLen;
        relatedFileObject = fileObject->RelatedFileObject;

		  //
        // Only look at related file object if this is a relative name
        //
        if( fileObject->FileName.Buffer[0] != L'\\' &&
            relatedFileObject && relatedFileObject->FileName.Length ) {
			DbgPrint("relatedFileObject filename : %s",relatedFileObject->FileName);

			if( !NT_SUCCESS( RtlUnicodeStringToAnsiString( &relatedName, &relatedFileObject->FileName, TRUE ))) {

                sprintf( fullPathName, "%C: <Out of Memory>", hookExt->LogicalDrive );
                RtlFreeAnsiString( &fileName );
                return;
            }
            pathLen += relatedName.Length+1;
        }

		if( fileObject->DeviceObject->DeviceType != FILE_DEVICE_NETWORK_FILE_SYSTEM ) {

            sprintf( fullPathName, "%C:", hookExt->LogicalDrive );
        }

		 if( pathLen >= MAXPATHLEN ) {

            strcat( fullPathName, " <Name Too Long>" );

        } else {

            //
            // Now we can build the path name
            //
            fullPathName[ pathLen ] = 0;

            pathOffset = fullPathName + pathLen - fileName.Length;
            memcpy( pathOffset, fileName.Buffer, fileName.Length + 1 );

            if( fileObject->FileName.Buffer[0] != L'\\' &&
                relatedFileObject && relatedFileObject->FileName.Length ) {

                //
                // Copy the component, adding a slash separator
                //
                *(pathOffset - 1) = '\\';
                pathOffset -= relatedName.Length + 1;

                memcpy( pathOffset, relatedName.Buffer, relatedName.Length );

                //
                // If we've got to slashes at the front zap one
                //
                if( pathLen > 3 && fullPathName[2] == '\\' && fullPathName[3] == '\\' )  {

                    strcpy( fullPathName + 2, fullPathName + 3 );
                }
            }
        }  

}

上面的精简后的函数代码为只考虑目前处于第一种情况,即打开文件请求中,但文件尚未打开时。
在此时,文件的名字信息存储在文件对象fileObject->FileName,与fileObject->RelatedFileObject->FileName, FileObject->FileName RelatedObject 的相对路径,通过对两者的解析组合出文件名字。

而在FilemonGetFullPath 函数体中的另一段代码:

FilemonGetFullPath
{
…………………..
…………………..
…………………..
if( !gotPath && !createPath ) {

        fileNameInfo = (PFILE_NAME_INFORMATION) ExAllocatePool( NonPagedPool,
                                                                MAXPATHLEN*sizeof(WCHAR) );

        if( fileNameInfo &&
            FilemonQueryFile(hookExt->FileSystem, fileObject, FileNameInformation,
                             fileNameInfo, (MAXPATHLEN - prefixLen - 1)*sizeof(WCHAR) )) {

            fullUniName.Length = (SHORT) fileNameInfo->FileNameLength;
            fullUniName.Buffer = fileNameInfo->FileName;
            if( NT_SUCCESS( RtlUnicodeStringToAnsiString( &fileName, &fullUniName, TRUE ))) { 

                fullPathName[ fileName.Length + prefixLen ] = 0;

                if( hookExt->Type == NPFS ) {

                    strcpy( fullPathName, NAMED_PIPE_PREFIX );

                } else if( hookExt->Type == MSFS ) {

                    strcpy( fullPathName, MAIL_SLOT_PREFIX );

                } else if( fileObject->DeviceObject->DeviceType != FILE_DEVICE_NETWORK_FILE_SYSTEM ) {

                    sprintf( fullPathName, "%C:", hookExt->LogicalDrive );

                } else {

                    //
                    // No prefix for network devices
                    //
                }

                memcpy( &fullPathName[prefixLen], fileName.Buffer, fileName.Length );
                gotPath = TRUE;
                RtlFreeAnsiString( &fileName );
                fileName.Buffer = NULL;
            }
        }
        if( fileNameInfo ) ExFreePool( fileNameInfo );
}
…………………
…………………
…………………
}

上面这段代码是处理另外一种情况,即是在其他读写请求中,自己根据拦截到的fileObject构建IRP,下发到底层,以此来查询文件名信息。整个过程还是易于理解的。

在理清这两种脉络后,再剖析此整个函数,就很容易理解整个函数代码了。
代码中对 MajorFunction == IRP_MJ_CREATE_NAMED_PIPE
MajorFunction == IRP_MJ_CREATE_MAILSLOT 的判断是为了辨别对拦截到的进程间的两种通信方式:命名管道与邮槽的处理。

周末来了,祝大家周末愉快。余下的整理后再帖,希望和大家多交流。

连城碧 QQ 276265852 MSN:haochao0000@hotmail.com

Int 13 中断功能

三月 12th, 2008

INT 13H,AH=00H 软、硬盘控制器复位

说明:
此功能复位磁盘(软盘和硬盘)控制器板和磁盘驱动器,它在磁盘控制器芯片上完成复位操场作并在磁盘进行所需的操作之前做一系列用于磁盘校准的磁盘操作。
当磁盘I/O功能调用出现错误时,需要调用此功能,此刻复位功能将使BIOS象该磁盘重新插入一样检查驱动器中磁盘状态,并将磁头校准使之在应该在的位置上。
此功能调用不影响软盘或硬盘上的数据。
入口参数:
AH=00H 指明调用复位磁盘功能。
DL 需要复位的驱动器号。
返回参数:
若产生错误,进位标志CF=1,错误码在AH寄存器。详情请见磁盘错误状态返回码一文。
示例:
C_SEG SEGMENT PUBLIC
ASSUME CS:C_SEG,DS:C_SEG
ORG 100H
START: MOV AH, 00H
MOV DL, 80H
INT 13H
;复位硬盘 C
JC ERROR
……
ERROR: ……
C_SEG ENDS
END START

INT 13H,AH=02H 读扇区说明:
调用此功能将从磁盘上把一个或更多的扇区内容读进存贮器。因为这是一个低级功能,在一个操作中读取的全部扇区必须在同一条磁道上(磁头号和磁道号相同)。BIOS不能自动地从一条磁道末尾切换到另一条磁道开始,因此用户必须
把跨多条磁道的读操作分为若干条单磁道读操作。
入口参数:
AH=02H 指明调用读扇区功能。
AL 置要读的扇区数目,不允许使用读磁道末端以外的数值,也不允许使该寄存器为0。
DL 需要进行读操作的驱动器号。
DH 所读磁盘的磁头号。
CH 磁道号的低8位数。
CL 低5位放入所读起始扇区号,位7-6表示磁道号的高2位。
ES:BX 读出数据的缓冲区地址。
返回参数:
如果CF=1,AX中存放出错状态。读出后的数据在ES:BX区域依次排列。详情请参见磁盘错误状态返回码一文。
示例:
C_SEG SEGMENT PUBLIC
ASSUME CS:C_SEG,DS:C_SEG
ORG 100H
START: JMP READ
BUFFER DB 512 DUP(0)
READ: PUSH CS
POP ES
MOV BX, OFFSET BUFFER
MOV AX, 0201H
MOV CX, 0001H
MOV DX, 0000H
INT 13H
;读软盘A, 0面0道1扇区
;读出后数据在BUFFER中
JC ERROR
……
ERROR: ……
C_SEG ENDS
END START

INT 13H,AH=03H 写扇区

说明:
调用此功能将从磁盘上把一个或更多的扇区内容写入驱动器。因为这是一个低级功能,在一个写入操作中的全部扇区必须在同一条磁道上(磁头号和磁道号相同)。BIOS不能自动地从一条磁道末尾切换到另一条磁道开始,因此用户必须把跨多条磁道的写操作分为若干条单磁道写操作。
入口参数:
AH=03H 指明调用写扇区功能。
AL 置要写的扇区数目,不允许使用超出磁道末端以外的数值,也不允许使该寄存器为0。
DL 需要进行写操作的驱动器号。
DH 所写磁盘的磁头号。
CH 磁道号的低8位数。
CL 低5位放入所读起始扇区号,位7-6表示磁道号的高2位。
ES:BX 放置写入数据的存贮区地址。
返回参数:
如果CF=1,AX中存放出错状态。详情请参见磁盘错误状态返回码一文。
示例:
C_SEG SEGMENT PUBLIC
ASSUME CS:C_SEG,DS:C_SEG
ORG 100H
START: JMP WRITE
BUFFER DB 512 DUP(0FFH)
WRITE: PUSH CS
POP ES
MOV BX, OFFSET BUFFER
MOV AX, 0301H
MOV CX, 0001H
MOV DX, 0000H
INT 13H
;写入软盘A, 0面0道1扇区
;把此扇区数据全部置为0FFH
JC ERROR
……
ERROR: ……
C_SEG ENDS
END START
INT 13H,AH=04H 检测扇区

说明:
这个功能检测磁盘上1个或更多的扇区。这个验证测试不是把磁盘上的数据和内存中的数据进行比较,而只是简单地确定读出的数据有无CRC错误。
这个功能可用来验证驱动器中的软盘版。如果盘片的格式正确,CF=0。
入口参数:
AH=03H 指明调用检测扇区功能。
AL 置要检测的连续扇区数目,不允许使用超出磁道末端以外的数值,也不允许使该寄存器为0。
DL 需要进行检测的驱动器号。
DH 磁盘的磁头号。
CH 磁道号的低8位数。
CL 低5位放入起始扇区号,位7-6表示磁道号的高2位。
返回参数:
如果CF=1,AX中存放出错状态。CF=0,检测正确。详情请参见磁盘错误状态返回码一文。
示例:
C_SEG SEGMENT PUBLIC
ASSUME CS:C_SEG,DS:C_SEG
ORG 100H
START: MOV AX, 0401H
MOV CX, 0001H
MOV DX, 0000H
INT 13H
;检测软盘A, 0面0道1扇区
JC ERROR
……
ERROR: ……
C_SEG ENDS
END START

磁盘错误状态返回码:

磁盘错误状态

AH=
00H 未出错
01H 非法功能调用命令区。
02H 地址标记损坏,扇区标识(ID)无效或未找到。
03H 企图对有写保护的软盘执行写操作。
04H 所寻找的扇区没找到。
05H 复位操作失败。
06H 无介质。
07H 初始化错误,数据未存在DMA的64K缓冲区内。
08H DMA故障
09H DMA边界错误,数据未存在DMA的64K缓冲区内。
0AH 检测出错误码率的扇区标志。
0BH 所寻找的磁道没找到。
0CH 介质类型没发现。
0DH 扇区号有问题。
0EH 发现控制数据地址标记。
0FH 超出DMA边界
10H 读磁盘时奇偶校验错,且纠错码(EDC)不能纠正。
11H 读磁盘时奇偶校验错,但纠错码(EDC)已纠正错误。
20H 控制器错。
40H 查找操作无效。
80H 超时错误,驱动器不响应。
AAH 驱动器未准备好。
BBH 不明错误。
CCH 被选驱动器出现写故障。
E0H 错误寄存器是零
FFH 非法操作。

备注:
控制器的最后状态将会在磁盘操作完成后写入相应的BIOS数据区(40:41)