Windows下的内存堆栈

 

对内存进行操作的第三个机制是使用堆栈。堆栈可以用来分配许多较小的数据块。例如,若要对链 接表和链接树进行管理,最好的方法是使用堆栈,堆栈的优点是,可以不考虑分配粒度和页面边界之类的问题,集中精力处理手头的任务。堆栈的缺点是,分配和释 放内存块的速度比其他机制要慢,并且无法直接控制物理存储器的提交和回收。

从内部来讲,堆栈是保留的地址空间的一个区域。开始时,保留区域中的大多数页面没有被提交物 理存储器。当从堆栈中进行越来越多的内存分配时,堆栈管理器将把更多的物理存储器提交给堆栈。物理存储器总是从系统的页文件中分配的,当释放堆栈中的内存 块时,堆栈管理器将收回这些物理存储器。

线程的堆栈:

每当创建一个线程时,系统就会为线程的堆栈(每个线程有它自己的堆栈)保留一个堆栈空间区 域,并将一些物理存储器提交给这个已保留的区域。按照默认设置,系统保留1 MB的地址空间并提交两个页面的内存。但是,这些默认值是可以修改的,方法是在你链接应用程序时设定M i c r o s o f t的链接程序的/ S TA C K选项:

/STACK:reserve[,commit]

当创建一个线程的堆栈时,系统将会保留一个链接程序的/ S TA C K开关指明的地址空间区域。

进程的默认堆栈

当进程初始化时,系统在进程的地址空间中创建一个堆栈。该堆栈称为进程的默认堆栈。按照默认 设置,该堆栈的地址空间区域的大小是1 MB。但是,系统可以扩大进程的默认堆栈,使它大于其默认值。当创建应用程序时,可以使用/ H E A P链接开关,改变堆栈的1 M B默认区域大小。由于D L L没有与其相关的堆栈,所以当链接D L L时,不应该使用/ H E A P链接开关。/ H E A P链接开关的句法如下:

/HEAP:reserve[,commit]

许多Wi n d o w s函数要求进程使用其默认堆栈。特别是widows提供的API。对默认堆栈的访问是顺序进行的。换句话说,系统必须保证在规定的时间内,每次只有一个线 程能够分配和释放默认堆栈中的内存块。如果两个线程试图同时分配默认堆栈中的内存块,那么只有一个线程能够分配内存块,另一个线程必须等待第一个线程的内 存块分配之后,才能分配它的内存块。一旦第一个线程的内存块分配完,堆栈函数将允许第二个线程分配内存块。这种顺序访问方法对速度有一定的影响。如果你的 应用程序只有一个线程,并且你想要以最快的速度访问堆栈,那么应该创建你自己的独立的堆栈,不要使用进程的默认堆栈。

单个进程可以同时拥有若干个堆栈。这些堆栈可以在进程的寿命期中创建和撤消。但是,默认堆栈 是在进程开始执行之前创建的,并且在进程终止运行时自动被撤消。不能撤消进程的默认堆栈。每个堆栈均用它自己的堆栈句柄来标识,用于分配和释放堆栈中的内 存块的所有堆栈函数都需要这个堆栈句柄作为其参数。

可以通过调用G e t P r o c e s s H e a p函数获取你的进程默认堆栈的句柄:

HANDLE GetProcessHeap();

为什么要创建辅助堆栈

除了进程的默认堆栈外,可以在进程的地址空间中创建一些辅助堆栈。由于下列原因,你可能想要在自己的应用程序中创建一些辅助堆栈:

? 保护组件。

? 更加有效地进行内存管理。

? 进行本地访问。

? 减少线程同步的开销。

? 迅速释放。

保护组件

通过创建多个独立的堆栈,是数据隔离,且相互独立的操作。

更有效的内存管理

通过在堆栈中分配同样大小的对象,就可以更加有效地管理堆栈。就是把大小相同的对象放在一个堆栈中进行分配。

进行本地访问

每当系统必须在R A M与系统的页文件之间进行R A M页面的交换时,系统的运行性能就会受到很大的影响。如果经常访问局限于一个小范围地址的内存,那么系统就不太可能需要在R A M与磁盘之间进行页面的交换。

