精华内容
下载资源
问答
  • 用这段代码跳转到系统文件管理器了。 但是没有保存按钮该怎么保存?能不能在跳转的界面上添加个保存按钮 ![图片说明](https://img-ask.csdn.net/upload/201603/30/1459329298_876563.jpg)想要的效果! [图片...
  • CvMemStorage 内存储存器

    千次阅读 2012-12-21 08:54:28
    CvMemStorage 内存储存器,双向链表,OpenCV以此统一管理动态内存。 /* Creates new memory storage.  block_size == 0 means that default,  somewhat optimal size, is used (currently, it is 64K) */ ...

    CvMemStorage  内存储存器,双向链表,OpenCV以此统一管理动态内存。

    /* Creates new memory storage.
       block_size == 0 means that default,
       somewhat optimal size, is used (currently, it is 64K) */
    CVAPI(CvMemStorage*)  cvCreateMemStorage( int block_size CV_DEFAULT(0));创建一个内存储存器,block_size对应内存存储器每个内存块的大小。默认为0,默认大小为64k。

    /* Releases memory storage. All the children of a parent must be released before
       the parent. A child storage returns all the blocks to parent when it is released */
    CVAPI(void)  cvReleaseMemStorage( CvMemStorage** storage );释放内存储存器的所有内存空间。

    /* Clears memory storage. This is the only way(!!!) (besides cvRestoreMemStoragePos)
       to reuse memory allocated for the storage - cvClearSeq,cvClearSet ...
       do not free any memory.
       A child storage returns all the blocks to the parent when it is cleared */


    CVAPI(void)  cvClearMemStorage( CvMemStorage* storage );清空内存存储器。它与cvReleaseMemStorage的区别在于,它仅将释放的内存返还给内存存储器,并不返还给

    系统。可方便重复使用内存存储器中的内存空间。

     

     

    展开全文
  • 计算机系统的储存机制现在主要

    计算机系统的储存机制现在主要是 段页式储存,即分段也分页。

    分页将程序分为相等的固定长且比较小的程序块,每一个程序块称为页,能分配到存储器中 分割为固定长度的储存块(称为页帧)上。

    用页表来保存一些数据,保存的内容为进程中每一页的页帧地址。

    逻辑地址为 在程序代码中 相对与程序起始地址的存储单元地址。转换为物理地址的操作为,根据逻辑地址的页号,找到其对应的页表中的信息,即页帧地址,然后将逻辑地址的页号改为页帧地址前部,获得完整的物理地址。


    由于分页,导致了虚拟存储器的概念。即当程序加载时,外部看到的似乎整个程序已经被加载到 内存中,而事实上,其只是一部分在内存上,还有一些仍在磁盘上,则这个时候我们看到的这个加载了程序的内存称为虚拟存储器。由于分页导致抖动问题:抖动是指 处理器花费大量时间用于交换页而不是执行指令。


    分段 是对程序员课件的,为组织程序和数据提供方便,并提供把特权,保护属性,与指令数据想联系的手段。分段允许程序员把存储器看成多个地址空间或段组成。段长度是可变的,是动态分配的。


    对于分段分页存储器:

    分段分页的地址称为虚拟地址,将其的段号解读,转换为线性地址,线性地址为整个程序作为一个整体时的地址,再转换为物理地址。

    一开始的逻辑地址为段号 加 段内位移。将其查段表,计算获得线性地址,线性地址左端为 目录和页号,先在页目录表中找到对应的页表,然后再在页表中找到对应的页帧地址,与位移结合获得物理地址。


    展开全文
  • 管理器class Manager管理器是一个接口,数据库查询操作通过它提供给django的模型。django应用的每个模型至少拥有一个 管理器管理器类的工作方式在 执行查询文档中阐述,而这篇文档涉及了自定义管理器行为的模型...

    Django 文档协作翻译小组人手紧缺,有兴趣的朋友可以加入我们,完全公益性质。

    交流群:467338606

    网站:http://python.usyiyi.cn/django/index.html

    管理器

    class Manager

    管理器是一个接口,数据库查询操作通过它提供给django的模型。django应用的每个模型至少拥有一个 管理器。

    管理器类的工作方式在 执行查询文档中阐述,而这篇文档涉及了自定义管理器行为的模型选项。

    管理器的名字

    通常,django为每个模型类添加一个名为objects的管理器。然而,如果你想将objects用于字段名称,或者你想使用其它名称而不是objects访问管理器,你可以在每个模型类中重命名它。在模型中定义一个值为models.Manager()的属性,来重命名管理器。例如:

    from django.db import models
    
    class Person(models.Model):
        #...
        people = models.Manager()

    使用例子中的模型, Person.objects会抛出AttributeError异常,而Person.people.all()会返回一个包含所有Person对象的列表。

    自定义管理器

    在一个特定的模型中,你可以通过继承管理器类来构建一个自定义的管理器,以及实例化你的自定义管理器。

    你有两个原因可能会自己定义管理器:向器类中添加额外的方法,或者修改管理器最初返回的查询集。

    添加额外的管理器方法

    为你的模型添加表级(table-level)功能时,采用添加额外的管理器方法是更好的处理方式。如果要添加行级功能--就是说该功能只对某个模型的实例对象起作用。在这种情况下,使用 模型方法 比使用自定义的管理器方法要更好。)

    自定义的管理器 方法可以返回你想要的任何数据,而不只是查询集。

    例如,下面这个自定义的 管理器提供了一个 with_counts() 方法,它返回所有 OpinionPoll 对象的列表,而且列表中的每个对象都多了一个名为 num_responses的属性,这个属性保存一个聚合查询(COUNT*)的结果:

    from django.db import models
    
    class PollManager(models.Manager):
        def with_counts(self):
            from django.db import connection
            cursor = connection.cursor()
            cursor.execute("""
                SELECT p.id, p.question, p.poll_date, COUNT(*)
                FROM polls_opinionpoll p, polls_response r
                WHERE p.id = r.poll_id
                GROUP BY p.id, p.question, p.poll_date
                ORDER BY p.poll_date DESC""")
            result_list = []
            for row in cursor.fetchall():
                p = self.model(id=row[0], question=row[1], poll_date=row[2])
                p.num_responses = row[3]
                result_list.append(p)
            return result_list
    
    class OpinionPoll(models.Model):
        question = models.CharField(max_length=200)
        poll_date = models.DateField()
        objects = PollManager()
    
    class Response(models.Model):
        poll = models.ForeignKey(OpinionPoll)
        person_name = models.CharField(max_length=50)
        response = models.TextField()

    在这个例子中,你已经可以使用 OpinionPoll.objects.with_counts() 得到所有含有 num_responses属性的 OpinionPoll对象。

    这个例子要注意的一点是: 管理器方法可以访问 self.model来得到它所用到的模型类。

    修改管理器初始的查询集

    管理器自带的 查询集返回系统中所有的对象。例如,使用下面这个模型:

    from django.db import models
    
    class Book(models.Model):
        title = models.CharField(max_length=100)
        author = models.CharField(max_length=50)

    … Book.objects.all() 语句将返回数据库中所有的 Book 对象。

    你可以通过重写 Manager.get_queryset() 的方法来覆盖 管理器自带的 查询集。get_queryset() 会根据你所需要的属性返回 查询集。

    例如,下面的模型有两个 管理器,一个返回所有的对象,另一个则只返回作者是 Roald Dahl 的对象:

    # First, define the Manager subclass.
    class DahlBookManager(models.Manager):
        def get_queryset(self):
            return super(DahlBookManager, self).get_queryset().filter(author='Roald Dahl')
    
    # Then hook it into the Book model explicitly.
    class Book(models.Model):
        title = models.CharField(max_length=100)
        author = models.CharField(max_length=50)
    
        objects = models.Manager() # The default manager.
        dahl_objects = DahlBookManager() # The Dahl-specific manager.

    在这个简单的例子中,Book.objects.all()将返回数据库中所有的图书。而 Book.dahl_objects.all() 只返回作者是 Roald Dahl 的图书。

    由于 get_queryset() 返回的是一个 查询集 对象,所以你仍可以对它使用 filter(), exclude()和其他 查询集的方法。所以下面这些例子都是可用的:

    Book.dahl_objects.all()
    Book.dahl_objects.filter(title='Matilda')
    Book.dahl_objects.count()

    这个例子还展示了另外一个很有意思的技巧:在同一个模型中使用多个管理器。你可以随你所意在一个模型里面添加多个 Manager() 实例。下面就用很简单的方法,给模型添加通用过滤器:

    例如:

    class AuthorManager(models.Manager):
        def get_queryset(self):
            return super(AuthorManager, self).get_queryset().filter(role='A')
    
    class EditorManager(models.Manager):
        def get_queryset(self):
            return super(EditorManager, self).get_queryset().filter(role='E')
    
    class Person(models.Model):
        first_name = models.CharField(max_length=50)
        last_name = models.CharField(max_length=50)
        role = models.CharField(max_length=1, choices=(('A', _('Author')), ('E', _('Editor'))))
        people = models.Manager()
        authors = AuthorManager()
        editors = EditorManager()

    在这个例子中,你使用 Person.authors.all(), Person.editors.all(),以及 Person.people.all(), 都会得到和名称相符的结果。

    默认管理器

    如果你使用了自定义 管理器对象,要注意 Django 中的第一个 管理器 (按照模型中出现的顺序而定) 拥有特殊的地位。Django 会将模型中定义的管理器解释为默认的 管理器,并且 Django 中的一部分应用(包括数据备份)会使用默认的管理器,除了前面那个模型。因此,要决定默认的管理器时,要小心谨慎,仔细考量,这样才能避免重写 get_queryset() 导致无法正确地获得数据。

    使用管理器访问关联对象

    默认情况下,在访问相关对象时(例如choice.poll),Django 并不使用相关对象的默认管理器,而是使用一个”朴素”管理器类的实例来访问。这是因为 Django 要能从关联对象中获得数据,但这些数据有可能被默认管理器过滤掉,或是无法进行访问。

    如果普通的朴素管理器类(django.db.models.Manager)并不适用于你的应用,那么你可以通过在管理器类中设置 use_for_related_fields ,强制 Django 在你的模型中使用默认的管理器。这部分内容在 下面有 详细介绍。

    调用自定义的查询集

    虽然大多数标准查询集的方法可以从管理器中直接访问到,但是这是一个例子,访问了定义在自定义 查询集上的额外方法,如果你也在管理器上面实现了它们:

    class PersonQuerySet(models.QuerySet):
        def authors(self):
            return self.filter(role='A')
    
        def editors(self):
            return self.filter(role='E')
    
    class PersonManager(models.Manager):
        def get_queryset(self):
            return PersonQuerySet(self.model, using=self._db)
    
        def authors(self):
            return self.get_queryset().authors()
    
        def editors(self):
            return self.get_queryset().editors()
    
    class Person(models.Model):
        first_name = models.CharField(max_length=50)
        last_name = models.CharField(max_length=50)
        role = models.CharField(max_length=1, choices=(('A', _('Author')), ('E', _('Editor'))))
        people = PersonManager()

    这个例子展示了如何直接从管理器 Person.people调用authors() 和 editors()。

    创建管理器

    * django 1.7 中新增 *

    对于上面的例子,同一个方法需要在查询集 和 管理器上创建两份副本,作为替代,QuerySet.as_manager()可以创建一个管理器的实例,它拥有自定义查询集的方法:

    class Person(models.Model):
        ...
        people = PersonQuerySet.as_manager()

    通过QuerySet.as_manager()创建的管理器 实例,实际上等价于上面例子中的PersonManager。

    并不是每个查询集的方法都在管理器层面上有意义。比如 QuerySet.delete(),我们有意防止它复制到管理器 中。

    方法按照以下规则进行复制:

    • 公共方法默认被复制。
    • 私有方法(前面带一个下划线)默认不被复制。
    • 带queryset_only 属性,并且值为False的方法总是被复制。
    • 带 queryset_only 属性,并且值为True 的方法不会被复制。

    例如:

    class CustomQuerySet(models.QuerySet):
        # Available on both Manager and QuerySet.
        def public_method(self):
            return
    
        # Available only on QuerySet.
        def _private_method(self):
            return
    
        # Available only on QuerySet.
        def opted_out_public_method(self):
            return
        opted_out_public_method.queryset_only = True
    
        # Available on both Manager and QuerySet.
        def _opted_in_private_method(self):
            return
        _opted_in_private_method.queryset_only = False

    from_queryset

    classmethod from_queryset(queryset_class)

    在进一步的使用中,你可能想创建一个自定义管理器和一个自定义查询集。你可以调用Manager.from_queryset(),它会返回管理器的一个子类,带有自定义查询集所有方法的副本:

    class BaseManager(models.Manager):
        def manager_only_method(self):
            return
    
    class CustomQuerySet(models.QuerySet):
        def manager_and_queryset_method(self):
            return
    
    class MyModel(models.Model):
        objects = BaseManager.from_queryset(CustomQueryset)()

    你也可以在一个变量中储存生成的类:

    CustomManager = BaseManager.from_queryset(CustomQueryset)
    
    class MyModel(models.Model):
        objects = CustomManager()

    自定义管理器和模型继承

    类继承和模型管理器两者之间配合得并不是很好。 管理器一般只对其定义所在的类起作用,在子类中对其继承绝对不是一个好主意。 而且,因为第一个 管理器会被 Djange 声明为默认的管理器,所以对默认的管理器 进行控制是非常必要的。下面就是 Django 如何处理自定义管理器和模型继承(model inheritance)的:

    • 定义在非抽象基类中的管理器是 不会 被子类继承的。如果你想从一个非抽象基类中重用管理器,只能在子类中重定义管理器。 这是因为这种管理器与定义它的模型 绑定得非常紧密,所以继承它们经常会导致异常的结果(特别是默认管理器运行的时候)。 因此,它们不应继承给子类。
    • 定义在抽象基类中的管理器总是被子类继续的,是按 Python 的命名解析顺序解析的(首先是子类中的命名覆盖所有的,然后是第一个父类的,以此类推)。 抽象类用来提取子类中的公共信息和行为,定义公共管理器也是公共信息的一部分。
    • 如果类当中显示定义了默认管理器,Django 就会以此做为默认管理器;否则就会从第一个抽象基类中继承默认管理器; 如果没有显式声明默认管理器,那么 Django 就会自动添加默认管理器。

    如果你想在一组模型上安装一系列自定义管理器,上面提到的这些规则就已经为你的实现提供了必要的灵活性。你可以继承一个抽象基类,但仍要自定义默认的管理器。例如,假设你的基类是这样的:

    class AbstractBase(models.Model):
        # ...
        objects = CustomManager()
    
        class Meta:
            abstract = True

    如果你在基类中没有定义管理器,直接使用上面的代码,默认管理器就是从基类中继承的 objects:

    class ChildA(AbstractBase):
        # ...
        # This class has CustomManager as the default manager.
        pass

    如果你想从 AbstractBase继承,却又想提供另一个默认管理器,那么你可以在子类中定义默认管理器:

    class ChildB(AbstractBase):
        # ...
        # An explicit default manager.
        default_manager = OtherManager()

    在这个例子中, default_manager就是默认的 管理器。从基类中继承的 objects 管理器仍是可用的。只不过它不再是默认管理器罢了。

    最后再举个例子,假设你想在子类中再添加一个额外的管理器,但是很想使用从 AbstractBase继承的管理器做为默认管理器。那么,你不在直接在子类中添加新的管理器,否则就会覆盖掉默认管理器,而且你必须对派生自这个基类的所有子类都显示指定管理器。 解决办法就是在另一个基类中添加新的管理器,然后继承时将其放在默认管理器所在的基类 之后。例如:

    class ExtraManager(models.Model):
        extra_manager = OtherManager()
    
        class Meta:
            abstract = True
    
    class ChildC(AbstractBase, ExtraManager):
        # ...
        # Default manager is CustomManager, but OtherManager is
        # also available via the "extra_manager" attribute.
        pass

    注意在抽象模型上面定义一个自定义管理器的时候,不能调用任何使用这个抽象模型的方法。就像:

    ClassA.objects.do_something()

    是可以的,但是:

    AbstractBase.objects.do_something()

    会抛出一个异常。这是因为,管理器被设计用来封装对象集合管理的逻辑。由于抽象的对象中并没有一个集合,管理它们是毫无意义的。如果你写了应用在抽象模型上的功能,你应该把功能放到抽象模型的静态方法,或者类的方法中。

    实现上的注意事项

    无论你向自定义管理器中添加了什么功能,都必须可以得到 管理器实例的一个浅表副本:例如,下面的代码必须正常运行:

    >>> import copy
    >>> manager = MyManager()
    >>> my_copy = copy.copy(manager)

    Django 在一些查询中会创建管理器的浅表副本;如果你的管理器不能被复制,查询就会失败。

    这对于大多数自定义管理器不是什么大问题。如果你只是添加一些简单的方法到你的管理器中,不太可能会把你的管理器实例变为不可复制的。但是,如果你覆盖了__getattr__,或者其它管理器中控制对象状态的私有方法,你应该确保不会影响到管理器的复制。

    控制自动管理器的类型

    这篇文档已经提到了Django创建管理器类的一些位置:默认管理器和用于访问关联对象的“朴素” 管理器。在 Django 的实现中也有很多地方用到了临时的朴素管理器。 正常情况下,django.db.models.Manager 类的实例会自动创建管理器。

    在整个这一节中,我们将那种由 Django 为你创建的管理器称之为 “自动管理器”,既有因为没有管理器而被 Django 自动添加的默认管理器, 也包括在访问关联模型时使用的临时管理器。

    有时,默认管理器也并非是自动管理器。 一个例子就是 Django 自带的django.contrib.gis 应用,所有 gis模型都必须使用一个特殊的管理器类(GeoManager),因为它们需要运行特殊的查询集(GeoQuerySet)与数据库进行交互。这表明无论自动管理器是否被创建,那些要使用特殊的管理器的模型仍要使用这个特殊的管理器类。

    Django 为自定义管理器的开发者提供了一种方式:无论开发的管理器类是不是默认的管理器,它都应该可以用做自动管理器。 可以通过在管理器类中设置 use_for_related_fields 属性来做到这点:

    class MyManager(models.Manager):
        use_for_related_fields = True
        # ...

    如果在模型中的默认 管理器(在这些情况中仅考虑默认管理器)中设置了这个属性,那么无论它是否需要被自动创建,Django 都会自动使用它。否则 Django 就会使用 django.db.models.Manager.

    历史回顾

    从它使用目的来看,这个属性的名称(use_for_related_fields)好象有点古怪。原本,这个属性仅仅是用来控制访问关联字段的管理器的类型,这就是它名字的由来。 后来它的作用更加拓宽了,但是名称一直未变。 因为要保证现在的代码在 Django 以后的版本中仍可以正常工作(continue to work),这就是它名称不变的原因。

    在自动管理器实例中编写正确的管理器

    在上面的django.contrib.gis 已经提到了, use_for_related_fields这个特性是在需要返回一个自定义查询集子类的管理器中使用的。要在你的管理器中提供这个功能,要注意以下几点。

    不要在这种类型的管理器子类中过滤掉任何结果

    一个原因是自动管理器是用来访问关联模型 的对象。 在这种情况下,Django 必须要能看到相关模型的所有对象,所以才能根据关联关系得到任何数据 。

    如果你重写了 get_queryset() 方法并且过滤掉了一些行数据,Django 将返回不正确的结果。不要这么做! 在 get_queryset()方法中过滤掉数据,会使得它所在的管理器不适于用做自动管理器。

    设置 use_for_related_fields

    use_for_related_fields属性必须在管理器类中设置,而不是在类的 实例中设置。上面已经有例子展示如何正确地设置,下面这个例子就是一个错误的示范:

    # BAD: Incorrect code
    class MyManager(models.Manager):
        # ...
        pass
    
    # Sets the attribute on an instance of MyManager. Django will
    # ignore this setting.
    mgr = MyManager()
    mgr.use_for_related_fields = True
    
    class MyModel(models.Model):
        # ...
        objects = mgr
    
    # End of incorrect code.

    你也不应该在模型中使用这个属性之后,在类上改变它。这是因为在模型类被创建时,这个属性值马上就会被处理,而且随后不会再读取这个属性值。 这节的第一个例子就是在第一次定义的时候在管理器上设置use_for_related_fields属性,所有的代码就工作得很好。

    展开全文
  • 第五章 Windows CE的储存管理 本章将介绍Windows CE 的储存管理。我们将本章内容分为两大部分,前半部会依序介绍 Windows CE的档案系统类型、 Windows CE储存管理结构和每一个层次、以及如何自行开发档案系统并加载...
     
    

     

    第五章 Windows CE的储存管理

     

    本章将介绍Windows CE 的储存管理。我们将本章内容分为两大部分,前半部会依序介绍 Windows CE的档案系统类型、 Windows CE储存管理结构和每一个层次、以及如何自行开发档案系统并加载之,后半部则以Ramdisk上的档案系统为例,实际分析储存管理相关的原始程序代码与数据型态。

     

    5.1 Windows CE的储存管理架构

     

    5.1.1 概述

     

     Windows CE提供了三种类型的档案系统:RAM-based 档案系统、ROM-based 档案系统、以及用于支持ATA (Advanced Technology Attachment) 装置和SRAM卡等外围储存装置的FAT档案系统。其中,前两种档案系统属于 Windows CE的内建档案系统,后者属于可安装性档案系统。另外,嵌入式系统的开发人员也可以编写自己的档案系统,并在系统中注册使用。Windows CE提供了platform-independent API,不论是何种储存装置,所有对档案系统的存取都是透过Win32 API完成。

     

    Windows CE预设的储存装置为最大可达256MBRAM内存。RAM被分割为程序空间 (program memory) 和对象空间 (object store) 。程序空间和一般计算机系统中RAM的使用类似,用来储存执行程序及所需数据。对象空间则类似一般计算机系统中的硬盘,用来储存应用程序及档案。对象空间中存放的数据可分为三大类: (1) 档案系统 (file system) (2) 注册信息 (registry) (3)  Windows CE数据库 (Windows CE database) Windows CE系统的电源管理机制,即使在关机状态时,仍旧有少量的电力从电池供应RAM,以保留储存在其中的数据。只有在电池电力完全耗尽时,RAM中储存的数据才会消失。

     

    与对象空间相关的RAM-based档案系统和ROM-based档案系统,是 Windows CE预设支持的内建档案系统。除此之外,使用者还可以安装用于支持外围储存装置的档案系统,比如FATUDFS等等。对外围储存装置的存取,都是透过这种可安装性档案系统来完成的。另外,我们还可以将一个外围储存装置,分为多个volume()并分别加载,其中每个volume可以使用不同的档案系统。Windows CE没有像Windows XPWindows ME使用磁盘驱动器代号来表示volume,加载后的volumeWindows CE内以目录的形式呈现。

     

    5.1.2 对象空间

     

    如前所述,对象空间的数据分为三部分-档案系统、系统注册信息及数据库。然而,档案系统、数据库以及注册信息都不一定要储存在对象空间中,它们也可以被存放ROM或是外围储存装置中。数据的建立和使用,与实际的储存装置无关,只是依赖于储存的类型。

     

    操作系统负责管理内存堆,在必要的时候会对档案进行压缩和解压缩。对象空间使用基于事务的数据管理机制。如果当数据写入对象空间时,发生了电源中断的情况,Windows CE会透过各种手段保证对象空间不被破坏,例如在系统重启后继续完成该写操作,或是恢复到电源中断前的状态。

     

    在早期的Windows CE版本中,RAM档案系统和档案大小最大皆只能达16MB。到了Windows CE 3.0这个版本,RAM档案系统最大可达256MB,而档案大小最大也能达32MB。另外,对象空间中的对象数目也由原本的65,536增加到4,000,000左右。最新的Windows CE .NET允许档案最大可达4GB

    Windows CE为对象空间中的每个对象,都分配了一个唯一的对象识别码 (Windows CE object identifier, 简称CEOID) 。对象识别码的作用就是用于存取对象空间中的对象。在对象空间中,以下列出的各项都可以被定义为一个对象:

     

    • 注册信息中的一个键
    • 注册信息中的一个值
    • 一个档案
    • 档案数据中大小为4KB的一部分 (意即一个12KB的档案本身会对应一个档案对象,另外其所含的三个4KB的数据块也分别对应着一个对象)
    • 数据库中的一项记录 (最多可达4KB数据)
    • 数据库中一项记录的扩展信息 (也可达4KB数据)
    • 一个数据库
    • 数据库的一个volume (a database volume)

     

    需要说明的一点是,Windows CE仅仅保证对象识别码在同一个volume中是唯一的,当有多个volume,可能在不同volume中发现相同对象识别码的对象。因此,Windows CE为每个数据库volume (database volume) 都分配了一个唯一的数据库识别码 (CEGUID)。数据库识别码和对象识别码的结合,就可以表示数据库volume中的某个对象了。

     

    要对对象空间中的对象进行存取,第一步就是获得该对象的对象识别码。根据不同的对象类型,Windows CE提供了相对应的函式以获得该对象的对象识别码,参考表5.1

     

    5.1 获得对象识别码函式

    对象类型

    获得对象识别码的方法

    目录或档案

    FindFirstFileFindNextFile函式回传的WIN32_FIND_DATA结构的dwOID字段。GetFileInformationByHandle函式回传的BY_HANDLE_FILE_INFORMATION结构的dwOID字段。

    数据库

    CeCreateDatabaseEx2CeFindNextDatabaseEx函式的回传值

    数据库记录

    CeSeekDatabaseExCeReadRecordPropsEx以及 CeWriteRecordProps函式的回传值。

    已加载的数据库volume

    CeMountDBVolCeEnumDBVolumes函式可以回传已挂载数据库volume中的数据库识别码。

     

    有了对象识别码,就可以使用CeOidGetInfoEx函式得到该对象内的数据。CeOidGetInfoEx函式会回传一个CEOIDINFO结构 (结构原型列于程序代码5.1)CEOIDINFO结构有一个wObjType字段说明了对象的类型。假如该字段的值为OBJTYPE_DATABASE,就指这是一个数据库对象。由wObjType可得知该对象是档案、目录、数据库还是数据库记录,同时也得知该对象的数据地址。例如,如果对一个数据库对象使用CeOidGetInfoEx的话,那么回传的类型值为OBJTYPE_DATABASE,而与之相关的数据库名称、类型识别码、记录的数目、数据库的大小以及排序方式等信息,都将保存在一个CEDBASEINFO结构中。

     

    程序代码5.1 CEOIDINFO数据结构

    typedef struct _CEOIDINFO {

    WORD  wObjType;                                        //Type of object

    //      OBJTYPE_INVALID                                     | There was no valid object

    //                                                                                   with this CEOID

    //      OBJTYPE_FILE                                           | The object is a file

    //      OBJTYPE_DIRECTORY                            | The object is a directory

    //      OBJTYPE_DATABASE                      | The object is a database

    //      OBJTYPE_RECORD                                  | The object is a record inside

    //                                                                                   a database

    WORD   wPad;                                                       // dword alignment

    union {                                                                         //This is a union

    CEFILEINFO  infFile;                            //Valid for file objects

    CEDIRINFO   infDirectory;                           //Valid for directory objects

    // @CESYSGEN IF FILESYS_FSDBASE

    CEDBASEINFO infDatabase;                        //Valid for database objects

    CERECORDINFO infRecord;                         //Valid for record objects

    // @CESYSGEN ENDIF

    };

    } CEOIDINFO, PCEOIDINFO;

     

    5.1.3 储存管理程序

     

    储存管理程序 (Storage Manager) 负责管理外围储存装置所使用的档案系统和区块装置驱动程序 (Block Device Driver) 。储存管理程序的功能实作位在fsdmgr.dll模块,它由三部分组成,包括区块装置管理程序 (Block Driver Manager) 、分割区管理程序 (Partition Manager) 以及档案系统管理程序 (File System Driver Manager,或简称FSD Manager) 。所有对档案和volume的处理,都是经由储存管理程序完成的,储存管理程序在永久性储存介质的存取过程中,扮演了很重要的角色。

     

    5.1 储存管理程序结构图

     

    Windows CE的储存管理程序是一个分层结构 ,其结构如图5.1所示,因此做Disk I/O必需经过储存管理程序的各个阶层。首先是过滤器层 (File System Filter) ,在将I/O请求交由档案系统驱动程序处理之前,在过滤器这一层可以完成一些加密、压缩和病毒扫描等性质的额外工作。接下来是档案系统驱动程序 (File System Driver) ,在这里对参数进行格式化,将文件名称转换为区块装置驱动程序可以识别的物理地址。在这之后,就可以经分割区管理程序,或直接交由区块装置驱动程序,完成实体的I/O操作了。

     

    在储存管理程序的启动过程中,有很多信息是从系统的注册信息中得到的。例如,下面的注册信息会告知系统加载储存管理程序模块:

     

    [HKEY_LOCAL_MACHINE/System/StorageManager]

        "Dll"="fsdmgr.dll"

     

    另外,每当系统检测到新的外围储存装置时,装置管理程序会自动加载支持该装置的区块装置驱动程序,之后向储存管理程序报告,该装置的配置信息在注册信息中的位置。储存管理程序就是根据这些信息,决定如何加载适当的分割区驱动程序 (partition driver) 和档案系统驱动程序。下面是所有装置都可使用的预设配置信息:

     

    [HKEY_LOCAL_MACHINE/System/StorageManager/Profiles]

    "AutoMount"=dword:1

    "AutoPart"=dword:0

    "AutoFormat"=dword:0

    "MountFlags"=dword:0

    "DefaultFileSystem"="FATFS"

    "PartitionDriver"="mspart.dll"

    "Folder"="Mounted Volume"

     

    其中AutoFormat表明是否对该装置进行自动格式化,AutoPart表明是否用该装置上的最大可用空间自动建立分割区,AutoMount表明是否自动挂载每个检测到的分割区,而MountFlags则指出了应如何对分割区进行挂载。

     

    如果装置管理程序向储存管理程序,指定新装置的配置信息所在的位置,那么储存管理程序会使用该装置的指定配置信息。例如,硬盘的配置信息可能如下:

     

    [HKEY_LOCAL_MACHINE/System/StorageManager/Profiles/HDProfile]

        "Name"="IDE Hard Disk Drive"

        "Folder"="Hard Disk"

     

    对于未提供一些配置信息,比如AutoFormatAutoPartAutoMount,则要使用系统提供的默认值。

     

           当系统发现新的外围储存装置时,储存管理程序为其加载分割区驱动程序和档案系统驱动程序的程序如下:

     

    1.装置管理程序负责加载区块装置的驱动程序,

    2.由该装置发出一个通知,告知储存管理程序该装置的装置名称及其GUID

    3.储存管理程序根据注册信息中的配置信息,为该装置加载分割区驱动程序,

    4.储存管理程序行举该装置上的所有分割区,

    5.储存管理程序为每个分割区,加载档案系统驱动程序。

     

    5.1.3.1 档案系统过滤器

     

    档案系统过滤器和档案系统驱动程序类似,它们都有一组输出函式被CreateFile CreateDirectory这类的标准档案系统API映像到。二者不同之处在于,档案系统过滤器要提供HookVolumeUnhookVolume这两个函式,而档案系统驱动程序必须提供的是MountDiskUnmountDisk这两个函式。档案系统驱动程序和档案系统过滤器的加载,都是透过档案系统管理程序完成的。

     

    有关档案系统过滤器的信息,也是保存在注册信息中。档案系统管理程序需要到以下各处搜寻应加载的档案系统过滤器:

     

    [HKEY_LOCAL_MACHINE/System/StorageManager/AutoLoad/FileSystem
    /Filters]

    [HKEY_LOCAL_MACHINE/System/StorageManager/Profiles/ProfileName
    /FileSystem/Filters]

    [HKEY_LOCAL_MACHINE/System/StorageManager/FileSystem/Filters]

    [HKEY_LOCAL_MACHINE/System/StorageManager/Filters]

     

    此处,FileSystem代表档案系统在注册信息中所使用的名称,例如FATFSUDFS等等。如果在

     

    [HKEY_LOCAL_MACHINE/System/StorageManager/AutoLoad/FileSystem
    /Filters]

    [HKEY_LOCAL_MACHINE/System/StorageManager/Profiles/ProfileName
    /FileSystem/Filters]

     

    中出现了一个过滤器的话,那么所有使用该配置信息的档案系统,都将使用该过滤器。如果在

     

    [HKEY_LOCAL_MACHINE/System/StorageManager/FileSystem/Filters]

     

    中出现了一个过滤器的话,那么名为FileSystem的档案系统将使用该过滤器。如果一个过滤器出现在

     

    [HKEY_LOCAL_MACHINE/System/StorageManager/Filters]

     

    中,那么所有的档案系统都将使用该过滤器。下面是一个注册信息中有关过滤器信息的例子:

     

    [HKEY_LOCAL_MACHINE/System/StorageManager/.../Filters/FilterName]

    "dll"="filter.dll"

    "Order"=dword:x

     

    其中dll的值,是该过滤器所对应的动态链接库,Order的值,则是建造过滤器堆栈时各过滤器之间的顺序。

     

    5.1.3.2 档案系统管理程序与档案系统驱动程序

     

    首先来看一下档案系统驱动程序 (File System DriverFSD) 。档案系统驱动程序只适用于区块装置。我们知道,任何装置加载系统后,都需要对应的装置驱动程序的配合才能得以工作,对于区块装置而言,自然也少不了区块装置驱动程序。但是与其它装置不同的是,区块装置除了需要直接和硬件沟通的区块装置驱动程序之外,还需要一个档案系统驱动程序,这是因为,虽然区块装置驱动程序已经向上层提供了各种函式接口,透过这些接口就可以对区块装置进行初始化、读写数据区块等等各种操作了,但是这种接口的操作对象却是数据区块,如果直接将这种接口提供给使用者使用的话,使用者所见到的区块装置就是一个杂乱无章、毫无规律的数据区块集合,这对使用者而言非常地不方便。因此必需将这些散乱的数据区块,组织成档案的形式,并向使用者提供一个按名存取的档案系统接口,这就是档案系统驱动程序的主要功能。意即档案系统驱动程序建立在区块装置驱动程序模块之上,负责对区块装置的进一步抽象,把毫无规律的数据区块,组织成对使用者来说,更为方便易用的档案,并向上提供了档案操作的函式接口。档案系统程序与区块装置驱动程序间的关系如图5.2所示。

     

    File 1

    File 2

    File k

    ……

    档案系统驱动程序

    区块装置驱动程序

    B 1

    B 2

    B n

    ……

    5.2 档案系统驱动程序和区块装置驱动程序关系图

     

    Windows CE支持大多数Windows XPWindows ME使用的档案I/O函式,例如CreateFileReadFileWriteFileCloseHandleWin32 API呼叫。同时,Windows CE仍然遵循使用句柄对档案进行存取的传统方法。由CreateFile函式回传,新建立或打开的档案句柄,之后的读写操作,都使用该句柄来决定档案操作的对象。当然,对读写操作来说,还需要一个指标,来指出在档案中进行读写的位置。

     

    系统中所有的可安装档案系统,都是由档案系统管理程序负责管理的。每个可安装档案系统,都对应着一个档案系统驱动程序,并且以动态链接库 (Dynamic Link Library) 的形式提供,它有一组输出函式被映像到标准的档案系统Win32 API上。像CreateFileReadFileWriteFileCloseHandle这些档案系统API的功能,最后都是呼叫某个档案系统驱动程序的函式MyFSD_CreateFileWMyFSD_ReadFileMyFSD_WriteFileMyFSD_CloseFile完成的。另外值得注意的一点是,档案系统驱动程序的动态链接库的名字,与该档案系统驱动程序的输出函式的前缀 (prefix) 是相同的。例如,如果档案系统名为MyFSD,那么对应的动态链接库就是MyFSD.dll,其输出函式的前缀为MyFSD_*

     

    在应用程序中存取可安装档案系统,使用的是标准的档案系统Win32 API。例如,当应用程序想要在某个储存装置上建立一个目录时,可以呼叫CreateDirectory。之后由档案系统管理程序对CreateDirectory中指明的路径加以识别,如果发现该路径指向一个使用可安装档案系统的装置,便会呼叫相对应的档案系统驱动程序-MyFSD.dll内的建立目录函式MyFSD_CreateDirectoryW。也就是说,应用程序呼叫CreateDirectory的结果,是导致档案系统管理程序,呼叫相对应档案系统驱动程序的函式MyFSD_CreateDirectoryW

     

    Windows CE中,负责管理档案系统驱动程序的模块,称为档案系统管理程序 (File System Driver Manager) 。由于现代操作系统一般都支持多个档案系统并存,因此需要对系统中用到的各个档案系统加以管理,这就是档案系统管理程序的作用。从功能上讲,Windows CE中的档案系统管理程序与Linux中的虚拟档案系统VFS类似,如图5.3所示。首先,作为不同的档案系统,它们向上层提供的函式接口必然也是不同的。但是,如果为了使用系统中不同的档案系统,就必须记忆多套不同的函式接口,这对使用者来说是难以接受的。我们所希望的是向使用者提供一套CreateFileReadFileWriteFileCloseHandle这样的标准接口,为使用者隐藏掉不同的档案系统间的差异。这个任务就是由档案系统管理程序来完成的。在Windows CE中,使用者见到的并不是档案系统驱动程序所提供的接口,而是经过档案系统管理程序统一包装过的Win32 API函式接口。这种统一接口的作法虽方便了使用者,但另一方面却又不可避免的带来了系统复杂性。对使用者而言,使用者根本不用关心,他所使用的档案所在的volume到底使用何种档案系统,因为档案系统管理程序已经为使用者隐藏了其中的细节,但是在进行的实际的档案操作时,我们又必须要能够找出对应的档案系统,这也是由档案系统管理程序来完成的。在Windows CE中没有目前目录 (current directory) 的概念,所有对档案的存取都必需提供完整路径名称。在为外围储存装置安装档案系统驱动程序的同时,档案系统管理程序会为其注册一个标识性的目录名称。如此一来,当使用者对外围储存装置上的档案进行存取时,档案系统管理程序就可以根据使用者提供的完整目录名称,分析出该档案所在的volume、使用的档案系统等。

     

    …..

    Standard file system Win32 API

    档案系统管理程序

    FSD 1

    FSD 2

    FSD n

    5.3 档案系统管理程序与档案系统驱动程序关系图

     

    5.1.3.3 分割区管理程序与分割区驱动程序

     

    一个储存装置是由多个扇区 (sector) 组成的,每个扇区包含了一组连续的字节,所有扇区的大小都是相同的。储存装置可能被划分为多个逻辑分割区,每个分割区由一组连续的扇区组成。分割区之间不能相互重迭,否则操作系统将停止响应。没有被划分到任何分割区中的储存空间属于未分割区空间。分割区管理程序是储存管理程序的一部分,它透过呼叫分割区驱动程序来完成对这些分割区的管理、挂载 (mount) 与卸载 (unmount)

     

    同一储存装置上的不同分割区可以使用不同的档案系统,每个分割区所使用的档案系统类型是在建立或格式化时就确定了。在对该分割区进行挂载时,分割区管理程序会通知储存管理程序启动对应的档案系统。

     

    Windows CE可以同时支持多个分割区驱动程序,但是对于一个指定的储存装置来讲只能使用一个分割区驱动程序,由它为分割区管理程序解释该装置上的所有分割区。

     

    如前所述,分割区是对储存装置进行逻辑划分的结果。在Windows CE中,多个分割区既可以使用相同的档案系统,也可以使用不同的档案系统。分割区管理程序透过呼叫分割区驱动程序完成对分割区的操作。分割区驱动程序也是以动态链接库的形式提供的,在Windows CE中预设的分割区驱动程序是MSPart.dll,它输出了一组API。使用者也可以编写自己的分割区驱动程序,只要该分割区驱动程序支持与MSPart.dll相同的API即可。为了使自己编写的分割区驱动程序MyPart.dll生效,还需要在注册信息的配置信息中填写"PartitionDriver"="MyPart.dll",这样就可以使用自己的分割区驱动程序了。表5.2说明了一个分割区驱动程序应该提供的所有函式。

     

    5.2 分割区驱动程序提供的函式

    函式名

    功能

    PD_ClosePartition

    结束I/O操作,关闭分割区

    PD_CloseStore

    关闭储存装置上的volume

    PD_CreatePartition

    在储存装置的volume上建立分割区

    PD_DeletePartition

    从储存装置的volume上删除分割区

    PD_DeviceIoControl

    向指定的装置驱动程序直接发送句柄,完成相对应的操作

    PD_FindPartitionClose

    关闭列举分割区时使用的搜寻句柄

    PD_FindPartitionNext

    在列举分割区时得到下一个分割区的信息。

    PD_FindPartitionStart

    开始列举分割区。

    PD_FormatPartition

    在储存装置的volume上格式化一个分割区

    PD_FormatStore

    对储存装置进行格式化操作

    PD_GetPartitionInfo

    得到指定分割区的属性信息

    PD_GetStoreInfo

    得到储存装置的信息

    PD_IsStoreFormatted

    查询储存装置是否已被格式化

    PD_OpenPartition

    打开分割区以进行I/O操作

    PD_OpenStore

    打开储存装置上的volume

    PD_RenamePartition

    重新命名储存装置上的一个分割区

    PD_SetPartitionAttrs

    设定指定分割区的属性

     

    5.1.3.4 区块装置管理程序

     

    区块装置的读写操作都是以区块 (block) 为单位,而不是以字节为单位进行的。区块的大小都是固定的,常用的有512 字节、1 KB2 KB4 KB。区块装置驱动程序通常都会向上层提供一个串流 (stream) 的接口,应用程序透过使用CreateFileReadFile这些标准的档案系统API对区块装置进行存取。

     

    当应用程序呼叫ReadFile对区块装置进行存取时,档案系统会先找到对应的逻辑区块,检查这些逻辑区块是否保存在缓存区 (buffer) 中。如果在的话,则可直接由缓存区中读取数据,否则就需要透过区块装置驱动程序对实际的区块装置进行读取操作了。

     

    当一个区块装置驱动程序加载系统中时,它会通知储存管理程序这一个加载事件,作为对此事件的响应。储存管理程序将负责查询与此区块装置驱动程序相关的注册信息,并通知分割区管理程序加载对应的装置。

     

    5.1.4 档案系统驱动程序的建立与加载

     

    Windows CE提供了一套模板式方法,使得开发人员可以轻松地开发一套新的档案系统。新开发的档案系统既可以充分利用储存装置的特性,也能够限制储存装置的档案操作。

     

    5.1.4.1 档案系统驱动程序的建立

     

    档案系统驱动程序为一个有一组输出函式被映射到Win32 API上的动态链接库。当应用程序呼叫Win32 API来操作档案时,如果发现要操作的档案位于某一档案系统的volume上,那么档案系统管理程序会将该系统呼叫,映像到该档案系统相对应的输出函式,藉由该档案系统驱动程序的输出函式来操作档案。

     

    Windows CE还提供了一个档案系统驱动程序的开发模板,开发人员只要提供各种所需的输出函式就可以了。通常,一个档案系统驱动程序应该提供所有的输出函式,但这并不是必要的。例如,如果开发人员所开发的档案系统驱动程序不允许使用者建立或删除目录,只要在档案系统驱动程序中不提供CreateDirectoryRemoveDirectory这两个输出函式即可。档案系统驱动程序没有提供的输出函式,将由档案系统管理程序自动填入stub function代替,一般stub function只会回传ERROR_NOT_SUPPORTED。因此,在前面的例子中,当应用程序尝试建立或删除目录时就会失败。

     

    除了提供Win32 API需映像到的输出函式,档案系统驱动程序还要提供FSD_MountDiskFSD_UnmountDisk这两个函式。当系统要安装或移除某个档案系统驱动程序时,装置管理程序会呼叫这两个函式。

     

    如果要开发一个名为MyFSD的档案系统驱动程序,那么该档案系统驱动程序的动态链接库必须命名为MyFSD.dll。该动态链接库中要包含了数据类型的定义、标头档案 (header file) 以及MyFSD所支持的输出函式。

     

    PVOLUMEPFILEPSEARCH这三种数据预设都是DWORD类型的,分别用于储存已载入的volume、档案和搜寻句柄的信息。然而,我们也可以在MyFSD.dll中对这三种数据类型重新进行定义,使它们包含我们所需要的信息。例如,我们可以将它们定义为指向某种结构的指针。需要注意的一点是,这种重新定义必须在纳入标头档案Fsdmgr.h之前进行,否则将使用预设的定义。

     

    编译生成MyFSD.dll的程序代码中必须包含Fsdmgr.h,该档案中包含了所有的输出函式原型宣告。前面已经讲过,在纳入Fsdmgr.h之前可以对PVOLUMEPFILEPSEARCH这三种数据类型重新定义。除此之外,我们还必须定义一个FSDAPI常数,此常数决定了档案系统名称、所有函式前缀以及最后生成的动态链接库名称。程序代码5.2说明了如何在MyFSD.dll中纳入Fsdmgr.h

     

    程序代码5.2 MyFSD.dll中纳入Fsdmgr.h

    // This defines a type that is a pointer to a structure containing

    // two DWORDs.

    typedef struct {

    DWORD first_field;

    DWORD second_field;

    } TWODWORDS, *PTWODWORDS;

     

    // In this example, only PSEARCH is redefined.

    // PFILE and PVOLUME will default to being defined as DWORDs.

    // You can define these types to be different types or the same type,  based on your FSD needs.

    #define PSEARCH PTWODWORDS

     

    // Now define the name of the file system.

    #define FSDAPI   MyFSD

    #include <fsdmgr.h>

     

    每个MyFSD.dll所提供的输出函式名称,都必须以档案系统驱动程序的名称为开头,在本例中为MyFSD_。不过也有特例,那就是FSD_MountDiskFSD_UnmountDisk这两个函式,不必使用档案系统驱动程序的名称作为前缀。档案系统管理程序会检查这两个函式是否存在,来决定一个档案系统驱动程序是否合法。程序代码5.3说明了如何在模块定义档案 (.def) 中,定义MyFSD.dll所提供的函式。

     

    程序代码5.3 定义MyFSD.dll所提供函式的模块定义档案内容

    LIBRARY                     MyFSD

    DESCRIPTION 'My File System for Windows CE'

    EXPORTS

    MyFSD_MountDisk

    MyFSD_UnmountDisk

    FSD_MountDisk=MyFSD_MountDisk

    FSD_UnmountDisk=MyFSD_UnmountDisk

    MyFSD_CreateDirectoryW

    MyFSD_RemoveDirectoryW

    MyFSD_GetFileAttributesW

    MyFSD_SetFileAttributesW

    MyFSD_DeleteFileW

    MyFSD_MoveFileW

    MyFSD_DeleteAndRenameFileW

    MyFSD_GetDiskFreeSpaceW

    MyFSD_Notify

    MyFSD_RegisterFileSystemFunction

    MyFSD_FindFirstFileW

    MyFSD_FindNextFileW

    MyFSD_FindClose

    MyFSD_CreateFileW

    MyFSD_ReadFile

    MyFSD_ReadFileWithSeek

    MyFSD_WriteFile

    MyFSD_WriteFileWithSeek

    MyFSD_SetFilePointer

    MyFSD_GetFileSize

    MyFSD_GetFileInformationByHandle

    MyFSD_FlushFileBuffers

    MyFSD_GetFileTime

    MyFSD_SetFileTime

    MyFSD_SetEndOfFile

    MyFSD_DeviceIOControl

    MyFSD_CloseFile

    MyFSD_CloseVolume

     

    这个我们假想的档案系统驱动程序MyFSD,提供了所有的档案系统函式,但是开发人员也可以在档案系统驱动程序中不提供某些输出函式。对于这些缺少的函式,档案系统管理程序将自动以回传ERROR_NOT_SUPPORTEDstub function代替。

     

    5.1.4.2 档案系统驱动程序的加载

     

    当安装已注明使用某档案系统的外围储存装置时,该档案系统驱动程序会被自动加载。当外围储存装置加载系统时,储存管理程序会从注册信息中,读取与其相关的注册信息,其中定义了将要使用的档案系统驱动程序。在此我们仍假设该档案系统驱动程序为MyFSD。外围储存装置加载系统后,储存管理程序会从装置管理程序接收到一个通知,其中含有该装置的装置名称及其GUID等信息。其中,GUID的作用非常简单,透过检查装置的GUID,就可以确定新发现的装置是否为区块装置,因为只有区块装置才会用到档案系统。之后储存管理程序会根据从注册信息中得到的配置信息,为该装置加载合适的分割区驱动程序,并为每个分割区加载对应的档案系统。储存管理程序在加载档案系统驱动程序时,会检查MyFSD是否含有FSD_MountDiskFSD_UnmountDisk这两个函式,只有找到了FSD_MountDiskFSD_UnmountDisk,才能说明这是一个合法的档案系统驱动程序,也才会进行后续的工作。

     

    之后,档案系统管理程序会呼叫MyFSD.dll的函式FSD_MountDiskFSD_MountDisk负责根据MBR (Master Boot Record) 中的DPT (Disk Partition Table),以及扩展分割区连结串行中的信息找到装置上所有的volume,并呼叫FSDMGR_RegisterVolume向档案系统管理程序注册每个volume。所有这些注册过的volume,就会以目录的形式显示出来,应用程序也可以透过档案系统函式对它们进行存取了,往后当使用者透过档案系统Win32 API对该volume上的档案进行存取时,档案系统管理程序就可以透过完整目录名称中的目录名称,找到对应的档案系统驱动程序了。与此类似,当档案系统驱动程序的输出函式MyFSD_UnmountDisk被呼叫时,装置上的所有volume会被注销。

     

    5.1.4.3 档案系统管理程序的作用

     

    档案系统管理程序的功能是由Fsdmgr.dll这个模块提供的,它负责管理和沟通系统中所有可安装档案系统。档案句柄的建立、volume的注册、所有必需函式的安装、档案系统Win32 API到可安装档案系统输出函式的映像,所有这些都是由档案系统管理程序完成的。当应用程序存取某个volume上的目录和档案时,档案系统管理程序会负责呼叫相对应的档案系统输出函式。

     

    应用程序是透过档案系统Win32 API存取可安装档案系统的。例如,当应用程序要在装置上建立一个目录时,它会呼叫CreateDirectory函式。之后,再由档案系统管理程序将建立目录的工作,交由该装置所使用的档案系统驱动程序来完成。

    在开发档案系统驱动程序时,可以使用档案系统管理程序提供的下列函式:

     

    • FSDMGR_CreateFileHandle
    • FSDMGR_CreateSearchHandle
    • FSDMGR_DeregisterVolume
    • FSDMGR_GetDiskInfo
    • FSDMGR_GetVolumeName
    • FSDMGR_ReadDisk
    • FSDMGR_ReadDiskEx
    • FSDMGR_RegisterVolume
    • FSDMGR_WriteDisk
    • FSDMGR_WriteDiskEx

     

    这些函式实作了档案系统驱动程序与操作系统和装置驱动程序的双重隔离。除了以上函式之外,Fsdmgr.dll也为 CreateFileFindFirstFile这些函式提供了对应的函式。这些函式的原型都是在Fsdmgr.h中定义。

     

    5.2 实际分析

     

    5.2.1 概述

     

    对于任何新加载系统的外围储存装置而言,首先要做的就是将其纳入系统的管理,然后才使用该装置。从底层区块装置驱动程序的加载,到分割区驱动程序的加载,以及档案系统驱动程序的加载,最后得以将区块装置及其使用的各种驱动程序,纳入装置管理程序和储存管理程序的管理当中,这是一个由下而上的过程。而透过统一的档案系统Win32 API接口到对应的档案系统驱动程序,再经由分割区驱动程序或直接交给装置驱动程序,完成最后的档案操作,这则是一个由上而下的过程。前者就是将外围储存装置纳入系统管理的过程,后者则是对外围储存装置的使用过程,本段落的重点是分析将外围储存装置纳入系统管理的过程。

     

    当系统发现一个新的装置时,首先,装置管理程序会负责为其加载相对应的装置驱动程序,并在装置管理程序中建立相对应的数据结构,记录与该装置相关的各种信息。之后,如果发现新加载的装置是区块装置的话,还要由储存管理程序为其加载档案系统驱动程序。这时,装置管理程序会将一些重要的信息告知储存管理程序,由它来完成后面的工作。储存管理程序首先会根据注册信息中的配置信息,为区块装置加载分割区驱动程序,分割区驱动程序的主要作用就是定位区块装置上的分割区。储存管理程序会为装置加载所需的档案系统驱动程序。在这一过程中,储存管理程序会为装置上的volume,注册一级目录的入口项,将来使用者透过完整路径名称对档案进行存取时,就是由其中的一级目录名找到相对应的档案系统的。这一过程大致如下:

     

    1.装置管理程序负责加载区块装置的驱动程序,

    2.由该装置发出一个通知,告知储存管理程序该装置的装置名称及其GUID等信息,

    3.储存管理程序根据配置信息,为该装置加载分割区驱动程序,

    4.储存管理程序行举该装置上的所有分割区,

    5.储存管理程序为每个分割区加载档案系统驱动程序。

     

    下面我们先对这一过程中所使用到的数据结构做介绍,然后再详细讲说上述储存管理程序分层结构的建立过程,以Ramdisk的安装过程为例,看一看在实际的程序代码中这一过程是如何实作的。

     

    5.2.2 重要的数据结构

     

    每当系统中出现新的外围储存装置时,储存管理程序都会为其建立一系列的数据结构,用来维护与该装置有关的信息。这些数据结构包括CStoreDriverStateDSK等等,它们都是用来描述一个装置的。对于装置上的分割区,储存管理程序中也有一套用于维护分割区信息的数据结构,包括CPartitionPartStateVOL等等。

     

    CStore是一个类别,它描述了一个外围储存装置的一些基本信息,由储存管理程序负责建立,其中一些成员变量的含义如下:

     

    • m_szDeviceName:装置名称,由装置管理程序告知;
    • m_DeviceGuid:装置的GUID,也是由装置管理程序告知的;
    • m_szRootRegKey:此装置的配置信息在注册信息中的位置;
    • m_pPartDriver:装置所使用的分割区驱动程序;
    • m_pPartitionList:装置上的CPartition分割区连结串行;
    • m_dwStoreId:由分割区管理程序维护的与此装置对应的DriverState结构的指针;
    • m_pNextStore:指向g_pStoreRoot队列中下一个CStore

     

    DriverState由分割区管理程序负责建立并维护,记录了一些与分割区管理程序有关的装置细节信息,其中一些成员变量的含义如下:

     

    • pPartState:装置上的PartState分割区连结串行;
    • pSearchState:指向SearchState结构组成的连结串行;
    • diskInfo:装置参数,包括CHS值、每个扇区所含的字节数等等;
    • snExtPartSector:装置上扩展分割区的起始扇区;
    • snExtPartEndSec:扩展分割区的结束扇区。

     

    CPartition也是一个类别,用于描述装置上的分割区,由储存管理程序负责建立并维护,其中一些成员变量的含义如下:

     

    • m_dwPartitionId:由分割区管理程序维护,与此分割区对应的PartState的句柄;
    • m_dwStoreId:与此分割区所在的装置对应的DriverState结构的指针;
    • m_szPartitionName:分割区的名称;
    • m_szFileSys:此分割区所使用的档案系统的名称;
    • m_pDsk:指向由档案系统驱动程序负责维护,与此装置相关的DSK结构;
    • m_hFSD:档案系统驱动程序对应的动态链接库的句柄;
    • m_pNextPartition:指向同一装置上的下一个CPartition物件。

     

    PartState是分割区管理程序为维护分割区信息所使用的数据结构,与DriverState密切相关,其中一些成员变量的含义如下:

     

    • cPartName:分割区的名称;
    • snStartSector:该分割区的起始扇区号;
    • snNumSectors:此分割区所占用的扇区数,即分割区大小;
    • NextPartState:指向同一装置上的下一个PartState结构;
    • pState:指向此分割区所属装置的DriverState结构。

     

    CPartDriver是一个类别,用于描述一个分割区驱动程序,其成员变量m_hPartDriver记录了该分割区驱动程序对应的动态链接库的句柄,其它的成员变量均为函式指针,一一对应于分割区驱动程序的输出函式。以上这些数据结构的关系如图5.4所示:

     

    5.4 CStore, DriverState, CPartition, PartState, DriverState数据结构关系图

     

    下面再来看一下由档案系统管理程序建立并维护的FSDDSKVOL这三个数据结构。

     

    FSD结构用来描述一个档案系统驱动程序或是一个过滤器,其中一些成员变量的含义如下:

     

    • hFSD:档案系统驱动程序或过滤器对应的动态链接库句柄;
    • pfnMountDisk:指向档案系统驱动程序函式FSD_MountDisk的指标;
    • pfnUnmountDisk:指向档案系统驱动程序函式FSD_UnmountDisk的指标;
    • pfnHookVolume:指向过滤器函式FSD_HookVolume的指标;
    • pfnUnhookVolume:指向过滤器函式FSD_ UnhookVolume的指标;
    • szFileSysName:档案系统或过滤器的名称;
    • szRegKey:档案系统或过滤器的相关信息在注册信息中的位置;
    • wsFSD:档案系统驱动程序函式的前缀;
    • apfnAFSapfnFileapfnFind:记录了所有输出函式的地址。

     

    每个档案系统驱动程序和过滤器除了对应一个FSD结构以外,还会对应一个DSK结构,其中一些成员变量的含义如下:

     

    • pVol:指向由VOL结构双向连结串行的标头 (header)
    • pFSD:指向对应的FSD结构;
    • fdi:装置参数信息,包括CHS值、每个扇区所含的字节数等等;
    • pDeviceIoControl:指向DeviceIoControl函式的指标。

     

    VOL结构用于描述一个volume,也就是一个分割区,其中一些成员变量的含义如下:

     

    • pDsk:指向此volume所使用的最上层档案系统,或过滤器所对应的DSK结构;
    • dwVolData:由FSDMGR_RegisterVolume提供的某一档案系统驱动程序中使用的VOLUME结构的指针;
    • iAFS:由FSDMGR_RegisterVolume获得的AFS号。

     

    5.2.3 相关程序代码的分析

     

    首先看一下Windows CE与档案系统相关的原始程序代码组织。在下面的说明中,%_WINCEROOT% 代表Windows CE所安装的根目录。

     

    • %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/FSD:三种档案系统驱动程序的原始程序代码,包括FATRELFSD以及UDFS
    • %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE:储存管理程序的原始程序代码。
    • %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE/DOSPARTMSPart.dll的原始程序代码。
    • %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE/FSDMAIN:档案系统管理程序的原始程序代码。
    • %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE/FSDSERV:档案系统管理程序使用的一些service routine及其提供的API
    • %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE/STOREAPI:储存管理程序提供的API的封装。
    • %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE/STOREMGR:储存管理程序的启动程序代码,CStoreCPartition等类的原始程序代码。

     

    另外,与Ramdisk相关的程序代码位于以下两个目录:

     

    • %_WINCEROOT%/PUBLIC/COMMON/OAK/DRIVERS/BLOCK/RAMDISK/DRIVERBuild此目录下的原始程序代码可以得到Ramdisk的驱动程序ramdisk.dll
    • %_WINCEROOT%/PUBLIC/COMMON/OAK/DRIVERS/BLOCK/RAMDISK/LOADERBuild此目录下的原始程序代码可以得到Ramdisk的安装程序ceramdrv.exe,该程序用于启动Ramdisk

     

    下面我们就以Ramdisk的安装过程为例,说明当系统中出现新的外围储存装置时,其装置驱动程序、分割区驱动程序以及档案系统驱动程序是如何被加载系统的,以及系统是如何透过装置管理程序和储存管理程序来管理新发现的装置。

     

    在系统的启动过程中会自动加载两个模块,一是filesys.exe,与对象空间相关的RAM-basedROM-based 档案系统的功能就是由该模块实作的;另一个是fsdmgr.dll,当新的外围储存装置加载系统时,由它负责加载FAT这种可安装的档案系统。系统在加载fsdmgr.dll这一模块时会呼叫初始化函式Init,而在Init函式的InitStorageManager会开始建立储存管理程序分层结构。在InitStorageManager中,首先会呼叫CreateAPISetRegisterAPISet注册储存管理程序提供的API,以负责建立g_hPNPUpdateEvent事件。接下来呼叫CreateMsgQueue函式建立了一个讯息队列g_hPNPQueue,由于msgopts.bReadAccess的值被设为TRUE,因此对Storage Manager而言只能对该讯息队列 (message queue) 进行读取的操作。之后是启动用于支持随插即用的PNPThread。在此thread中,首先由GetProcAddress得到RequestDeviceNotificationsStopDeviceNotifications这两个函式在coredll.dll中的入口地址,然后透过呼叫RequestDeviceNotifications,告知装置管理程序将发现新的区块装置的通告写入讯息队列g_hPNPQueue中。这些准备工作完成后,PNPThread将进入一个无穷循环,每隔一定时间对g_hPNPQueueg_hPNPUpdateEvent进行一次轮询。如果装置管理程序发现新装置,会透过WriteMsgQueue向讯息队列中写入含有新装置信息 (GUID,装置名称等) DEVDETAIL结构。这时,在PNPThread中就可以透过ReadMsgQueue将此信息读出,并呼叫MountStore来进行接下来的加载工作了。程序代码5.4InitStorageManagerPNPThread函式的原始程序代码。

     

    程序代码5.4 InitStorageManagerPNPThread函式原始程序代码

    ***%_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE/STOREMGR/storemain.cpp Line 608 ***

    BOOL InitStorageManager ()

    {

    DWORD dwThreadId = 0;

    int iAFS = INVALID_AFS;

    MSGQUEUEOPTIONS msgopts;

     

    iAFS = RegisterAFSName (L"StoreMgr") ;

    if (iAFS != INVALID_AFS && GetLastError () == 0)  {

    g_hSTRMGRApi = CreateAPISet("PFSD", ARRAY_SIZE(apfnSTGMGRAPIs),
    (const PFNVOID*) apfnSTGMGRAPIs, asigSTGMGRAPIs);

    if (!RegisterAFSEx(iAFS, g_hSTRMGRApi, (DWORD)1, AFS_VERSION,
    AFS_FLAG_HIDDEN))  {

    DEBUGMSGW(ZONE_INIT, (DBGTEXTW("STOREMGR:InitStoreMgr failed registering secondary volume/r/n")));

    DeregisterAFSName(iAFS);

    goto Fail;

    }

    } else {

    goto Fail;

    }

     

    // 建立API set

    g_hFindStoreApi = CreateAPISet("FSTG", ARRAY_SIZE(apfnFindStoreAPIs),
    (const PFNVOID *) apfnFindStoreAPIs, asigFindStoreAPIs);

    g_hFindPartApi = CreateAPISet("FPRT", ARRAY_SIZE (apfnFindPartAPIs),
    (const PFNVOID *) apfnFindPartAPIs, asigFindPartAPIs);

    g_hStoreApi = CreateAPISet("STRG", ARRAY_SIZE(apfnSTGAPIs),
    (const PFNVOID *) apfnSTGAPIs, asigSTGAPIs);

    g_hPNPUpdateEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

     

    // 建立讯息队列,储存管理程序只能对该队列进行读取操作

    msgopts.dwSize = sizeof(MSGQUEUEOPTIONS);

    msgopts.dwFlags = 0;

    msgopts.dwMaxMessages = 0; //?

    msgopts.cbMaxMessage = sizeof(g_pPNPBuf);

    msgopts.bReadAccess = TRUE;

     

    g_hPNPQueue = CreateMsgQueue(NULL, &msgopts);

     

    if (!g_hFindPartApi || !g_hFindStoreApi || !g_hStoreApi || !g_hPNPUpdateEvent
    || !g_hPNPQueue)

    goto Fail;

     

    // 注册API set

    RegisterAPISet (g_hFindStoreApi, HT_FIND | REGISTER_APISET_TYPE);

    RegisterAPISet (g_hFindPartApi, HT_FIND | REGISTER_APISET_TYPE);

    RegisterAPISet (g_hStoreApi, HT_FILE | REGISTER_APISET_TYPE);

     

    InitializeCriticalSection (&g_csStoreMgr);

     

    // 启动PNP执行绪,随时准备加载新发现的外围储存装置

    if (g_hPNPThread = CreateThread(NULL, 0, PNPThread, NULL, 0, &dwThreadId))  {

    } else {

    goto Fail;

    }

    return TRUE;

    Fail:

    CloseAllGlobalHandles();

    return FALSE;

    }

     

    *** %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE/STOREMGR/storemain.cpp
    Line 493 ***

    DWORD PNPThread (LPVOID lParam)

    {

    extern const TCHAR *g_szSTORAGE_PATH; // = L"System//StorageManager";

    extern const TCHAR *g_szReloadTimeOut; // = L"UnloadDelay";

    DWORD dwFlags, dwSize;

    //   HKEY hDevKey;

    HANDLE hReg;

    DEVDETAIL * pd =  (DEVDETAIL *) g_pPNPBuf;

    GUID guid = {0};

    HANDLE pHandles[2];

    TCHAR szGuid[MAX_PATH];

    HMODULE hCoreDll;

    DWORD dwTimeOut = INFINITE, dwTimeOutReset = DEFAULT_TIMEOUT_RESET;

    PSTOPDEVICENOT pStopDeviceNotification = NULL;

    PREQUESTDEVICENOT pRequestDeviceNotification = NULL;

     

    pHandles[0] = g_hPNPQueue;

    pHandles[1] = g_hPNPUpdateEvent;

     

    HKEY hKey;

    // 从注册信息[HKEY_LOCAL_MACHINE/System/StorageManager]处读取PNPUnloadDelay的值

    if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, g_szSTORAGE_PATH, 0, 0,
    &hKey) )  {

    if ( !FsdGetRegistryValue(hKey, g_szReloadTimeOut, &dwTimeOutReset))  {

    dwTimeOutReset = DEFAULT_TIMEOUT_RESET;

    }

    }

    DEBUGMSG(ZONE_INIT, (L"STOREMGR: Using PNP unload delay of %ld/r/n",
    dwTimeOutReset));

     

    AutoLoadFileSystems();

     

    // coredll.dll中得到RequestDeviceNotificationsStopDeviceNotifications两个函式的地址

    hCoreDll = (HMODULE) LoadLibrary(L"coredll.dll");

    if (hCoreDll)  {

    pRequestDeviceNotification = (PREQUESTDEVICENOT) GetProcAddress(hCoreDll,
    L"RequestDeviceNotifications");

    pStopDeviceNotification = (PSTOPDEVICENOT) GetProcAddress(hCoreDll,
    L"StopDeviceNotifications");

    }

    FreeLibrary (hCoreDll);

     

    if (pRequestDeviceNotification && pStopDeviceNotification)  {

    DEBUGMSG (ZONE_INIT, (L"STOREMGR: PNPThread Created/r/n"));

    while ( !IsAPIReady (SH_DEVMGR_APIS))  {

    Sleep(1000);

    }

    // 告知系统将新发现的区块装置的信息写入刚刚建立的讯息队列g_hPNPQueue

    hReg = pRequestDeviceNotification (&BLOCK_DRIVER_GUID, g_hPNPQueue, TRUE);

     

    while (TRUE)  {

    DWORD dwWaitCode;

    dwWaitCode = WaitForMultipleObjects (2, pHandles, FALSE, dwTimeOut);

    if (dwWaitCode == WAIT_OBJECT_0)  {

    // 讯息队列有新的讯息写入,读取有关新装置的信息

    if (ReadMsgQueue (g_hPNPQueue, pd, sizeof(g_pPNPBuf), &dwSize, INFINITE,
    &dwFlags))  {

    FsdStringFromGuid (&pd->guidDevClass, szGuid);

    DEBUGMSG(ZONE_INIT,(L"STOREMGR: Got a plug and play event %s
    Class (%s)  Attached=%s!!!/r/n", pd->szName, szGuid, pd->fAttached ? L"TRUE":L"FALSE"));

    // 检查新发现的装置是否为区块装置

    if(memcmp(&pd->guidDevClass, &BLOCK_DRIVER_GUID, sizeof(GUID)) == 0)
    {

    // 如果新的区块装置的状态为Attached则继续后面的加载工作

    if (pd->fAttached) {

    // 将装置名称及其GUID作为参数传给MountStore

    MountStore ( pd->szName, pd->guidDevClass);

    …… // 后面略去

     

           对于Ramdisk来说,当我们执行安装程序ceramdrv.exe后,系统便会发现这块虚拟的外围储存装置,且装置管理程序加载区块装置驱动程序ramdisk.dllRamdisk所使用的装置驱动程序等配置信息,是其由安装程序ceramdrv.exe写入注册信息的 (见图5.5)。其中dll的键值 (key value) 就是Ramdisk的装置驱动程序,而IClass的键值{A4E7EDDA-E575-4252-9D6B-4195D48BB865}即为Windows CE中标识区块装置所用的GUID,它将作为参数之一传给MountStore。另外还有很重要的一个键是Profile,它的键值说明了Ramdisk的配置信息所在的位置,在后面我们还会提到。

     

    5.5 Ramdisk的注册信息

     

    另外,在成功为其加载区块装置驱动程序之后,装置管理程序会将Ramdisk作为已经启动的装置填入注册信息 (见图5.6),并透过WriteMsgQueueRamdisk的装置名称及其GUID等信息写入讯息队列。可以看到,与Ramdisk有关的装置基本信息位于[HKEY_LOCAL_MACHINE/Drivers/Builtin/Ramdisk],也就是图5.6所示的注册信息位置;另外,Name的键值即为Ramdisk的装置名称,也就是说DSK1: 将作为参数之一传给MountStore

     

    5.6 启动装置的注册信息

     

    进入MountStore之后,首先由以g_pStoreRoot为标头的连结串行,搜寻是否已经存在具有相同装置名称的store。如果发现了具有相同装置名称的相符项,并且该store的标志位STORE_FLAG_DETACHED处于复位状态 (说明装置可用) ,则表明该装置已经加载到系统中,不再做其它动作。相反,如果在连结串行中未找到相符项,或虽然找到但是该store的标志位STORE_FLAG_DETACHED处于置位状态 (说明装置不可用) ,则继续接下来的工作。以装置管理程序告知的装置名称及其GUID建立一个CStore对象,然后呼叫CStore::MountStore (BOOL bMount) 完成实际的加载工作。如果以g_pStoreRoot为标头的连结串行中存在标志位STORE_FLAG_DETACHED置位的store,则参数bMountFALSE,否则为TRUE

     

    CStore::MountStore函式中,首先呼叫OpenDisk。由于从装置管理程序那里我们已经知道了装置名称,因此可以透过CreateFileW函式建立装置句柄,之后还可以由DeviceIoControl函式得到有关该装置的详细信息。在呼叫DeviceIoControl时如果dwIoControlCode值为DISK_IOCTL_GETINFO的话,那么就可以得到包含CHS等装置参数的DISK_INFO结构,并将其存在Cstore的成员变量m_si中;如果dwIoControlCode值为IOCTL_DISK_DEVICE_INFO,那么可以得到有关该装置的一些额外信息,这些信息以STORAGEDEVICEINFO结构表示,并且存在Cstore的成员变量m_sdi中。STORAGEDEVICEINFO结构中很重要的一项信息,就是该装置的Profile在注册信息中的位置,这决定了我们将从哪里得到有关该装置的配置信息。之后,我们就可以呼叫GetStoreSettings,由注册信息的Profile中读出该装置的配置信息。例如,是否支持AUTOMOUNTAUTOFORMAT以及AUTOPART、该装置所使用的预设档案系统、为该装置加载哪个partition driver等等。

     

    得到这些配置信息后,首先建立一个CPartDriver对象,并透过LoadPartitionDriver,加载我们从注册信息中得知的该装置所使用的分割区驱动程序。另外LoadPartitionDriver还会完成刚加载的分割区驱动程序中,输出函式到CPartDriver中函式指标的映射。如果在注册信息中未提供有关分割区驱动程序的加载信息,则系统使用预设的分割区驱动程序 (MSPart.dll) 。接下来呼叫的OpenStore函式会映像到刚刚加载的分割区驱动程序的PD_OpenStore函式,它会根据由装置启动扇区读入的磁盘分割区表,建立并维护分割区管理程序将用到的DriverState (代表装置) PartState (代表装置上的分割区) SearchState (用于go through装置上的分割区) 等数据结构。该函式回传时,m_dwStoreId值为与装置相对应的DriverState结构的指针。之后的GetStoreInfo被映像为PD_GetStoreInfo,由于刚刚得到的m_dwStoreId是指向DriverState结构的指针,因此可以从分割区管理程序保存的数据结构中读出我们这里所需的信息,并把这些信息组织在一个PD_STOREINFO结构中。接下来检查m_dwStoreId所指向的装置是否已经格式化,如果该装置尚未被格式化,并且前面由注册信息读出的配置信息表明该装置支持AUTOFORMAT,则在此透过呼叫FormatStore函式对该装置进行自动格式化。

     

    接下来的GetPartitionCount透过PD_FindPartitionStartPD_FindPartitionNext以及PD_FindPartitionClose这三个函式的扫视该装置的分割区,由此统计出装置上的分割区个数。如果统计结果为零,说明该装置尚未分割。这时如果该对应的注册信息表明该装置支持AUTOPART,则在此以装置上最大储存空间自动建立并格式化为一个分割区,这是透过呼叫CreatePartitionFormatPartition这两个函式完成的。最后一步是呼叫LoadExistingPartitions,它会像GetPartitionCount一样透过所加载的分割区驱动程序函式PD_FindPartitionStartPD_FindPartitionNext以及PD_FindPartitionClose go through装置的所有分割区,但这次go through的目的不是为了统计分割区个数,而是呼叫LoadPartition依次载入每个分割区。下述的程序代码5.5依序列出CStore::MountStore CStore::OpenDiskGetStoreSettings的原始程序代码。

     

    程序代码5.5 CStore::MountStoreCStore::OpenDiskGetStoreSettings原始程序代码

    ***  %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE/STOREMGR/store.cpp 
    Line 322  ***

    DWORD CStore::MountStore (BOOL bMount)

    {

    DWORD dwError = ERROR_GEN_FAILURE;

    // 由装置名称DSK1:建立Ramdisk的装置句柄,并得到装置的相关信息

    if (ERROR_SUCCESS ==  (dwError = OpenDisk()))

    // 得到Ramdisk的配置信息

    if (GetStoreSettings ())  {

    m_pPartDriver = new CPartDriver ();

    // 加载预设的分割区驱动程序mspart.dll

    if(ERROR_SUCCESS == (dwError = m_pPartDriver->LoadPartitionDriver
    (m_szPartDriver)))  {

    // mspart.dllPD_OpenStoreRamdisk在分割区管理程序建立相对应的数据结构

    if(ERROR_SUCCESS==(dwError=m_pPartDriver->OpenStore (m_hDisk,
    &m_dwStoreId)))  {

    DEBUGMSG (1, (L"Opened the store hStore=%08X/r/n", m_dwStoreId));

    m_si.cbSize = sizeof (PD_STOREINFO);

    m_pPartDriver->GetStoreInfo ( m_dwStoreId, &m_si);

    DEBUGMSG(1, (L"NumSec=%ld BytesPerSec=%ld FreeSec=%ld BiggestCreatable=%ld/r/n", (DWORD)m_si.snNumSectors,
    (DWORD)m_si.dwBytesPerSector, (DWORD)m_si.snFreeSectors,
    (DWORD) m_si.snBiggestPartCreatable));

    // 检查Ramdisk是否已经进行过格式化操作,如果尚未进行过格式化

    // 并且Ramdisk支持AUTOFORMAT,则对其进行自动格式化操作

    if (ERROR_SUCCESS != m_pPartDriver->IsStoreFormatted (m_dwStoreId))

    { // TODO: Look for ERROR_BAD_FORMAT

    if (m_dwFlags & STORE_ATTRIBUTE_AUTOFORMAT)  {

    // TODO: Check for failure

    m_pPartDriver->FormatStore (m_dwStoreId);

    m_pPartDriver->GetStoreInfo ( m_dwStoreId, &m_si);

    }

    }

    // 透过分割区驱动程序提供的PD_FindPartitionStart
    // PD_FindPartitionNextPD_FindPartitionClose三个函式统计分割区个数
    // GetPartitionCount ();
    // 如果统计结果为零,说明装置尚未建立分割区。这时根据注册信息中
    // AutoPart的键值决定是否自动分割区

    if (!m_dwPartCount)  {

    if (m_dwFlags & STORE_ATTRIBUTE_AUTOPART)  {

    PD_STOREINFO si;

    si.cbSize = sizeof (PD_STOREINFO);

    m_pPartDriver->GetStoreInfo (m_dwStoreId, &si);

    if ((ERROR_SUCCESS==m_pPartDriver->CreatePartition(m_dwStoreId,L"PART00", 0, si.snBiggestPartCreatable, TRUE))
    &&(ERROR_SUCCESS==m_pPartDriver->FormatPartition(m_dwStoreId, L"PART00", 0, TRUE)))  {

    m_pPartDriver->GetStoreInfo ( m_dwStoreId, &m_si);

    DEBUGMSG(1, (L"NumSec=%ld BytesPerSec=%ld FreeSec=%ldBiggestCreatable=%ld/r/n", (DWORD) m_si.snNumSectors,
    (DWORD)m_si.dwBytesPerSector, (DWORD) m_si.snFreeSectors,(DWORD) m_si.snBiggestPartCreatable));

    m_dwPartCount = 1;

    }

    }

    }

    // 呼叫LoadExistingPartitions完成后面的工作

    LoadExistingPartitions (bMount);

    m_dwFlags |= STORE_FLAG_MOUNTED;

    }

    }

    }

    }

    return dwError;

    }

     

    ***  %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE/STOREMGR/store.cpp 
    Line 178  ***

    DWORD CStore::OpenDisk ()

    {

    DWORD dwError = ERROR_SUCCESS;

    // 根据装置名称建立装置句柄,首先尝试以可读可写方式建立

    m_hDisk = CreateFileW(m_szDeviceName, GENERIC_READ | GENERIC_WRITE,  0, NULL,
    OPEN_EXISTING, 0, NULL);

     

    if (m_hDisk == INVALID_HANDLE_VALUE)  {

    dwError = GetLastError ();

    // 如果尝试失败则以只读方式建立

    if (dwError == ERROR_ACCESS_DENIED)  {

    dwError = ERROR_SUCCESS;

    m_hDisk = CreateFileW (m_szDeviceName, GENERIC_READ, FILE_SHARE_READ,
    NULL, OPEN_EXISTING, 0, NULL);

    if (m_hDisk != INVALID_HANDLE_VALUE)  {

    m_dwFlags |= STORE_ATTRIBUTE_READONLY;

    } else {

    dwError = GetLastError();

    }

    }

    }

    if (m_hDisk != INVALID_HANDLE_VALUE)  {

    DWORD dwRet;

    // 得到含有CHS值的DISK_INFO信息

    if (!::DeviceIoControl(m_hDisk, DISK_IOCTL_GETINFO, &m_si, sizeof(DISK_INFO),NULL,
    0, &dwRet, NULL))  {

    dwError = GetLastError ();

    if ((dwError == ERROR_BAD_COMMAND) ||
    (dwError == ERROR_NOT_SUPPORTED))  {

    memset ( &m_si, 0, sizeof (DISK_INFO));

    dwError = ERROR_SUCCESS;

    }

    }

    if (dwError == ERROR_SUCCESS)  {

    // 得到STORAGEDEVICEINFO信息,其中m_sdi. szProfile指明了装置的

    // 配置信息在注册信息中的位置

    if (!::DeviceIoControl(m_hDisk, IOCTL_DISK_DEVICE_INFO, &m_sdi,
    sizeof(STORAGEDEVICEINFO), NULL, 0, &dwRet, NULL))  {

    DEBUGMSG (ZONE_INIT, (L"FSDMGR!CStore::OpenDisk (0x%08X)  call to IOCTL_DISK_DEVICE_INFO failed..filling info/r/n", this));

    m_sdi.dwDeviceClass = STORAGE_DEVICE_CLASS_BLOCK;

    m_sdi.dwDeviceFlags = STORAGE_DEVICE_FLAG_READWRITE;

    m_sdi.dwDeviceType = STORAGE_DEVICE_TYPE_UNKNOWN;

    wcscpy ( m_sdi.szProfile, L"Default");

    }

    }

    DEBUGMSG ( ZONE_INIT,  (L"FSDMGR!CStore::OpenDisk (0x%08X)  DeviceInfo Class(0x%08X)  Flags (0x%08X)  Type (0x%08X)  Profile (%s) /r/n", this, m_sdi.dwDeviceClass,m_sdi.dwDeviceFlags, m_sdi.dwDeviceType, m_sdi.szProfile));

    }

    return dwError;

    }

     

    ***  %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE/STOREMGR/store.cpp 
    Line 121  ***

    BOOL CStore::GetStoreSettings ()

    {

    HKEY hKeyStorage=NULL, hKeyProfile = NULL;

    BOOL bRet = FALSE;

    // hKeyStorage值设为[HKEY_LOCAL_MACHINE/System/StorageManager/Profiles]

    if (ERROR_SUCCESS == RegOpenKeyEx (HKEY_LOCAL_MACHINE, g_szPROFILE_PATH, 0,
    0, &hKeyStorage))  {

    DUMPREGKEY(ZONE_INIT, g_szPROFILE_PATH, hKeyStorage);

    // hKeyProfile值设为[HKEY_LOCAL_MACHINE/System/StorageManager/Profiles/RamDisk]

    wsprintf (m_szRootRegKey, L"%s//%s", g_szPROFILE_PATH, m_sdi.szProfile);

    if (ERROR_SUCCESS != RegOpenKeyEx ( HKEY_LOCAL_MACHINE, m_szRootRegKey,
    0, 0, &hKeyProfile))  {

    hKeyProfile = NULL;

    } else {

    DUMPREGKEY (ZONE_INIT, m_sdi.szProfile, hKeyProfile);

    }

    // 根据注册信息中AUTOMOUNTAUTOFORMATAUTOPART三者的值设定m_dwFlags

    if (!hKeyProfile || !FsdLoadFlag (hKeyProfile, g_szAUTO_MOUNT_STRING, &m_dwFlags,
    STORE_ATTRIBUTE_AUTOMOUNT))

    if (!FsdLoadFlag (hKeyStorage, g_szAUTO_MOUNT_STRING, &m_dwFlags,
    STORE_ATTRIBUTE_AUTOMOUNT))

    m_dwFlags |= STORE_ATTRIBUTE_AUTOMOUNT;

    if (!hKeyProfile||!FsdLoadFlag (hKeyProfile,g_szAUTO_FORMAT_STRING, &m_dwFlags,
    STORE_ATTRIBUTE_AUTOFORMAT))

    FsdLoadFlag (hKeyStorage, g_szAUTO_FORMAT_STRING, &m_dwFlags,
    STORE_ATTRIBUTE_AUTOFORMAT);

    if (!hKeyProfile||!FsdLoadFlag (hKeyProfile, g_szAUTO_PART_STRING, &m_dwFlags,
    STORE_ATTRIBUTE_AUTOPART))

    FsdLoadFlag (hKeyStorage,g_szAUTO_PART_STRING, &m_dwFlags,
    STORE_ATTRIBUTE_AUTOPART);

    // 根据注册信息得到Ramdisk所使用的档案系统驱动程序,最后使用的一级目录名

    // 称以及储存装置的描述性名称

    if (!hKeyProfile||!FsdGetRegistryString (hKeyProfile,g_szFILE_SYSTEM_STRING,
    m_szDefaultFileSystem, sizeof (m_szDefaultFileSystem)))

    if (!FsdGetRegistryString(hKeyStorage,g_szFILE_SYSTEM_STRING, m_szDefaultFileSystem,
    sizeof (m_szDefaultFileSystem)))

    wcscpy (m_szDefaultFileSystem, g_szDEFAULT_FILESYSTEM);

    if ( !hKeyProfile || !FsdGetRegistryString (hKeyProfile, g_szFOLDER_NAME_STRING,
    m_szFolderName, sizeof(m_szFolderName)))

    if ( !FsdGetRegistryString (hKeyStorage, g_szFOLDER_NAME_STRING, m_szFolderName,
    sizeof(m_szFolderName)))

    wcscpy (m_szFolderName, g_szDEFAULT_FOLDER_NAME);

    if ( !hKeyProfile || !FsdGetRegistryString(hKeyProfile, g_szSTORE_NAME_STRING,
    m_szStoreName, sizeof(m_szStoreName)))

    if ( !FsdGetRegistryString (hKeyStorage, g_szSTORE_NAME_STRING, m_szStoreName,
    sizeof(m_szStoreName)))

    wcscpy (m_szStoreName, g_szDEFAULT_STORAGE_NAME);

    }

    // 从注册信息中读取Ramdisk所使用的分割区驱动程序名称

    if ( !GetPartitionDriver (hKeyStorage, hKeyProfile))

    goto ExitFalse;

    bRet = TRUE;

    ExitFalse:

    if (hKeyStorage)  {

    RegCloseKey (hKeyStorage);

    }

    if (hKeyProfile)  {

    RegCloseKey (hKeyProfile);

    }

    return bRet;

    }

     

    由图5.5可知,Profile的键值为RamDisk,这个值记载了Ramdisk的配置信息在注册信息中的位置,也就是[HKEY_LOCAL_MACHINE/System
    /StorageManager/Profiles/RamDisk],此处所含的注册信息信息如图5.7所示。

     

    5.7 Ramdisk的注册信息

     

    由图5.7可知,Ramdisk支持AutoFormatAutoPartAutoMount三种操作,使用的档案系统驱动程序为FATFS,在后面注册时使用的一级目录名为Storage Card。另外对于此处没有给出的一些信息则要用到系统的预设配置了,这些预设配置信息位于[HKEY_LOCAL_MACHINE/System/StorageManager/Profiles],见图5.8。这里Ramdisk用到的唯一预设配置就是分割区驱动程序mspart.dll

     

    5.8 Ramdisk配置信息

     

    下面再来看看LoadPartition。首先会呼叫OpenPartition,透过该函式得到由分割区管理程序维护,并代表该分割区的PartState结构的指针。在这之后,建立一个CPartition对象并将其加入到CStorem_pPartitionList连结串行中,然后呼叫LoadPartition得到有关该分割区的具体信息 (汇总为一个PD_PARTINFO结构) 。之后是一个判断语句,如果该store代表一个新发现的装置,并且前面由注册信息读出的配置信息表明该装置支持AUTOMOUNT,则呼叫MountPartition。其参数m_szRootRegKey为该装置的Profile在注册信息中的位置,另一个参数则是预设使用的档案系统名称。在函式MountPartition中,首先由注册信息读出有关分割区的Mount选项以及使用的档案系统等信息,然后加载对应的档案系统驱动程序的动态链接库。最后呼叫InitEx。接下来的工作就交由档案系统管理程序来完成了。程序代码5.6CStore::LoadPartitionCPartition::MountPartition的原始程序代码。

     

    程序代码5.6 CStore::LoadPartitionCPartition::MountPartition原始程序代码

    ***  %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE/STOREMGR/store.cpp 
    Line 291  ***

    BOOL CStore::LoadPartition (LPCTSTR szPartitionName, BOOL bMount)

    {

    DWORD dwPartitionId;

    // 得到由分割区管理程序负责维护的PartState结构的指针

    if (ERROR_SUCCESS == m_pPartDriver->OpenPartition (m_dwStoreId,szPartitionName,
    dwPartitionId))  {

    // 建立CPartition对象并将其加入到CStorem_pPartitionList连结串行中

    CPartition *pPartition = new CPartition(m_dwStoreId, dwPartitionId, m_pPartDriver,
    m_szFolderName);

    AddPartition (pPartition);

    // 得到有关该分割区的具体信息

    pPartition->LoadPartition (szPartitionName);

    // 如果的配置信息表明该装置支持AUTOMOUNT,则呼叫CPartition的成员函式MountPartition
    // 完成后面的工作
    if (bMount && (m_dwFlags & STORE_ATTRIBUTE_AUTOMOUNT))  {

    if (pPartition->MountPartition (m_szRootRegKey, m_szDefaultFileSystem))  {

    m_dwMountCount++;

    }

    }

    return TRUE;

    }

    return FALSE;

    }

     

    ***  %_WINCEROOT%/PRIVATE/WINCEOS/COREOS/STORAGE/STOREMGR/store.cpp 
    Line 291  ***

    BOOL CPartition::MountPartition (LPCTSTR szRootRegKey, LPCTSTR szDefaultFileSystem)

    {

    extern const TCHAR *g_szPART_TABLE_STRING;

    extern const TCHAR *g_szFILE_SYSTEM_MODULE_STRING;

    extern const TCHAR *g_szMOUNT_FLAGS_STRING;

    TCHAR szRegKey[MAX_PATH];

    TCHAR szPartId[10];

    HKEY hKey = NULL;

    DWORD dwFlags = 0;

     

    // [HKEY_LOCAL_MACHINE/System/StorageManager/Profiles/RamDisk]得到MountFlags的值

    wcscpy (m_szRootKey, szRootRegKey);

    if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, m_szRootKey, 0, 0, &hKey))

    {

    if (!FsdGetRegistryValue (hKey, g_szMOUNT_FLAGS_STRING, &dwFlags))  {

    dwFlags = 0;

    }

    RegCloseKey (hKey);

    }

     

    wsprintf (szRegKey, L"%s//%s", m_szRootKey, g_szPART_TABLE_STRING);

     

    // 决定Ramdisk所使用的档案系统驱动程序,即FATFS

    wcscpy (m_szFileSys, szDefaultFileSystem);

    if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, szRegKey, 0, 0, &hKey)

    {

    DUMPREGKEY (ZONE_INIT, szRegKey, hKey);

    wsprintf (szPartId, L"%02X", m_pi.bPartType);

    if ( !FsdGetRegistryString (hKey, szPartId, m_szFileSys, sizeof (m_szFileSys)))  {

    wcscpy (m_szFileSys, szDefaultFileSystem);

    }

    }

    // 从注册信息[HKEY_LOCAL_MACHINE/System/StorageManager/FATFS]得到FAT

    // 档案系统驱动程序的模块名fatfsd.dll

    if (wcslen (m_szFileSys))  {

    GetFSDString (g_szFILE_SYSTEM_MODULE_STRING, m_szModuleName,
    sizeof (m_szModuleName));

    GetFSDString ( g_szFILE_SYSTEM_MODULE_STRING, m_szFriendlyName,
    sizeof (m_szFriendlyName));

    if (m_szModuleName)  {

    // 加载FAT档案系统驱动程序fatfsd.dll

    m_hFSD = LoadDriver ( m_szModuleName);

    // 填写FSDINITDATA数据结构,呼叫InitEx

    if (m_hFSD)  {

    FSDINITDATA fid;

    fid.pIoControl =  (PDEVICEIOCONTROL) PartitionIoControl;

    fid.hDsk =  (HANDLE) this;

    fid.dwFlags = dwFlags;

    wcscpy ( fid.szFileSysName, m_szFileSys);

    wcscpy ( fid.szRegKey, szRootRegKey);

    wcscpy ( fid.szDiskName, m_szPartitionName);

    fid.hFSD = m_hFSD;

    fid.pNextDisk = NULL;

    m_pDsk = InitEx ( &fid);

    }

    }

    }

    if (m_pDsk)  {

    m_dwFlags |= PARTITION_ATTRIBUTE_MOUNTED;

    } else {

    if (m_hFSD)  {

    FreeLibrary (m_hFSD);

    m_hFSD = NULL;

    }

    }

    if (hKey) {

    RegCloseKey (hKey);

    }

    return TRUE;

    }

     

    进入InitEx之后首先会呼叫FSDLoad,由FSDLoad完成两件事:

    1.    呼叫AllocFSD完成FSD结构的分配及初始化,并将其纳入档案系统管理程序的管理当中。其参数hFSD是加载档案系统驱动程序,所对应的动态链接库后得到的句柄,有了这个句柄,我们就可以透过呼叫GetModuleFileName得到该动态链接库的名称。由于档案系统驱动程序所对应的动态链接库的名称,与该档案系统驱动程序的输出函式所使用的前缀名相同,因此刚刚得到的动态链接库的名称是所有输出函式所使用的前缀名。下面透过LocalAlloc申请分配一个FSD结构,并将其插入到档案系统管理程序负责维护的dlFSDList双向连结串行中。之后是透过GetFSDProcAddress,检查hFSD对应的动态链接库是否带有FSD_MountDiskFSD_UnmountDisk这两个函式输出点。如果有,则说明这是一个档案系统驱动程序;否则继续呼叫GetFSDProcAddress,检查该动态链接库是否带有FSD_HookVolumeFSD_UnhookVolume这两个函式,如果有,则说明这是一个过滤器驱动程序,如果还未找到则将刚刚申请的FSD结构从dlFSDList双向连结串行中删除,并释放该结构所占用的空间。在经由了上述检查之后,由于前面我们已经知道了该档案系统驱动程序的输出函式所使用的前缀名,因此可以透过GetFSDProcArray得到该档案系统驱动程序,和过滤器驱动程序的所有输出函式的地址。到此我们就完成了一个FSD结构的初始化工作,并且已经将其纳入档案系统管理程序负责维护的dlFSDList双向连结串行中。

    2.    呼叫AllocDisk为该FSD分配并初始化一个DSK结构。DSK结构中的hDsk句柄由呼叫CreateFileW得到。在呼叫CreateFileW时,首先试探以可读可写方式打开,如失败则以只读方式打开。由此也确定了DSK结构中的dwFlags是否具有DSK_READONLY属性。之后透过LocalAlloc申请分配一个DSK结构,并完成其成员变量的初始化。其中成员变量pFSD的值是刚刚透过AllocFSD得到的,这样DSKFSD这两个结构之间就建立了连接。下面为InitExFSDLoadAllocFSDAllocDisk的原始程序代码。

     

    程序代码 5.7 InitExFSDLoadAllocFSDAllocDisk函式原始码

    ***  PRIVATE/WINCEOS/COREOS/STORAGE/FSDMAIN/Fsdmain.cpp  Line 155  ***

    PDSK InitEx(FSDINITDATA *pInitData)

    {

    BOOL bSuccess = TRUE;

    PDSK pDsk = FSDLoad( pInitData ) ;                                // 呼叫FSDLoad建立的_DSK (装置)

    // _FSD (FSD模块) 结构

    PDSK pDskNew = HookFilters( pDsk, pDsk->pFSD ) ;         // FSD上挂载过滤器

    PFSD pFSD = pDsk->pFSD;                                                  // 指向FSDLoad中建立的_FSD结构

    // 如果_FSD_DSK均建立成功

    if (pFSD && pDsk)  {

    if (pFSD->pfnMountDisk)  {

    // 呼叫XXX_MountDisk注册装置中的每个volume

    if (pFSD->pfnMountDisk(pDsk))  {

    // 修改_DSK的状态

    UnmarkDisk(pDsk, DSK_UNCERTAIN);

    bSuccess = TRUE;

    }

    }

    }

    // 如果建立失败,释放前面申请的内存资源,略去

    // 回传_DSK结构的指针

    return (pDsk);

    }

     

    ***  PRIVATE/WINCEOS/COREOS/STORAGE/FSDMAIN/Fsdmain.cpp  Line 127  ***

    PDSK FSDLoad(FSDINITDATA * pInitData)

    {

    PFSD pFSD;

    PDSK pDsk;

     

    // 透过AllocFSD建立_FSD结构

    pFSD = AllocFSD((HMODULE) pInitData->hFSD);

    // 透过AllocDisk建立 DSK 结构

    pDsk = AllocDisk(pFSD, pInitData->szDiskName, pInitData->hDsk, pInitData->pIoControl);

     

    // 如果_DSK结构建立成功,继续设定

    if (pDsk)  {

    FSDMGR_GetDiskInfo(pDsk, &pDsk->fdi) ;                 // 获得装置硬件的信息(容量,扇区大小等)

    pDsk->dwFlags |= pInitData->dwFlags << 16;          // 设定_DSK的属性

    pDsk->pNextDisk = pInitData->pNextDisk;                  // 在实作其值为NULL

    pDsk->pPrevDisk = NULL;

    }

    // 如果_FSD结构建立成功,继续设定

    if (pFSD)  {

    // 拷贝名称 (实作时值是NULL)

    wcscpy(pFSD->szFileSysName, pInitData->szFileSysName);

    // 拷贝名称 (实作时值是NULL)

    wcscpy(pFSD->szRegKey, pInitData->szRegKey);

    }

    // 回传 DSK 结构的指针

    return pDsk;

    }

     

    ***  PRIVATE/WINCEOS/COREOS/STORAGE/FSDSERV/Fsdalloc.cpp  Line 37  ***

    PFSD AllocFSD (HMODULE hFSD)

    {

    PFSD pFSD;                        // 用于记录这个FSD模块的前缀

    WCHAR baseName[MAX_FSD_NAME_SIZE];

     

    // 如果模块中FSD_CreateFileW函式存在,那么前缀很简单,就是FSD_,这是一种特殊情况

    if (GetProcAddress(hFSD, L"FSD_CreateFileW"))  {

    wcscpy(baseName, L"FSD_");

    } else {

    // 否则就会比较麻烦才能找到这个前缀,略过不看

    }

    // 函式前缀被存放在了baseName

    // 首先,为新建的_FSD分配内存空间

    pFSD = (PFSD) LocalAlloc(LPTR, sizeof(FSD));

    if (pFSD)  {

    // 设定SIGFIELD(就是把后一个值,赋给SIGFIELD)

    INITSIG(pFSD, FSD_SIG);

    // 把新建的_FSD结构,加入全局的双向连结串行,连结串行头是dlFSDList

    AddListItem ( (PDLINK) &dlFSDList,  (PDLINK) &pFSD->dlFSD);

    // 设定_FSD中模块句柄的属性

    pFSD->hFSD = hFSD;

    // baseName拷贝到_FSD中去

    wcscpy ( pFSD->wsFSD, baseName);

    }

     

    // 有了名称前缀,我们就能透过GetProcAddress获得模块中函式的指针

    // 这里的GetFSDProcAddress本质上还是呼叫了GetProcAddress,只不过

    // 先把后边的 (MountDisk) 和前缀合并起来,再去呼叫

    if (pFSD)  {

    if (!pFSD->pfnMountDisk || !pFSD->pfnUnmountDisk)  {

    // _FSD中的函式指针属性赋值

    pFSD->pfnMountDisk = (PFNMOUNTDISK) GetFSDProcAddress(pFSD,
    TEXT("MountDisk"));

    pFSD->pfnUnmountDisk = (PFNMOUNTDISK) GetFSDProcAddress(pFSD,
    TEXT("UnmountDisk"));

    }

    }

     

    // _FSD结构中有三个函式指针的数组,而GetFSDProcArray就是给这个数组中的

    // 函式指标的值,其本质上还是去呼叫GetProcAddress,没有什么特别的地方

    if (GetFSDProcArray(pFSD, pFSD->apfnAFS, apfnAFSStubs, apwsAFSAPIs,
    ARRAY_SIZE(apwsAFSAPIs)) && GetFSDProcArray(pFSD, pFSD->apfnFile, apfnFileStubs,
    apwsFileAPIs, ARRAY_SIZE(apwsFileAPIs)) && GetFSDProcArray(pFSD, pFSD->apfnFind,
    apfnFindStubs, apwsFindAPIs, ARRAY_SIZE(apwsFindAPIs)))  {

    pFSD->pRefreshVolume = (PREFRESHVOLUME) GetFSDProcAddress(pFSD,
    L"RefreshVolume");

    // 如果获取函式指标失败的话,透过DeallocFSD释放_FSD结构 (并从连结串行中取出)

    } else {

    DeallocFSD (pFSD);

    pFSD = NULL;

    }

     

    // 回传新建立的_FSD结构的指针 (如果建立失败,回传NULL)

    return pFSD;

    }

     

    ***  PRIVATE/WINCEOS/COREOS/STORAGE/FSDSERV/Fsdalloc.cpp  Line 159  ***

    PDSK AllocDisk(

    PFSD                   pFSD,                          // 这个装置所需要的FSD模块对应的_FSD结构指针

    PCWSTR   pwsDsk,                     // 标识的装置名称 (比如说COM1:)

    HANDLE    hDsk,                          // InitEx中呼叫时传递的是INVALID_HANDLE_VALUE

    PDEVICEIOCONTROL                pIOControl                  // 用于IoControl的函式指标

    )

    {

    DWORD dwFlags = DSK_NONE;

     

    // 如果前面_FSD建立失败,直接错误退出

    if (pFSD == NULL)

    return NULL;

     

    // 透过装置的唯一名称,呼叫CreateFile打开装置

    if (hDsk == INVALID_HANDLE_VALUE)  {

    hDsk = CreateFileW(pwsDsk,   GENERIC_READ | GENERIC_WRITE, 0, NULL,

    PEN_EXISTING, 0, NULL);

     

    // 如果成功,可以进行读写操作,设定dwFlag

    if (hDsk != INVALID_HANDLE_VALUE)

    // 本装置可以进行读写操作 (而不是只读)

    dwFlags &= ~DSK_READONLY;

    // 否则的话,以只读模式开启装置

    else if (GetLastError() == ERROR_ACCESS_DENIED) {

    hDsk = CreateFileW (pwsDsk, GENERIC_READ, FILE_SHARE_READ, NULL,
    OPEN_EXISTING, 0, NULL);

    if (hDsk != INVALID_HANDLE_VALUE)                // 如果成功,说明装置是只读的

    dwFlags |= DSK_READONLY;                        // 装置只可以进行读取操作

    }

    pwsDsk = NULL;

    }

     

    // 如果读取操作都不可以的话,直接错误退出

    if (hDsk == INVALID_HANDLE_VALUE)  {

    return NULL;

    }

     

    // 申请_DSK结构需要的的内存空间

    PDSK pDsk = (PDSK) LocalAlloc(LPTR, sizeof (DSK));

     

    // 接下来开始设定_DSK结构的各个属性

    if (pDsk)  {

    INITSIG (pDsk, DSK_SIG) ;                                            // 设定 SIGFIELD

    pDsk->pFSD = pFSD;                                                       // 指向前面建立的_FSD的结构

    pDsk->pDeviceIoControl = pIOControl;                        // 设定用于IoControl的函式指标

    pDsk->pVol = NULL;                                                // 尚未向FSD Manager注册本装置

    // 中的档案系统volume,现在是空的

     

    // 把本装置的标识名称拷贝到_DSK

    if (pwsDsk) {

    wcscpy ( pDsk->wsDsk, pwsDsk);

    } else {

    wcscpy ( pDsk->wsDsk, L"");

    }

    }

    // 如果为_DSK分配空间失败,关闭前面开启的装置句柄(程序代码一定要严谨)

    else {

    if (hDsk) {

    CloseHandle(hDsk);

    }

    }

     

    if (pDsk)  {

    pDsk->hDsk = hDsk;                            // 设定装置的句柄

    pDsk->dwFlags = dwFlags;                // 设定装置的属性(现在只有只读,或者读写两种属性)

    }

    // 最后,回传新建立的_DSK结构的指针

    return pDsk;

    }

     

    FSDLoad回传之后,再透过FSDMGR_GetDiskInfo函式以及FSDINITDATA结构中尚未用到的一些信息,完成FSDDSK两个结构的剩下初始化工作。如所使用的档案系统的名称,装置的配置信息在注册信息中的位置等等。在这之后,FSDLoad成功回传。

     

    之后是呼叫HookFilters从注册信息中搜寻所有与该档案系统驱动程序相关的过滤器,并将它们纳入档案系统管理程序的管理之中。如前所述,有关档案系统过滤器的信息也是保存在注册信息中的,档案系统管理程序需要到以下各处,搜寻应加载的档案系统过滤器:

     

    [HKEY_LOCAL_MACHINE/System/StorageManager/AutoLoad/FileSystem
    /Filters]

    [HKEY_LOCAL_MACHINE/System/StorageManager/Profiles/ProfileName
    /FileSystem/Filters]

    [HKEY_LOCAL_MACHINE/System/StorageManager/FileSystem/Filters]

    [HKEY_LOCAL_MACHINE/System/StorageManager/Filters]

     

    HookFilters的第一项工作就是到以上各处,搜寻需要加载的过滤器驱动程序,并透过呼叫LoadFSDList,将所有的过滤器驱动程序链接成一个单向队列。注册信息中与每个过滤器相关的配置信息中都有一个Order,由它决定向档案系统驱动程序上加载这些过滤器驱动程序时所采用的顺序。与此对应,LoadFSDList函式也有一个参数bReverse用于表明链接时所采用的顺序。如果为TRUE,则LoadFSDList在搜寻到过滤器驱动程序后将它们按升幂排列。在收集了全部有关过滤器的信息之后,下面要做的就是将所有的过滤器驱动程序一个一个链接到档案系统驱动程序上。这也是透过呼叫FSDLoad完成的,只不过前面加载的对象是档案系统驱动程序,而这次是一些过滤器驱动程序。由于每次都是由LoadFSDList建立的pFSDLoadList标头 (header) 摘下一个过滤器,因此连接完成之后这些过滤器驱动程序的顺序便成了降序 (REVERSE) 。这一过程如图5.9、图5.10所示,所有的过滤器驱动程序构成了过滤器驱动程序堆栈。

     

    filter1

    filter2

    filter3

    Order=dword:0     Order=dword:1      Order=dword:2

    5.9 呼叫LoadFSDList之后

     

     

    filter3

    filter2

    filter1

    DSK

    Order=dword:2      Order=dword:1     Order=dword:0

    5.10 DSK链接之后

     

    最后要做的是呼叫档案系统驱动程序的输出函式FSD_MountDisk,由其负责定位新装置上的所有volume,并呼叫FSDMGR_RegisterVolume在档案系统管理程序中对它们进行注册。在每个档案系统驱动程序中,一般还会有一套由其自己定义的DSKVOLUME及其它一些结构,这些结构所代表的对象与档案系统管理程序中定义的DSKVOL等结构相同,不过会适应不同档案系统驱动程序的具体需要。FSD_MountDisk的主要作用就在于配合档案系统管理程序,完成对其自身定义的这些结构的管理工作,以便日后使用。

     

    FSD_MountDisk首先呼叫GetDiskInfo得到装置的相关信息,之后再由ReadWriteDisk读入该装置上的第一个扇区启动扇区的内容。由于启动扇区中含有分割区信息表,因此可知道如何为该装置分配DSKVOLUME等结构、保存这些信息、并将它们管理起来。最后对各个volume以目录的形式在档案系统管理程序中进行注册

     

    注册时要用到就是下面将要分析的FSDMGR_RegisterVolume函式,它负责申请并初始化一个VOL结构,然后呼叫RegisterAFSEx以及RegisterAFSName,按照档案系统驱动程序指定的名称对该卷进行注册。注册成功之后,档案系统管理程序就会以一级目录的形式将这些volume显示出来了,并且使得应用程序可以透过标准的档案系统API函式,对这些volume上的档案进行存取。FSDMGR_RegisterVolume函式的原型如下:

     

    PVOL

    FSDMGR_RegisterVolume(

    PDSK         pDsk,            // 档案系统volume所在装置的_DSK结构

    // 指标

    PWSTR            pwsName,    // 希望把这个volume挂载到以此字符串为名

    // 的一级目录中

    PVOLUME      pVolume        // 具体的FSD中用于管理volume使用的

    // 数据结构指针

    // 对于FAT FSD而言它就是_VOLUME

    // 结构的指针

    )

     

    前两个参数从变量名称就可以清楚知道其用途,我们以下针对第三个参数做详细的解释。事实上,作为档案系统管理程序,它并不需要知道关于每一个volume的详细信息 (如分割区从哪个扇区开始,在哪个扇区结束) ,它的工作仅仅是去告诉相对应的档案系统驱动程序,由它管理的哪一个volume需要进行什么操作。这些详细的信息对于档案系统管理程序而言是完全不必要的,因此不必放在对应的_VOL结构中。但对于具体的一个档案系统驱动程序,情况就不是这样了。这里的pVolume就指向了具体档案系统驱动程序中,管理这个档案系统volume的数据结构 (对于FAT档案系统驱动程序就是_VOLUME) 。在FSDMGR_RegisterVolume中,所要做的就是简单的就是把这个指标赋给 (将被建立的) _VOL结构中的dwVolData属性。

     

           具体而言,这些入口是怎么被注册的呢?简单的想来,系统中应该维护一个清单,清单中记录了这些入口。一旦使用者呼叫档案系统API,如CreateFile,必然会传给系统一个路径。系统先从路径名中分析出一级目录的名字,然后开始从清单中进行搜索,看这个一级目录是否是已经被登记的一个外设入口,如果是的话,就把操作权转让给档案系统管理程序,档案系统管理程序会进一步把操作权转给装置对应的档案系统模块。

     

           虽然这些注册入口的函式原始码并没有公开,但是不难透过呼叫函式的原型大概猜测出其注册的方法。上面讲的那种入口,在Windows CE中称为AFS。一个AFS包括了这个入口的名称,还记录了是否有装置被挂载 (mount) 到了这个入口下,或者说是否有档案系统volume占用了这个入口。此外还会有一个序号 (或者说索引) 同每一个AFS一一对应。系统中预设进行预先登记的那个Storage Card对应的AFS序号是OID_FIRST_AFS

     

    AFS进行具体操作的函式有如下三个:

     

    1.      GetAFSName

    查看iAFS这个索引所指向的AFS是否已经被一个档案系统volume占用,pwsAFS会记录下这个入口的名字。如果回传值为零,就表示这个入口尚未被占用。

    2.      RegisterAFSName

    建立一个AFS,其入口的名字就是此函式唯一的参数。回传值就是新建AFS一一对应的索引

    3.      RegisterAFSEx

    占用一个AFS入口。函式需要的参数包括这个被占用的AFS的索引 (首先透过上面的RegisterAFSName来建立) ,以及占用这个入口的档案系统volume所对应的_VOL结构指针等等。

     

           当一个档案系统volume进行挂载,寻找 (建立) AFS入口的时候,系统首先会从注册信息中,搜寻是否有已经设定好的对应这个装置的预设入口名称A (FSDMGR_RegisterVolume呼叫时那个pwsName就指向了这个名称A,如果注册信息中没有预设的名字,那么这个指标就是NULL) 。然后会判断Storage Card这个预设的入口是否已经被占用,如果尚未被占用,就优先把volume挂载到这个入口中。否则的话,判断是否提供了预设的入口 (pweName是否为NULL) 。如果两个方向都被否定了,就比较麻烦了,需要自己提供一个名字作为入口。

     

    FSDMGR_RegisterVolume首先会呼叫AllocVolume为这个档案系统volume建立相对应的_VOL结构。接下来,透过一定的策略为这个volume找出一个入口名,最后把这个档案系统volume注册在这个入口AFS中。程序代码5.8FSDMGR_RegisterVolume的具体原始程序代码。

     

    程序代码5.8 FSDMGR_RegisterVolume函式实作

    ***  PRIVATE/WINCEOS/COREOS/STORAGE/FSDSERV/Fsdserv.cpp  Line 336  ***

    PVOL FSDMGR_RegisterVolume(PDSK pDsk, PWSTR pwsName, PVOLUME pVolume)

    {

    PVOL pVol;

    BOOL bSuccess = FALSE;

    EnterCriticalSection (&csFSD) ;

     

    // 如果已经为装置注册了档案系统volume,先透过DeregisterVolume注销掉,再重新注册

    if (pDsk->pVol)  {

    FSDMGR_DeregisterVolume (pDsk->pVol);

    pDsk->pVol = NULL;

    }

     

    // 呼叫AllocVolume建立 (并初始化) _VOL结构

    if (pVol = AllocVolume(pDsk,  (DWORD) pVolume))  {

    PFSD pFSD = pVol->pDsk->pFSD;

     

    // 接下来的工作就是给这个新的volume注册一个AFS入口,首先需要找到合适的入口名称

    if (pVol->iAFS == INVALID_AFS)  {

    int cch;

    int iAFS, iSuffix = 1;

    WCHAR wsAFS[128];

     

    // 首先透过GetAFSNameStorage Card这个预设的入口是否已经被占用

    cch = GetAFSName(OID_FIRST_AFS, wsAFS, ARRAY_SIZE (wsAFS));

     

    // 如果没有给出可供使用的入口名(pwsName=NULL)而且Storage Card已经被占用的话

    if (pwsName == NULL && cch)  {

    // 后面会为这种最差的情况做出一个入口名

    iSuffix = 2;

    pwsName = wsAFS;

    }

     

    // 如果没有给可供使用的入口名(或者入口名就是Storage Card),而这个

    // 预设的入口并没有被占用

    if (((pwsName == NULL) || (wcscmp(pwsName, L"Storage Card")==0)) && !cch)  {

    // 那么直接呼叫RegisterAFSEx来占用这个入口就可以了

    if (RegisterAFSEx(OID_FIRST_AFS, hAFSAPI, (DWORD) pVol, AFS_VERSION,
    (pDsk->dwFlags >> 16) & (AFS_FLAG_ROOTFS | AFS_FLAG_HIDEROM
    | AFS_FLAG_BOOTABLE)))  {

    // 入口的AFS索引就是OID_FIRST_AFS

    pVol->iAFS = OID_FIRST_AFS;               // 把入口名称储存在_VOL结构中

    wcscpy ( pVol->wsVol, wsAFS);

    bSuccess = TRUE;

    }

    }

    if (!bSuccess)  {

    // 如果上面的做法都不成功的话,就给Storage Card(或者给出的那

    // 个名字)后面加上数字序号12…,如Storage Card1Storage Card2等等

    wcscpy(wsAFS, pwsName[0] == '//'? pwsName+1 : pwsName);

    cch = wcslen(wsAFS);

    do {                // 不断尝试各个序号

    if (iSuffix > 1)  {

    wsAFS[cch] = '0'+iSuffix;

    wsAFS[cch+1] = 0;

    }

    // 看这个新的名字 (末尾加了序号) 对应的入口是否已经被占用

    iAFS = RegisterAFSName(wsAFS);

    // 如果尚未被使用,就可以透过RegisterAFSEx来占用它,否则的

    // 话继续尝试 (序号++)

    if (iAFS != INVALID_AFS && GetLastError() == 0)  {

    if (RegisterAFSEx(iAFS, hAFSAPI, (DWORD)pVol, AFS_VERSION,
    (pDsk->dwFlags >> 16)  &  (AFS_FLAG_ROOTFS | AFS_FLAG_HIDEROM
    | AFS_FLAG_BOOTABLE)))  {

    // 把入口名称储存在_VOL结构中

    wcscpy ( pVol->wsVol, wsAFS) ;   // AFS的索引储存在_VOL结构中

    pVol->iAFS = iAFS;

    break;

    }

    }

    } while (iSuffix++ < 9) ;

    }

    }

     

    // 如果连序号都试过 (Storage Card1Storage Card9) ,却都被占用了

    // 的话(这种情况应该很少见,毕竟CE不可能连接那么多装置)

    if (pVol->iAFS == INVALID_AFS)  {

    // 注册 (Mount) 失败,释放前面申请的资源

    DeallocVolume (pVol);

    pVol = NULL;

    } else {

    pVol->hNotifyHandle = NotifyCreateVolume(pVol->wsVol);

    }

    }

    LeaveCriticalSection(&csFSD);

    return pVol;

    }

     

    AllocVolume这个函式的目的如同前面的AllocFSDAllocDisk都非常类似。主要就是建立 (并初始化) 一个同档案系统volume相对应的_VOL结构。比起AllocFSDAllocDisk来要简单很多。首先会申请_VOL结构需要的内存空间,然后做简单的属性设定就可以了。程序代码段5.9AllocVolume函式的具体原始程序代码。

     

    程序代码5.9 AllocVolume函式

    ***  PRIVATE/WINCEOS/COREOS/STORAGE/FSDSERV/Fsdalloc.cpp  Line 298  ***

    PVOL AllocVolume (

    PDSK                 pDsk,                // 档案装置volume所属装置的_DSK结构指针

    DWORD   dwVolData                  // 具体FSD中用于管理这个档案系统volume使用的数据结构指针

    // 它会被简单的赋予_VOL结构中的 dwVolData属性

    )

    {

    PVOL pVol;

    if (pDsk == NULL)

    return NULL;

    // 首先,申请_VOL结构需要的内存空间

    pVol = (PVOL) LocalAlloc(LPTR, sizeof (VOL));

     

    // 申请成功的话,进行简单的初始化工作

    if (pVol)  {

    INITSIG (pVol, VOL_SIG) ;                            // 设定SIGFIELD

    InitList ( (PDLINK) &pVol->dlHdlList) ;      // 初始化_HDL结构的连结串行头

    pDsk->pVol = pVol;                                         // 设定_DSK_VOL指标

    pVol->dwFlags = 0;

    pVol->pDsk = pDsk;                                       // 设定_VOL_DSK指标

    pVol->iAFS = INVALID_AFS;                          // 现在尚未被挂载,所以索引还没有意义

    pVol->dwVolData = dwVolData;                   // dwVolData赋给_VOL中的相对应属性

    }

     

    // 最后回传_VOL结构的指针

    return pVol;

    }

     

    所有的工作结束之后,Ramdisk就会以一级目录的形式展现在我们面前了,而这之后我们就可以透过CreateFileReadFileWriteFile这些标准的档案系统Win32 API接口对Ramdisk进行档案操作了。图5.11中的一级目录“储存卡”即为载入后的Ramdisk,这个一级目录名称正是图5.6所示的注册信息中Folder的键值。当然由于是中文版Windows CE的关系,Storage Card变成了储存卡。

     

    5.11 Ramdisk挂载为一级目录储存卡

     

    展开全文
  • STM32系统学习——DMA(直接储存器访问)

    万次阅读 多人点赞 2017-12-01 15:56:15
    DMA主要功能是传输数据,但是不需要占用CPU,即在传输数据时,CPU可以做别的事,像多线程。数据传输从外设到存储器或者从存储器到存储器。DMA控制包含了DMA1和DMA2,其中DMA1有7个通道,DMA2有5个通道,可以理解为...
  • Android打开系统文件管理器

    千次阅读 2019-09-29 09:40:51
    1、调出系统文件管理器界面 private static final int READ_REQUEST_CODE = 42 ; . . . /** * Fires an intent to spin up the "file chooser" UI and select an image. */ public void ...
  • opensslrsa密钥的管理用法

    千次阅读 2015-12-19 23:31:53
    1.创建rsa密钥(公钥和私钥)并储存在keystore文件中: keytool -genkeypair -alias "myalias" -keyalg RSA -keystore keystore.jks 2.从keystore文件中导出使用x509标准验证的数字证书,包含公钥: k
  • BMC,执行伺服远端管理控制,英文全称为Baseboard Management Controller. 为基板管理控制。 它可以在机器未开机的状态下,机器进行固件升级、查看机器设备、等一些操作。 在BMC中完全实现IPMI功能需要一个...
  • C#实现文件管理器

    千次阅读 2010-09-18 13:25:00
    下面详细介绍这个小程序能实现的功能: 1.包括TXET文本的新建、复制、粘贴、删除和撤销等文件操作...文件管理器主界面 <br /> 进入驱动D盘后的界面 <br /> 新建文件界面
  • Python爬虫之URL管理器:Python爬虫:URL管理器实现方式:Python爬虫之网页下载:urllib2实现网页下载的三种方法:具体代码: Python网页解析:...
  • STM32 微控制系统存储器自举模式应用笔记 STM32™ 自举程序中使用的 USART 协议 ARM Cortex™-M Programming Guide to Memory Barrier Instructions 一、系统架构主系统由 32 位多层 AHB 总线矩阵构成,
  • 而随着高清技术逐渐普及,高清监控的应用监控存储产品和技术提出了新的要求。    视频监控存储中存储的主要形态包括DVR、NVR、IPSAN三种,而如果按照传输协议来分,则可以划分为文件传输协议、数据块传输协议...
  • 文件数据储存之内部储存

    万次阅读 2019-03-01 13:28:30
    内部储存:Android的内部储存位于系统中,是在手机内存中的文件,!!!不是SD卡(外部存储器)中的文件。内部储存文件默认的存放位置是data/data/files目录下,并且是私有的(private)。可以把它设置为公有的...
  • Java 五种线程池详解、更加优雅的管理线程

    万次阅读 多人点赞 2019-06-11 00:15:01
    线程池意味着可以储存线程,并让池内的线程得以复用,如果池内的某一个线程执行完了,并不会直接摧毁,它有生命,可以存活一些时间,待到下一个任务来时,它会复用这个在等待中线程,避免了再去创建线程的额外开销。...
  • Python第三方库和包管理器

    千次阅读 2018-01-31 22:06:30
    pip 是 Python 的标准包管理器,但不是唯一管理器。一个常见选择是专门为数据科学家和类似用户设计的 Anaconda。我们将讲解 pip,因为其属于一般标准。 使用 pip 安装包 让我们使用 pip 来安装 pytz 库。为了...
  • 点击进入_更多_Java千百问1、JVM是如何管理内存的Java中,内存管理是JVM自动进行的,无需人为干涉。...当JVM发现某些对象不再需要的时候,就会该对象占用的内存进行重分配(释放)操作,而且使得分配出来的内
  • 使用SVN管理altium designer工程版本

    千次阅读 2018-05-10 08:54:21
    摘要 介绍了在AD软件内建立版本库;解决了创建,新的版本库时,SVN选项不可选的问题;使用了AD中文件对比的功能;介绍了使用 ...这种做法有一个弊端,就是不方便使用储存器管理里的比较功能。      
  • 前景回顾前面我们讲解了操作系统段式存储管理主要内容。 32位,在保护方式下,其能够访问的线性地址空间可达4GB,而且允许几乎不受存储空间限制的虚拟存储器程序。虚拟存储器地址空间能够可达64TB。它还提供了复杂...
  • 问题的背景是这样的,在创建数据源的时候,总是测试连接不成功,我就想打开配置管理器查看一下是不是出了什么问题。结果,就出现了下面的弹窗:↓↓↓出错提示: 启动SQL Server 2005 配置管理器时,出现以下提示...
  • 内存管理

    万次阅读 2015-03-12 17:09:40
    内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟的收获则是一遍一遍的检查代码和C++的痛恨,但内存管理在C++中无处不在,内存泄漏几乎在每个C++...
  • pycharm版本控制和数据库管理

    万次阅读 2014-10-08 20:03:33
    Viewing changes in the editor在编辑中查看变化 Let's edit the source code: for example, rename a variable, remove a line, add a line. All the changes are marked in the left gutter: Click...
  • 最佳日志管理工具:51个有用的日志管理、监视、分析等工具 痛苦的纯文本日志管理日子一去不复返了。虽然纯文本数据在某些情况下仍然很有用,但是在进行扩展分析以收集有洞察力的基础设施数据并改进代码质量时,寻找...
  • 最近公司测试服务器上的WAS通过windows服务管理器进行重启和停止时发现没有作用,虽然看起来像是完成了整个操作,但Java进程不变任然存在,后台stopServer.log报出了错误,如下: ************ Start Display ...
  • 设备管理信息系统

    万次阅读 多人点赞 2016-04-08 19:26:29
    设备管理系统 设备管理系统(Equipment Management System)是将信息化了设备技术信息与现代化管理相结合,是实现研究级管理信息化的先导。设备管理软件是设备管理模式与计算机技术结合的产物,设备管理的对象是...
  • 其实很简单,在inf中有中设置图表索引 显示黄色问号肯定说明 你是这样填写的 HKR,,Icon,,"-18"   PCI设备要改成 -5   数值如下 Computer 0  ...如果还有不清楚 可以看你系统的驱动inf里面的值是什么
  • 论项目的沟通管理 【摘要】 2013年3月,我作为公司项目经理参加了XX市交通运输局的道路交通智能监控抓拍系统项目的建设工作。该项目是通过在全市道路干线的