`

《Windows核心编程》---又是内存

 
阅读更多

一般情况下,应用程序使用的内存空间里有以下“默认”的区域:

1)栈:用于维护函数调用的上下文,离开了栈函数调用就没法实现。栈通常在用户空间的最高地址处分配,通常有数兆字节的大小;

2)堆:用来容纳应用程序动态分配的内存区域,当程序使用mallocnew分配内存时,得到的内存来自堆里。堆通常存在于栈的下方(低地址方向),在某些时候,堆也可能没有固定统一的存储区域,堆一般比栈大很多,可以有几十到数百兆字节的容量;

3)可执行文件映像:存储着可执行文件在内存中的映像。由装载器在装载时将可执行文件的内存读取或映射到这里。

4)保留区:这并不是一个单一的内存区域,而是对内存中受到保护而禁止访问的内存区域的总称,例如,大多数操作系统里,极小的地址通常都是不允许访问的,如NULL。通常C语言将无效指针赋值为0也是出于这个考虑,因为0地址上正常情况下不可能有有效的可访问数据。

Linux下,如果可执行文件依赖其他共享库,那么系统就会为它在从0x40000000开始的地址分配相应的空间,并将共享库载入该空间。在Linux中,栈向低地址方向增长,堆向高地址方向增长。

栈:

在经典的操作系统中,栈总是向下增长的。在i386下,栈顶由称为esp的寄存器进行定位。压栈操作使栈顶地址减小,弹出操作使栈顶地址增大,即栈的生长方向由高地址到低地址。

栈在程序设计中具有举足轻重的地位,栈保存了一个函数调用所需要的维护信息,这常常被称为堆栈帧(Stack Frame)或活动记录(Active Record)。堆栈帧一般包括如下几方面的内容:

1)函数的返回地址和参数;

2)临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量;

3)保存的上下文:包括在函数调用前后需要保持不变的寄存器;

i386中,一个函数的活动记录用ebpesp这两个寄存器划定范围。esp寄存器始终指向栈的顶部,同时也指向了当前函数的活动记录的顶部,而相对地,ebp寄存器指向了函数活动记录的一个固定位置,ebp寄存器又被称为帧指针(Frame Pointer)

一个i386下的函数总是这样调用的:

1)把所有或一部分参数压入栈中,如果有其他参数没有入栈,那么使用某些特定的寄存器传递;

2)把当前指令的下一条指令的地址压入栈中;

3)跳转到函数体执行。

i386函数体的标准开头是这样的:

1push ebp:把ebp压入栈中(称为old ebp);

2mov ebp, espebp=esp(这时ebp指向栈顶,而此时栈顶就是old ebp);

3)【可选】sub esp, XXX:在栈上分配XXX字节的临时空间;

4)【可选】push YYY:如有必要,保存名为YYY的寄存器(可重复多个);

ebp压入栈中,是为了在函数返回时便于恢复以前的ebp;之所以可能要保存一些寄存器,在于编译器可能要求某些寄存器在调用前后保持不变。

在函数返回时,所进行的标准结尾与标准开头正好相反:

1)【可选】pop YYY:如有必要,恢复保存过的寄存器(可重复多个);

2mov esp ebp:恢复esp同时回收局部变量空间;

3pop ebp:从栈中恢复保存的ebp的值;

4ret:从栈中取得返回地址,并跳转到该位置。

我们在VC下调试程序时,常常会看到一些没有初始化的变量或内存区域的值是“烫”,这是因为分配的栈空间的每一字节都被初始化为0xCC,而0xCCCC(即两个连续排列的0xCC)的汉字编码就是“烫”,所以0xCCCC如果被当作文本看待就是“烫”。将未初始化数据设置为0xCC的理由是这样可以有助于判断一个变量是否没有初始化。如果一个指针变量的值是0xCCCCCCCC,那么我们就可以基本相信这个指针没有经过初始化。当然,有时编译器还会使用0xCDCDCDCD作为未初始化标记,此时我们就会看到汉字“屯屯”。

堆:

Windows的进程将地址空间分配给了各种EXEDLL文件、堆、栈。其中EXE文件一般位于0x00400000起始的地址;而一部分DLL位于0x10000000起始的地址,如运行库DLL;还有一部分DLL位于接近0x80000000的位置,如系统DLLNTDLL.dllKernel32.dll。栈的位置则在0x00030000EXE文件后面都有分布,这是因为Windows中每个线程的栈都是独立的,一个进程中有多少个线程,就应该有多少个对应的栈,每个线程默认的栈大小是1MB,在线程启动时,系统会为它在进程地址空间中分配相应的空间作为栈,线程栈的大小由函数CreateThread函数指定。

在分配完上面这些地址后,Windows的地址空间已经是支离破碎了,当程序向系统申请堆空间时,就只好从这些剩下的还没有被占用的地址空间上分配了。Windows提供VirtualAlloc()函数来向系统申请空间,事实上,VirtualAlloc()申请的空间不一定只用于堆,它仅仅是向系统预留了一块虚拟地址,应用程序可以按照需要随意使用。

在使用VirtualAlloc()函数申请空间时,系统要求空间大小必须是页的整数倍,即对于x86系统来说,必须有是4096字节的整数倍。显然,这将会造成内存碎片。此时Windows为我们提供了一个更合理的分配算法,这个算法实现位于堆管理器(Heap Manager)中,堆管理器提供了一套与堆相关的API用于创建、分配、释放和销毁堆空间:

HeapCreateHeapAllocHeapFreeHeapDestroy(详见:http://blog.csdn.net/ACE1985/archive/2010/07/25/5764917.aspx

堆管理器实际上存在于Windows的两个位置,一份是位于NTDLL.DLL中,这个DLLWindows操作系统用户层的最底层DLL,它负责Windows子系统DLLWindows内核之间的接口;而在Windows内核Ntoskrnl.exe中,还存在一份类似的堆管理器,它负责Windows内核的堆空间分配,内核堆管理器的接口都是由RtlHeap开头的。

堆分配算法:如何管理一大块连续的内存空间,如何能够按照需求分配空间,如何释放已申请的空间。

1)空闲链表:

实际上就是把堆中各个空闲的块按照链表的方式连接起来,当用户请求一块空间时,可以遍历链表,直到找到合适大小的块并且将它拆分;当用户释放空间时将它合并到空闲链表中。

空闲链表是这样一种结构,在堆里的每一个空闲空间的开头有一个头(header),头结构里记录了上一个(prev)和下一个(next)空闲块的地址,所有空闲块形成了一个链表。

2)位图:

核心思想是将整个堆划分为大量的块,每个块的大小相同。当用户申请内存时,总是分配整数个块的空间给用户,第一个块我们称为已分配区域的头(head),其余的称为已分配区域的主体(body)。而我们可以使用一个整数数组来记录块的使用情况,由于每个块只有头/主体/空闲三种状态,因此仅仅需要两位即可表示一个块,如用11表示Head10表示Body00表示Free;因此称为位图。

3)对象池:

如果每一次分配的空间大小是一样的,那么就可以按照这个每次请求分配的大小作为一个单位,将整个堆空间划分为大量的小块,每次请求的时候只需要找到一个小块就可以了。对象池的管理方法可以采用空闲链表,也可以采用位图,与它们的区别仅仅在于它假定了每次请求的都是一个固定的大小。

分享到:
评论

相关推荐

    windows高级编程指南(中文版+25个c源码案例)

    之后,他又推出了经典著作《Windows 高级编程指南》和《Windows核心编程》。如今这两本书早已成为Windows程序设计领域的颠峰之作,培育了几代软件开发设计人员。他的每一本新作问世,我们都有理由相信这是一本巨著,...

    window 核心编程part1

    之后,他又推出了经典著作《Windows 高级编程指南》和《Windows核心编程》。如今这两本书早已成为Windows程序设计领域的颠峰之作,培育了几代软件开发设计人员。他的每一本新作问世,我们都有理由相信这是一本巨著,...

    VC++6.0核心编程源码.rar

    建立这样一个列表时存在的问题是,你可以调用一个Windows函数,但是该函数能够在内部调用另一个函数,而这另一个函数又可以调用另一个函数,如此类推。由于各种不同的原因,这些函数中的任何一个函数都可能运行失败...

    新版Android开发教程.rar

    ----------------------------------- Android 编程基础 1 封面----------------------------------- Android 编程基础 2 开放手机联盟 --Open --Open --Open --Open Handset Handset Handset Handset Alliance ...

    windows驱动开发技术详解-part2

    本书是作者结合教学和科研实践经验编写而成的,不仅详细介绍了Windows内核原理,而且介绍了编程技 巧和应用实例,兼顾了在校研究生和工程技术人员的实际需求,对教学、生产和科研有现实的指导意义 ,是一本值得...

    c++ 面试题 总结

    3.请简单描述Windows内存管理的方法。 内存管理是操作系统中的重要部分,两三句话恐怕谁也说不清楚吧~~ 我先说个大概,希望能够抛砖引玉吧 当程序运行时需要从内存中读出这段程序的代码。代码的位置必须在物理...

    Windows驱动开发技术详解的光盘-part1

    本书是作者结合教学和科研实践经验编写而成的,不仅详细介绍了Windows内核原理,而且介绍了编程技巧和应用实例,兼顾了在校研究生和工程技术人员的实际需求,对教学、生产和科研有现实的指导意义,是一本值得推荐的...

    Window高级编程指南(分卷1)

    之后,他又推出了经典著作《Windows 高级编程指南》和《Windows核心编程》。如今这两本书早已成为Windows程序设计领域的颠峰之作,培育了几代软件开发设计人员。他的每一本新作问世,我们都有理由相信这是一本巨著,...

    Window高级编程指南(分卷2)

    之后,他又推出了经典著作《Windows 高级编程指南》和《Windows核心编程》。如今这两本书早已成为Windows程序设计领域的颠峰之作,培育了几代软件开发设计人员。他的每一本新作问世,我们都有理由相信这是一本巨著,...

    Oracle SQL高级编程(资深Oracle专家力作,OakTable团队推荐)--随书源代码

    该资料是《Oracle SQL高级编程》的源代码 对应的书籍资料见: Oracle SQL高级编程(资深Oracle专家力作,OakTable团队推荐) 基本信息 原书名: Pro Oracle SQL 原出版社: Apress 作者: (美)Karen Morton Kerry ...

    入门学习Linux常用必会60个命令实例详解doc/txt

    因为Linux与Windows不同,其后台运行着许多进程,所以强制关机可能会导致进程的数据丢失,使系统处于不稳定的状态,甚至在有的系统中会损坏硬件设备(硬盘)。在系统关机前使用 shutdown命令,系统管理员会通知所有...

    vc++ 开发实例源码包

    GMem.cpp和GMem.h是内存管理单元的源码文件。完成端口通讯模块内存管理。 haisanidsV1.2-网络连接监控 IP实时数据。自绘了很多控件。自绘CTabCtrl、CToolBar、CMenu、CButton、CHtmlCtrl、CListCtrl。 1.初始状态只...

    java-concurrent-source:Java多并发编程从入门到精通源码-源码通

    并发运算1.10 Linux和Windows对于并发采取的不同机制第2章认识Java里面的线程2.1先来看一下线程的简单实现三种方法2.2线程里面的属性和方法2.3关于线程的中断机制2.4螺纹的生命周期2.5什么是守护线程2.6线程组2.7...

    代码语法错误分析工具pclint8.0

    PCLint识别并报告C语言中的编程陷阱和格式缺陷的发生。它进行程序的全局分析,能识别没有被适当检验的数组下标,报告未被初始化的变量,警告使用空指针,冗余的代码,等等。软件除错是软件项目开发成本和延误的主要...

    PERL语言编程

    PERL语言编程 Perl 是一种能完成任务的语言。 <br/>当然,如果你的工作就是写程序,那么从理论上来讲,你可以使用任何“完整”的计算机语言来完成任务。但是从我们的经验来看,计算机语言的区别很大程度上...

Global site tag (gtag.js) - Google Analytics