所以,在设计应用程序的时候,如果有些数据将被同时访问,那么最好把它们分配在互相靠近的位置上。

减少线程同步的开销

正如下面就要介绍的那样,按照默认设置,堆栈是顺序运行的,这样,如果多个线程试图同时访问 堆栈,就不会使数据受到破坏。但是,堆栈函数必须执行额外的代码,以保证堆栈对线程的安全性。如果要进行大量的堆栈分配操作,那么执行这些额外的代码会增 加很大的负担,从而降低你的应用程序的运行性能。当你创建一个新堆栈时,可以告诉系统,只有一个线程将访问该堆栈,因此额外的代码将不执行。(就是用多个 堆栈来减少同步的性能消耗)

迅速释放堆栈

最后要说明的是,将专用堆栈用于某些数据结构后,就可以释放整个堆栈,而不必显式释放堆栈中 的每个内存块。例如,当Windows Explorer遍历硬盘驱动器的目录层次结构时,它必须在内存中建立一个树状结构。如果你告诉Windows Explorer刷新它的显示器,它只需要撤消包含这个树状结构的堆栈并且重新运行即可(当然,假定它将专用堆栈用于存放目录树信息)。对于许多应用程序 来说,这是非常方便的,并且它们也能更快地运行。

如何创建辅助堆栈

你可以在进程中创建辅助堆栈,方法是让线程调用H e a p C r e a t e函数:

HANDLE HeapCreate( DWORD fdwOptions, SIZE_T dwInitialSize, SIZE_T dwMaximumSize);

当试图从堆栈分配一个内存块时, H e a p A l l o c函数(下面将要介绍)必须执行下列操作:

1) 遍历分配的和释放的内存块的链接表。

2) 寻找一个空闲内存块的地址。

3) 通过将空闲内存块标记为“已分配”分配新内存块。

4) 将新内存块添加给内存块链接表。

从堆栈中分配内存块

若要从堆栈中分配内存块,只需要调用H e a p A l l o c函数:

PVOID HeapAlloc( HANDLE hHeap, DWORD fdwFlags, SIZE_T dwBytes);

改变内存块的大小

常常需要改变内存块的大小。有些应用程序开始时分配的内存块比较大,然后,当所有数据放入内 存块后,再缩小内存块的大小。有些应用程序开始时分配的内存块比较小,后来需要将更多的数据拷贝到内存块中去时,再设法扩大它的大小。如果要改变内存块的 大小,可以调用H e a p R e A l l o c函数:

PVOID HeapReAlloc( HANDLE hHeap, DWORD fdwFlags, PVOID pvMem, SIZE_T dwBytes);

了解内存块的大小

当内存块分配后,可以调用H e a p S i z e函数来检索内存块的实际大小:

SIZE_T HeapSize( HANDLE hHeap, DWORD fdwFlags, LPCVOID pvMem);

释放内存块

当不再需要内存块时,可以调用H e a p F r e e函数将它释放:

BOOL HeapFree( HANDLE hHeap, DWORD fdwFlags, PVOID pvMem);

撤消堆栈

如果应用程序不再需要它创建的堆栈,可以通过调用H e a p D e s t r o y函数将它撤消:

BOOL HeapDestroy(HANDLE hHeap);

调用H e a p D e s t r o y函数可以释放堆栈中包含的所有内存块,也可以将堆栈占用的物理存储器和保留的地址空间区域重新返回给系统。如果该函数运行成功, H e a p D e s t r o y返回T R U E。如果在进程终止运行之前没有显式撤消堆栈,那么系统将为你将它撤消。但是,只有当进程终止运行时,堆栈才能被撤消。如果线程创建了一个堆栈,当线程终 止运行时,该堆栈将不会被撤消。

在进程完全终止运行之前,系统不允许进程的默认堆栈被撤消。如果将进程的默认堆栈的句柄传递给H e a p D e s t r o y函数,系统将忽略对该函数的调用。

