如何避免驱动程序用光内核模式堆栈?

转载 2008年09月30日 14:04:00

如何避免驱动程序用光内核模式堆栈?

内核模式堆栈是一个有限的存储区域,经常用于存储从一个函数传递到另一个函数的信息以及用于局部变量存储。虽然堆栈被映射到系统空间,但是它被视为原始调用例程的线程上下文的一部分,而不是驱动程序本身的一部分。这意味着只要调用线程正在运行,就可以保证存在堆栈,但是它可以随线程一起清除。在任何内核模式线程上运行的代码(不管它是系统线程还是驱动程序创建的线程)都使用该线程的内核模式堆栈(除非代码是 DPC,这种情况下它在某些平台上使用处理器的 DPC 堆栈)。

内核模式堆栈的大小在不同的硬件平台上有所不同。例如:

在基于 x86 的平台上,内核模式堆栈是 12K。

在基于 x64 的平台上,内核模式堆栈是 24K。(基于 x64 的平台包括使用 AMD64 体系结构处理器的系统和使用 Intel EM64T 体系结构处理器的系统)。

在基于 Itanium 的平台上,内核模式堆栈是 32K(带有 32K 的备份存储区)。(如果处理器用光来自其寄存器文件的寄存器,那么它在分配函数返回之前使用备份存储区来存放寄存器的内容。这不会直接影响到堆栈分配,但是操作系统在基于 Itanium 的平台上使用的寄存器比在其它平台上使用的更多,这使得更多的堆栈可用于驱动程序。)

每个线程都分配有一个内核模式堆栈,所以即使少量增加堆栈的大小都会极大地增加系统的内存占用。因此,给定平台上内核模式堆栈的大小由操作系统设置,不能修改。

使用内核模式堆栈的指南。驱动程序应该适当地使用内核模式堆栈并避免深度嵌套或递归的调用。传递许多字节数据的严重递归的函数可能很快用光堆栈空间。驱动程序不应该在堆栈上传递大量数据,而应该分配系统空间的内存(根据数据将被用于何处,从分页内存池和未分页内存池进行分配)并传递数据的指针。对于递归的函数,驱动程序应该限制发生递归调用的次数。

尽可能将函数设计为采用单个结构的指针而不是单个变量作为参数。如果您需要将大量基于堆栈的参数从一个函数传递到另一个函数,那么将局部变量归类到结构中并将结构的本地副本的指针传递给目标函数。这将在随后的调用中节约内核堆栈空间。例如,下面的结构在堆栈上占据单个 PVOID 的大小,而不需要单独传递变量的 6 个 ULONG:

typedef struct _COMPUTE_AXIS_COUNT_PARAMS { ULONG x; ULONG y; ULONG z; ULONG xcount; ULONG ycount; ULONG zcount; } COMPUTE_AXIS_COUNT_PARAMS;

COMPUTE_AXIS_COUNT_PARAMS params;

ComputeAxisCount(&params);

不管函数是否进行嵌套或递归调用,驱动程序都应该通过只将指针或简单计数器声明为局部变量来使内核模式堆栈的占用最小。避免将局部变量声明为字节或字符串数组来用作函数的本地缓冲区。而应该声明已在分页内存池或未分页内存池中进行分配的缓冲区指针。(记住,未分页内存池也是一种有限的资源,请节约使用。)如果您必须在堆栈上声明结构的本地副本,那么请确保结构相对较小。避免在堆栈上声明大型结构或聚合结构(例如 C++ 类)的本地副本。

在驱动程序的中断服务例程 (ISR) 中最小化内核模式堆栈占用非常重要。从 ISR 调用的函数与在任何线程中调用的函数具有相同的堆栈限制。但是,ISR 在任意线程上下文中运行,因此 ISR 会使用它正好在其上运行的线程的内核模式堆栈。这可能是当前线程的堆栈或处理器的 DPC 堆栈(如果 DPC 正在运行的话)。在任何情况下,ISR 可用的堆栈都很少(取决于该线程上堆栈的其它用户)。

在延迟过程调用 (DPC) 中,驱动程序在使用内核模式堆栈方面可以自由一些。DPC 为所有平台(除了基于 Itanium 的平台)上的每个处理器使用一个内核模式堆栈。(在基于 Itanium 的平台上,DPC 使用当前线程的堆栈。)对于每个处理器,操作系统分配单个内核模式堆栈,供运行在该处理器上的任何 DPC 使用。在给定的处理器上,同一时间只有一个 DPC 在运行,所以 DPC 实际上拥有自己的堆栈。

