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过程的入口参数所在地址和局部变量所在地址之间的大小,可以判断出栈的增长方向。

这里提到,「在操作系统中执行的程序,都以进程的方式运行在更低的权限中」。事实上, 操作系统是以进程为单位去分配空间和执行的 。但是,进程和程序有什么不同呢?我们说

程序是一组指令的集合,它静态存储于诸如磁盘之类的存储器里;

当一个程序被操作系统执行时,它就会被载入内存空间,并在逻辑上产生一个独立的实例,这就是进程。

这就好像是说,程序是一道菜谱,其中的指令,就是指挥你开火加盐的步骤;进程则是烹饪的过程,操作系统按照指令一丝不苟地烹饪,得到的结果就是我们的菜肴。

随着 CPU 频率增长逐渐停滞,CPU 开始向多核的方向发展。为了让多个 CPU 核心同时为我们工作,并行地执行任务,就需要涉及线程的概念。线程的英文是 Thread,有时也称为轻量级进程 (Lightweight Process),它是操作系统进行任务调度的最小单元。线程存活于进程之中;同一个进程中的线程,共享一个虚拟内存空间,以及其中的资源;线程之间各自持有自己的线程 ID、当前指令的指针(PC)、寄存器集合以及栈。

通常来说,使用多线程(一个core假装自己是多个core)会带来一下一些优势:

将等待 I/O 操作的时间,调度到其他线程执行,提高 CPU 利用率;

将计算密集型的操作留给工作线程,预留线程保持与用户的交互;

在多 CPU/多核计算机下,有效吃干计算能力;

相比多进程的程序,更有效地进行数据共享(在同一个进程空间)。

    关于操作系统和操作系统内核这两个概念,很多人尝试去区分与解释,但是发现很难得解释的完全(包括我自己,这里只是把我自己的理解整理出来,有什么不对的地方,希望大家批评指正,共同进步)。

    查看了一些网上和CS系列书籍中的关于操作系统内核的概念解释,总结之后,我的理解是:

        (1)操作系统包括操作系统内核(这是必然的),也就是说内核程序是操作系统所包含的一组计算机程序中的一个子集,所以内核程序也是一组计算机程序,而这些内核程序是操作系统中最常使用基本模块,直接与硬件打交道,主要由用于管理存储器、文件、外设和系统资源的那些部分组成。

        (2)内核程序一直占据内存中的一段内存,这样处理器可以随时调用这些内核程序;

        (3)而操作系统除了内核程序外,还有包括其他一些基本组件,如文本编辑器、编译器、用来与用户进行交互的程序等

    对于第(2)点,可以引入《深入理解计算机系统》这本书中关于“虚拟存储器”(P12)解释的一幅图来说明,如下:

上图中,关于进程的虚拟地址空间的说明中,最上面的子区域“内核虚拟存储器”就是用来存储内核程序和数据的,这个地址空间是一个固定的结构,所以对于每一个应用程序(进程)来说,都具有同样结构的虚拟地址空间,这就可以保证每个进程都能调用操作系统内核程序来完成自己的功能。

    下面再用一幅图说明操作系统内核是操作系统的一组子程

上图中,操作系统的内核包围硬件,同时,其外层是系统调用接口,这就是操作系统中除内核以外的其他组件。

    下面整理两个网友关于操作系统与操作系统内核的解释,个人觉得解释的还是比较好的:

        (1)内核,是操作系统的基础模块,用于管理系统资源。例如提供对软件层面的抽象(例如对进程、文件系统、同步、内存、网络协议等对象的操作和权限控制),和对硬件访问的抽象(例如磁盘,显示,网络接口卡(NIC));操作系统,在内核的基础上有延伸,包括了提供基础服务的系统组件。

        (2)内核,就是计算机学科意义上的操作系统,直接与硬件交互,提供CPU时间片管理、中断、内存管理、IO管理等等;一般意义上的操作系统包含的东西要更多一些,至少要有用户交互的基本程序,比如一个命令行界面和基本的指令(文件遍历、进程管理等等),或者图形界面的桌面和文件浏览器。

Standard C library handling of write(). The library provides a portion of the system-call interface for many versions of Unix and Linux.


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

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

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

发表评论

登录后才能评论

评论列表(0条)

    保存