Linux进程虚拟地址空间的分布,以及堆和栈的区别

Linux进程虚拟地址空间的分布,以及堆和栈的区别,第1张

一、具体分布如图所示:

二、关于堆和栈

(1)分配方式:

栈:由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

堆: 一般由程序员分配释放,它的分配方式类似于链表。

(2)申请后系统的响应:

栈:只要所申请的空间小于栈的剩余空间,则系统为程序分配内存,否则栈溢出。

堆:操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,遍历该链表,找出第一个大于所申请空间的节点,然后将其从链表中删除并分配,如果没用完,则系统会把多余的重新放回到链表中。

(3)申请大小的限制:

栈:栈是高地址向低地址扩展的连续内存,栈的大小一般是2M;

堆:堆是低地址向高地址扩展的不连续内存,堆的大小与计算机有效的虚拟内存有关系。

(4)申请效率:

栈:由系统自动分配,速度较快;

堆:速度慢,容易产生内存碎片;

关于Linux命令的介绍,看看《linux就该这么学》,具体关于这一章地址3w(dot)linuxprobe/chapter-02(dot)html.

华为面试题:怎样判断栈的增长方向

在华为面试中有这么一道考试,请给出栈增长方向的判断方法。下面一起来看看这道题的参考答案,仅供大家参考!

该题目属于考查计算机组成原理中栈结构知识的题目。其参考答案如下:

可以编写一个带有过程(函数)调用的C程序,然后按照以下方法进行判断(采用类似思想,还可以写出许多答案)。

方法一:通过比较被调用过程中的入口参数所在地址和局部变量所在地址之间的大小来判断。若入口参数所在地址大于局部变量所在地址,则栈是向低地址增长的。

方法二:直接阅读汇编指令来判断。例如,在IA-32中,如果在一个过程的开始阶段(准备段)出现类似“sub $0x10,%esp”指令,说明栈顶指针(%esp)是变小的,因此栈是向低地址增长的。

方法三:显示栈顶指针寄存器的内容。在某个过程的开始阶段和结束阶段分别显示栈顶指针寄存器的内容,比较它们的大小。若开始处的值比结束处的大,则说明栈是向低地址增长的。

后面两种方法,需要对程序的机器级代码(汇编指令)进行调试,例如,利用Linux系统中的程序调试工具软件GDB进行调试。

该题是开放题目,答案应该没有唯一性。通过这个题目的回答可以考查出学生对计算机系统中栈结构的掌握情况。

栈是存储空间中的一个区域,分用户栈和内核栈两种类型。用户栈主要用来存放用户进程每次过程(函数)调用时,在被调用过程中使用的局部信息,每次过程调用都在栈中生长出一个新的栈帧,因此,栈帧是通过执行相应的指令动态生长出来的内核栈是操作系统内核中的动态存储区域,用于保存操作系统内核和硬件所需要的动态信息。

在采用虚拟存储管理机制的系统中,内核栈和用户栈都是虚拟地址空间中的一个存储区。每个源程序经编译、汇编、链接等处理生成可执行的二进制机器目标代码时,每个程序的目标代码都被映射到同样的虚拟地址空间,所有用户进程的虚拟地址空间是一致的。例如,图1给出了在IA32/Linux操作系统下hello程序的一个进程对应的虚拟地址空间映像。它分为两大部分:内核区(kernelarea)和用户区(userarea)。

从图1可以看出,内核区在0xC0000000以上的高端地址上,用来存放操作系统内核代码和数据以及与每个进程相关的数据结构(如进程标识信息、进程现场信息、页表等进程控制信息以及内核栈等),其中内核代码和数据区在每个进程的地址空间中都相同。用户程序没有权限访问内核区。

用户区用来存放用户进程的代码和数据,它被分为以下几个区域。

(1) 用户栈。用来存放程序运行时过程调用的参数、返回值、返回地址、过程局部变量等,随着程序的执行,该区会不断动态地从高地址向低地址增长或向反方向减退。

(2) 共享库。用来存放公共的共享函数库代码,如hello中的printf( )函数等。

(3) 堆。用于动态申请存储区,例如,C语言中用malloc()函数分配的存储区,或C++中用new操作符分配的存储区。申请一块内存时,动态地从低地址向高地址增长,用free( )函数或delete操作符释放一块内存时,动态地从高地址向低地址减退。

(4) 可读写数据区。存放用户进程中的静态全局变量,堆区从该区域的结尾处开始向高地址增长。

(5) 只读数据和代码区。存放用户进程中的代码和只读数据,如hello进程中的程序代码和字符串“hello,world\n”。

每个区域都有相应的'起始位置,堆区和栈区相向生长,栈区从内核起始位置0xC0000000开始向低地址增长,堆栈中的共享库代码区从0x40000000开始向高地址增长。代码和只读数据区从0x08048000开始向高地址增长。

对于栈的访问操作,有些指令集系统结构提供了专门的入栈和出栈指令,例如Intel架构中的push指令和pop指令分别用于入栈和出栈操作有些架构则不提供专门的入栈和出栈指令,而是通过访存指令和加/减指令来实现入栈和出栈操作,例如,MIPS架构中用sw指令和add或sub指令实现入栈操作,用lw指令和add或sub指令实现出栈操作。

对于像Intel这样提供专门入栈和出栈指令的情况,栈的增长方向可以根据入栈、出栈指令的功能来确定,例如,IA-31架构中的push指令自动将栈顶指针减4,而pop指令则自动将栈顶指针加4。因而,栈总是从高地址向低地址增长。

对于像MIPS架构这种没有专门入栈和出栈指令的情况,栈的增长方向就不一定,可能是高地址向低地址增长,或是相反。

因为栈是通过执行指令动态增长的,所以,最直接的判断办法就是在机器级代码层面(通常是汇编指令)来阅读或调试程序。

当一个过程P调用一个被调用过程Q,则P中传递给Q的参数会先入栈,然后执行调用指令(如IA-32中的call指令),跳转到Q执行,在被调用过程Q中,再将Q的局部变量入栈,因此,通过比较Q过程的入口参数所在地址和局部变量所在地址之间的大小,可以判断出栈的增长方向。


欢迎分享,转载请注明来源:夏雨云

原文地址:https://www.xiayuyun.com/zonghe/50556.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-02-25
下一篇2023-02-25

发表评论

登录后才能评论

评论列表(0条)

    保存