要确定是否存在足够的堆栈空间来调用函数或执行任务,驱动程序可以调用 IoGetStackLimitsIoGetRemainingStackSize 例程。如果没有足够的堆栈空间可用,那么驱动程序可以将任务排队到一个工作项(在单独的线程中运行,因此拥有其自身的内核模式堆栈)。但是,请记住,工作项将以 PASSIVE_LEVEL 级别在系统工作线程中运行。请记住,在原始版本的 Windows Server 2003 RTM 和早期版本的 Windows 中,必须以 IRQL PASSIVE_LEVEL 或 IRQL APC_LEVEL 调用 IoGetStackLimitsIoGetRemainingStackSize,因此不能以 DISPATCH_LEVEL 或更高级别(例如 DPC 例程)从任何例程调用它们。从 Windows Server 2003 SP1 开始,可以以任何 IRQL 调用 IoGetStackLimitsIoGetRemainingStackSize

重要:驱动程序不应该另外分配内存并将其作为内核模式堆栈使用。这绝不是任何平台的建议实践,因为它会影响操作系统的稳定性和可靠性。在基于 x64 的系统上,如果操作系统检测到未经授权的内核模式堆栈,那么它将生成一个错误检查并关闭系统。

在您的驱动程序中调试内核模式堆栈使用情况。 PREfast 是 Windows DDK 提供的带有驱动程序特定规则的静态源代码分析工具,可用来查找正在使用过量内核模式堆栈的函数。使用 PREfast 编译命令开关 /STACKHOGTHRESHOLD 来更改 PREfast 的默认堆栈使用阈值。

堆栈空间耗尽将导致操作系统崩溃和一个或几个可能的错误检查。可能包括下列错误检查:

0x7F:UNEXPECTED_KERNEL_MODE_TRAP(Parm1 设置为 EXCEPTION_DOUBLE_FAULT),由超出内核堆栈结束地址引起。

0x1E:KMODE_EXCEPTION_NOT_HANDLED,0x7E:SYSTEM_THREAD_EXCEPTION_NOT_HANDLED 或 0x8E:KERNEL_MODE_EXCEPTION_NOT_HANDLED 和一个异常码 STATUS_ACCESS_VIOLATION,后者指示内存访问违法。

0x2B:PANIC_STACK_SWITCH,通常在内核模式驱动程序使用过多堆栈空间时发生。

要调试这些问题,请使用 kf(显示堆栈跟踪)调试器命令来显示每个函数消耗的堆栈数量。如果堆栈跟踪显示不完整,那么您可以通过检查指示返回地址的符号的原始堆栈内存来手动确定堆栈跟踪。要为您的搜索建立边界,请使用 !thread 调试器扩展来找出堆栈的限制,然后使用 dps(显示字和符号)调试器命令来检查内存并尝试解析每个指针大小数目的符号。(请注意,在堆栈上找到的符号不一定指示有效的返回地址。)

检查基于 x86 的平台上的堆栈使用情况的另一项技术是检查函数的反汇编并查看函数入口处堆栈指针的移动量。(这种技术还可以在基于 x64 的平台上使用,但是反汇编稍有不同。)例如,下列典型的函数序言指示函数本地堆栈使用 140 字节 (0x8c),不计算函数可能调用的子例程消耗的任何附加堆栈:

1e3bf1de 8bff             mov     edi,edi 1e3bf1e0 55               push    ebp 1e3bf1e1 8bec             mov     ebp,esp 1e3bf1e3 81ec8c000000     sub     esp,0x8c

您可以使用 uf(反汇编函数)调试器命令或 WinDBG 反汇编窗口用汇编语言显示函数代码。

您应该做什么?
在您的驱动程序中最小化堆栈使用率:

只声明一个局部变量(指针或简单计数器)。

决不要在栈上分配大型结构或其他聚合结构(例如 C++ 类)。

不要将局部变量声明为字节或字符串数组来用作函数的本地缓冲区。而应该在分页内存池或未分页内存池中分配缓冲区并声明一个该缓冲区的指针。

避免深度嵌套或递归的调用。

不要将堆栈上的大量数据传递给另一个函数,而应该分配系统空间内存并传递数据的指针。

在递归函数中,限制发生递归调用的次数。

尽可能将函数设计为采用单个结构的指针而不是单个变量作为参数。

使用 IoGetStackLimitsIoGetRemainingStackSize 例程来确定是否存在足够的堆栈空间来调用函数去执行任务,如果没有,将任务排队到一个工作项中。