由于进程的地址空间中可以存在多个堆栈,因此可以使用G e t P r o c e s s H e a p s函数来获取现有堆栈的句柄:

DWORD GetProcessHeaps( DWORD dwNumHeaps, PHANDLE pHeaps);

若要调用G e t P r o c e s s H e a p s函数,必须首先分配一个H A N D L E数组,然后调用下面的函数:

HANDLE hHeaps[25];DWORD dwHeaps = GetProcessHeaps(25, hHeaps);if(dwHeaps > 5) { //More heaps are in this process than we expected.} else{ //hHeaps[0] through hHeap[dwHeaps - 1] //identify the existing heaps.}

注意,当该函数返回时,你的进程的默认堆栈的句柄也包含在堆栈句柄的数组中。

H e a p Va l i d a t e函数用于验证堆栈的完整性:

BOOL HeapValidate( HANDLE hHeap, DWORD fdwFlags, LPCVOID pvMem);

调用该函数时,通常要传递一个堆栈句柄,一个值为0的标志(唯一的另一个合法标志是H E A P _ N O _ S E R I A L I Z E),并且为p v M e m传递N U L L。然后,该函数将遍历堆栈中的内存块以确保所有内存块都完好无损。为了使该函数运行得更快,可以为参数p v M e m传递一个特定的内存块的地址。这样做可使该函数只检查单个内存块的有效性。

若要合并地址中的空闲内存块并收回不包含已经分配的地址内存块的存储器页面,可以调用下面的函数:

UINT HeapCompact( HANDLE hHeap, DWORD fdwFlags);

通常情况下,可以为参数f d w F l a g s传递0,但是也可以传递H E A P _ N O _ S E R I A L I Z E。

下面两个函数H e a p L o c k和H e a p U n l o c k是结合在一起使用的:

BOOL HeapLock(HANDLE hHeap);BOOL HeapUnlock(HANDLE hHeap);

这些函数是用于线程同步的。当调用H e a p L o c k函数时,调用线程将成为特定堆栈的所有者。如果其他任何线程调用堆栈函数(设定相同的堆栈句柄),系统将暂停调用线程的运行,并且在堆栈被H e a p U n l o c k函数解锁之前不允许它醒来。

H e a p A l l o c、H e a p S i z e和H e a p F r e e等函数在内部调用H e a p L o c k和H e a p U n l o c k函数来确保对堆栈的访问能够顺序进行。自己调用H e a p L o c k或H e a p U n l o c k这种情况是不常见的。

最后一个堆栈函数是H e a p Wa l k:

BOOL HeapWalk( HANDLE hHeap, PPROCESS_HEAP_ENTRY pHeapEntry);

该函数只用于调试目的。它使你能够遍历堆栈的内容。可以多次调用该函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 要在 Windows 内核中获取应用程序的用户模式堆栈,可以使用以下方法: 1. 获取进程句柄 使用函数 PsGetCurrentProcess() 获取当前进程句柄。 2. 获取进程对象 使用函数 ObReferenceObjectByHandle() 获取进程对象。 3. 获取线程列表 使用函数 PsGetNextProcessThread() 获取进程对象的线程列表。 4. 获取线程堆栈 使用函数 KeStackAttachProcess() 将当前线程切换到目标进程的上下文中,然后使用函数 KeGetCurrentThreadStackLimit() 获取当前线程的堆栈限制地址,再使用函数 KeGetCurrentThreadStackBase() 获取当前线程的堆栈基地址。 5. 计算堆栈大小 通过堆栈基地址和堆栈限制地址计算出堆栈大小。 6. 获取堆栈内容 使用函数 RtlCopyMemory() 将堆栈内容复制到缓冲区中。 完整的代码示例: ```cpp PEPROCESS Process; HANDLE ProcessHandle; NTSTATUS Status; KAPC_STATE ApcState; PKTHREAD Thread; ULONG StackSize; ULONG_PTR StackBase; ULONG_PTR StackLimit; PCHAR StackBuffer; // 获取当前进程句柄 Process = PsGetCurrentProcess(); Status = ObOpenObjectByPointer(Process, OBJ_KERNEL_HANDLE, NULL, PROCESS_ALL_ACCESS, *PsProcessType, KernelMode, &ProcessHandle); if (!NT_SUCCESS(Status)) { return Status; } // 获取进程对象 if (ProcessHandle) { // 遍历线程列表 for (Thread = PsGetNextProcessThread(Process, NULL); Thread != NULL; Thread = PsGetNextProcessThread(Process, Thread)) { // 切换到目标进程上下文 KeStackAttachProcess(&Thread->tcb, Process); // 获取堆栈信息 StackLimit = (ULONG_PTR)KeGetCurrentThreadStackLimit(); StackBase = (ULONG_PTR)KeGetCurrentThreadStackBase(); StackSize = (ULONG)(StackBase - StackLimit); // 分配缓冲区 StackBuffer = (PCHAR)ExAllocatePoolWithTag(NonPagedPool, StackSize, 'stak'); if (StackBuffer) { // 复制堆栈内容 RtlCopyMemory(StackBuffer, (PVOID)StackLimit, StackSize); // 处理堆栈内容 // ... // 释放缓冲区 ExFreePoolWithTag(StackBuffer, 'stak'); } // 恢复当前线程上下文 KeUnstackDetachProcess(&ApcState); } // 关闭进程句柄 ZwClose(ProcessHandle); } return STATUS_SUCCESS; ``` ### 回答2: Windows 内核中有一种功能称为内存转储(Memory Dump),它可以存储系统在发生错误或崩溃时的内存状态。内存转储可以包含应用程序、操作系统和设备驱动程序的数据,可以为调试和故障排除提供重要信息。 在Windows操作系统中,应用层堆栈(Application Layer Stack)是指在系统崩溃时被保存的应用程序的运行状态信息。应用层堆栈包含了应用程序的调用栈(Call Stack),记录了程序执行过程中的函数调用关系。 当系统发生错误或崩溃时,Windows内核会生成一个内核转储文件,也称为内核转储(Kernel Dump)。这个内核转储文件包含了整个系统的内存状态,可以通过分析它来识别问题的根本原因。在这个内核转储文件中,应用层堆栈是其中的一部分。 应用层堆栈中的信息可以用来确定哪个应用程序最后导致系统崩溃或故障。分析应用层堆栈可以帮助开发人员或系统管理员快速定位问题,并进行相应的修复。例如,可以查看堆栈中的函数调用关系,检查是否有无线循环、内存泄露或访问越界等问题。 要获取Windows内核转储文件中的应用层堆栈信息,可以使用一些调试工具,例如WinDbg。这些工具可以加载内核转储文件,并提供一个交互式的调试环境,用于分析应用层堆栈和其他相关信息。 总之,通过分析Windows内核转储文件中的应用层堆栈,可以了解系统崩溃的原因,并采取相应的措施来修复问题。这是一项重要的技术,可以帮助保证Windows系统的稳定性和可靠性。 ### 回答3: 在Windows内核中,应用层堆栈是指一个线程在执行过程中所使用的函数调用栈。当应用程序崩溃或发生异常时,Windows内核会产生一个称为dump文件的二进制文件,用于记录当前的内存状态和调用堆栈信息。 Windows内核dump应用层堆栈的过程如下: 首先,当应用程序发生崩溃或异常时,操作系统会拦截这个事件,并捕获相关的异常信息。然后,操作系统会保存这些信息和应用层堆栈的调用信息到一个特定的内存区域中。 接下来,操作系统会创建一个dump文件,将之前保存的内存信息和堆栈调用信息写入其中。这个dump文件通常是一个二进制文件,可以使用调试工具来进行分析和调试。 最后,开发人员或系统管理员可以使用不同的调试工具来分析dump文件,以找出导致应用程序崩溃或异常的原因。通过分析应用层堆栈的调用信息,可以确定问题发生的具体位置和可能的原因,从而进行修复或优化。 总之,Windows内核dump应用层堆栈是一种记录应用程序崩溃或异常信息的机制,它可以为开发人员或系统管理员提供快速的故障诊断和问题解决的手段。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值