练习 3 - 在启动期间跟踪驱动程序的内存占用情况和动态分配

池是内核模式组件的内存资源,操作系统和设备驱动程序用它来存储它们的数据结构。 池有四个基本分配区域:

  1. 非分页池:保证驻留在物理内存中的分配。

  2. 分页池:可以从内存分页到页面文件的分配。

  3. NX 不可分页池:不可执行的非分页分配。

  4. 会话池:按会话进行的分配。 这些分配是可分页的。

池使用量是计算机上的总体内存使用量的一个重要因素,它是启动后最大的内存使用者。 池使用量的任何减少都会降低整个操作系统中系统的总体内存使用量,不可分页的内存是推动减少的最优先类别。

在本练习中,你将查看启动期间内置的 Microsoft 驱动程序分配及其占用情况(初始化时)。

步骤 1:收集启动转换期间的池内存跟踪

在这一步中,你将使用 Windows Performance Recorder (WPR) 收集一个包含池和常驻集数据的启动跟踪。

  1. 从“开始”菜单打开“WPR”

  2. 选择正确的事件提供程序:

    1. 池使用情况

    2. 常驻集

    3. 一级会审

  3. 选择“启动”作为“性能方案”。

  4. 选择“文件”作为“日志记录模式”。

  5. 将“迭代次数”设置为 1。

  6. 单击“启动”,然后选择要保存 ETL 文件的位置。

系统将自动重新启动,收集跟踪,并在桌面可见后停止。

WPR 设置对话框的屏幕截图。

步骤 2:使用 WPA 查看池数据

池数据显示在 WPA 中的“池图表”摘要表中。 需要注意的重要列如下表所示。

如果右键单击列标题,可以添加或删除列。

术语 说明
池标记 与池分配相关联的标记。
池标记模块 与池标记相关联的模块(驱动程序)。
堆栈 显示线程上导致内存分配的代码路径。
分页 指示分配是置于分页池还是非分页池中。
影响类型 显示分配是影响稳定状态内存使用量还是一个暂时性分配。
  1. 使用 WPA 打开在步骤 1 中捕获的跟踪。

  2. 打开“跟踪”菜单,然后选择“配置符号路径”。

    • 指定符号缓存的路径。 有关符号详细信息,请参阅 MSDN 上的符号支持页面。
  3. 打开“跟踪”菜单,然后选择“负载符号”。

  4. 在“Graph 浏览器”的“内存”类别中找到“池”图表

  5. 将“池”图表拖放到“分析”选项卡上。

  6. 整理表以显示以下这些列:

    1. 池标记模块

    2. 分页

    3. 影响类型

    4. 堆叠

    5. 池标记

    6. 计数

    7. 影响大小和大小

    **有关池标记的说明:**

    如果你是驱动程序开发人员,请确保驱动程序所用的池标记清晰明了,便于分析。 例如,如果公司名称为 Fabrikam,可以向所有池标记添加一个“Fbk”前缀:FbkPool1、FbkPool2、FbkBuffer,等等。

    显示重新整理后的 WPA 表的屏幕截图。

  7. 禁用图表中的所有系列(右键单击 ->“禁用”->“整个图表中”->“所有系列”)

    WPA 中“禁用”菜单选项的屏幕截图。

  8. 通过单击“影响大小”列标题,按影响大小排序。

    稳定状态内存使用量最高的驱动程序显示在最上面。

步骤 3:截获池分配数据

  1. 放大到时间线的前 30 秒。

  2. 选择一个驱动程序(例如 ACPI.sys,但任何一个都可以)。

    1. 查看“非分页”内存并展开该行。

      非分页内存应该是调查的重点,因为当系统内存不足时,无法将其移到页面文件。

    2. 为“影响中”和“暂时”类别启用“图例”。

      显示内存使用情况的示例数据的屏幕截图。

  3. 通过单击列标题,按“影响大小”排序。

  4. 影响中的内存会一直构成驱动程序的总体内存占用量。 在前面的示例中,你可以断定 ACPI.sys 一直在使用某些非分页内存,并且这种稳定状态使用情况增加了两次(第一次是在加载驱动程序时,第二次是在 3 秒左右)。

    1. 展开堆栈并在其中浏览。 在顶部,应该会看到导致最大稳定状态池分配的函数调用。

    2. 在下面的示例中,可以看到 ACPI.sys 导致了总共 255 个池分配,在 ACPIInitStartACPI 函数下总计 1.2 MB。 这是驱动程序开发人员在改进驱动程序稳定状态内存使用情况时应重点关注的地方,因为此函数占了驱动分配的大部分。

      示例数据表的屏幕截图,显示了具有可扩展进程节点的 ACPI.sys 的内存使用情况

  5. 通过单击列标题,按“大小”排序。

  6. 对“暂时”类别执行相同的操作。 展开堆栈并在其中浏览。 在顶部,应该会看到导致最大暂时池分配的函数调用。

    • 在下面的示例中,可以看到暂时内存使用量的初始峰值主要是由 ACPI 执行设备 DPC(ACPI.sys!ACPIBuildDeviceDpc)导致的。 在这个函数调用下引入代码的峰值总计 455 KB。

      示例数据图的屏幕截图,显示 ACPI.sys 按“使用资源时间作为 AllocTime, FreeTime 的峰值未完成大小(Aggregation: Sum)”的内存使用情况

步骤 4:测量驱动程序代码占用量

  1. 在“Graph 浏览器”的“内存”类别中,找到“常驻集”图表。

  2. 将“常驻集”图表拖放到“分析”选项卡上。

  3. 确保取消缩放图表 (Ctrl+Shift+"-")。

  4. 选择“文件支持的页面”图表预设。

    “文件支持的页面”选项的屏幕截图。

  5. 在“路径树”列中,浏览到在步骤 3 中选择的驱动程序(例如,C:/Windows/drivers 下的 ACPI.sys)。

  6. 展开“驱动程序”类别,并关注“活动”页。

    “大小”列中的值表示驱动程序代码对内存占用量的影响。 在下面的示例中,该值为 0.48 MB。显示活动页的示例数据的屏幕截图。