分析和调试驱动程序中内核模式堆栈的使用情况:

使用 PREfast 来查找过度使用内核模式堆栈的函数。

使用 kf 调试器命令显示每个函数消耗的堆栈数量。如果堆栈显示不完整,那么使用 !threaddps 来识别可能的返回地址。

在基于 x86 和基于 x64 的平台上,检查函数的反汇编来查看函数入口处堆栈指针的移动量。

驱动开发之用户模式和内核模式

运行 Windows 的计算机中的处理器有两个不同模式:“用户模式”和“内核模式”。根据处理器上运行的代码的类型,处理器在两个模式之间切换。应用程序在用户模式下运行,核心操作系统组件在内核模式下运行。...
  • liyun123gx
  • liyun123gx
  • 2014年06月09日 12:50
  • 893

Xerox Phaser 3117激光打印机在windows 7 64bit版下安装驱动的问题

首先在Xerox的官方网站下载windows7的新版驱动。32位和64版是同一个压缩包。这里需要注意的是如果先前使用Xerox的随机光盘安装了驱动的话,必须首先卸载这个老版本的驱动,否则后面安装新驱动...
  • ronghaoyue
  • ronghaoyue
  • 2012年06月16日 14:45
  • 5604

WIN7系统内核网络堆栈实现简述

了解windows平台内部网络堆栈实现架构,对于我们开发 NDIS驱动,TDI驱动,WSK驱动,WFP驱动等网络驱动更有帮助。 因为windows并不是开源系统,不像linux那样可以从源代码中详细...
  • haolipengzhanshen
  • haolipengzhanshen
  • 2015年01月06日 16:54
  • 3760

内核模式驱动程序的网络结构

转载请标明是引用于 http://blog.csdn.net/chenyujing1234  欢迎大家拍砖!   1、Windows 2000 网络结构和OSI模型   Windows 20...
  • bobopeng
  • bobopeng
  • 2014年06月21日 23:48
  • 327

驱动程序堆栈

发送到设备驱动程序的大部分请求都打包在 I/O 请求数据包 (IRP) 中。每个设备都用设备节点表示,每个设备节点都有一个设备堆栈。有关详细信息,请参阅设备节点和设备堆栈。若要将读、写或控制请求发...
  • wzsy
  • wzsy
  • 2016年03月28日 14:18
  • 470

linux 系统调用如何进入内核模式

linux 系统调用如何进入内核模式 分类: LINUX 原文地址:linux 系统调用如何进入内核模式 作者:szbryan linux下的系统调用如何从用户态进入内核态?这个...
  • liushi558
  • liushi558
  • 2016年05月04日 08:46
  • 236

基于Windows8与Visual Studio11开发第一个USB内核驱动程序

USB ,是英文Universal Serial BUS(通用串行总线)的缩写,而其中文简称为“通串线,是一个外部总线标准,用于规范电脑与外部设备的连接和通讯。是应用在PC领域的接口技术。USB接口支...
  • opshres169
  • opshres169
  • 2016年06月18日 14:36
  • 144

当前打印机驱动程序与计算机上启用的某个策略不兼容,阻挡了windows?NT?4.0或windows?2000内核模式驱动程

问题解决办法:想安装局域网内一共享激光打印机但安装时提示“当前打印机驱动程序与计算机上启用的某个策略不兼容,阻挡了windows?NT?4.0或windows?2000内核模式驱动程序”。组策略编辑器...
  • lsd123
  • lsd123
  • 2009年01月04日 10:20
  • 13935

WIndows内核模式驱动程序运行环境

程序运行时所处的环境 即所谓的执行上下文是程序设计中的重要概念。 对于用户线程而言运行环境也就是线程所属进程的环境, 但对于驱动程序这个问题变得非常复杂。通常这是文件系统开发人员所关心的问题,但所有的...
  • crystal0011
  • crystal0011
  • 2012年10月30日 20:38
  • 579

linux的用户模式和内核模式

MS-DOS等操作系统在单一的CPU模式下运行,但是一些类Unix的操作系统则使用了双模式,可以有效地实现时间共享。在Linux机器上,CPU要么处于受信任的内核模式,要么处于受限制的用户模式。除了内...
  • sinat_15799399
  • sinat_15799399
  • 2015年03月13日 11:24
  • 1502
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:如何避免驱动程序用光内核模式堆栈?
举报原因:
原因补充:

(最多只允许输入30个字)