堆和栈的详解

    说到堆和栈就必须从32位Linux操作系统的虚拟内存空间布局说起,32位中虚拟内存空间大小都是4G,Linux分配时用户态内存和内核态内存之比为3:1的分配的方式,而Windows的虚拟内存空间则是2:2的分配方式,每个进程都有一个自己独立的4G虚拟内存空间,这并不矛盾,最终是需要映射到物理内存的(当然不可能有4*n G 这么大的内存条)。

 1.首先先说一说虚拟内存到物理内存的映射

物理内存是真实存在实际的物理内存,但是物理内存却是有限的;虚拟内存到物理内存的映射需要用到MMU(内存管理单元)中一个叫页表的数据结构,页表将虚拟页映射到物理页(一个页为4K),每次转换都会用页表。有了页表之后,就很容易将虚拟地

址映射到物理地址/物理内存,如图所示,当我们的页面未命中的时候就会引发缺页异常,内核就会调用do_page_fault。再多内容就不在此一一列举了。


说到这里就要说一说为什么要用虚拟内存,和它的好处:

1.较小的内存也可以运行较大的用户程序

2.更好得实现程序的并发执行

3.每个程序都彼此独立,不受影响。


2.虚拟内存空间布局


                                

如图,在虚拟内存空间布局中我们可以看出主要分为这几个部分:可以用objdump -h查看

.text段,data段,Bss段,堆,栈段和一些其他的段部分。

1..Reserved段:这个段是保留,我们是不能使用的段,若使用会引起段错误,是128M.因为它未赋予物理地址,所以任何对它的引用都是非法的,用于捕捉空指针。

2..readonly段:这个段里保存着我们的可执行代码的.text段,还有rodata段,和init段

3.data段和bss段:.data里存放的是已初始化的全局变量或者静态局部变量;.bss段里存放的是初始化为0或是未初始化的全局变量或者静态局部变量。(在.obj文件中未初始化的数据放在.common它是占内存的,但当程序运行起来之后,会将.bss清空,它只占有虚拟地址空间,而不占有真正的物理内存;所以就说当访问到数据段的时候回发出缺页异常,并分配合理的物理内存,而访问到.bss段的时候,内核将跳转到一个全零界面,所以说不会发生 缺页异常)

4.heap区:动态开辟的内存,当有malloc/new申请内存时,就会从堆上分配内存,当有free/delete的时候,从堆上释放内存,并且堆上的申请的内存需要程序员手动释放,否则会导致内存泄漏,最后操作系统进行内存回收

堆的一些特性:

(1)使用和释放都是由程序员自己控制,若不释放,则会导致内存泄漏;

(2)生长方向是从低地址向高地址增长,是不连续的内存

(3)一个堆在Linux上最多可申请2.9G;

(4)堆上分配内存是需要进行判断是否分配成功,若分配失败,在C语言中返回值为NULL,在C++中返回bad_alloc。

(5)释放内存时不仅要用free,还要将指针置空,free的原理其实并不是真正的释放内存,它只是将指针和内存的关系切断而已。频繁的free(),必将导致程序的崩溃。(详细的内存分配原理见后续的ptmalloc详解)

5.stack区:存放局部变量,函数参数,返回的地址

栈的一些特性:栈可能是编程中最最重要的一部分,没有了栈,就没有了什么函数,也就不存在什么递归调用。

(1)栈和堆恰恰相反,栈是用编译器自己去释放,一般的栈大小是1M~10M,可以用ulimit-s 查看并修改,基于编译器自动回收机制,就有了C++中的智能指针,通过栈的回退自动去析构对象。

(2)栈的操作是先进后出原则,内存增长是由高到底。具体大小由内核决定。

6.内核空间:

内核空间是操作系统的一部分,不允许应用程序对其进行访问。

二.

说一说Linux中的栈:进程栈,线程栈,内核栈,中断栈

1.进程栈

上面所说的虚拟内存空间中的stack段就是进程栈

进程栈的增长原理:当不停地向栈中添加元素,直到快将栈内的空间消耗殆尽,就会触发到一个缺页异常,它就会到内存中区找还有没有能让栈增长的空间,如果有,那就由内核来处理expand_stack(),调用acct_stack_growth来检测是否有空间可扩,但如果栈达到了最大值,就会引发栈溢出,(stack_overflow)或段错误。

2.线程栈。

每一个线程都会有自己的一个栈,(Linux内核中是没有线程的,它把线程也当做了进程来看待)线程仅仅被认为是与其他进程共享的资源的进程,这就是在Linux中进程和线程的区别。

对于Linux进程或者主线程,其栈是在fork的时候都已经创建好了,其实就是复制了父进程的stack,当用的时候再进行写时拷贝,但用主线程创建的子线程则不一样,它的大小是在一开始就已经是计算好的,不能进行动态增长。

线程栈的空间开辟源于进程的堆区,线程之间共享进程的内存空间,线程栈的起始大小放在pthread_attr_t中。之所以有线程栈和进程栈,为的目的是彼此间的独立性,不会互相影响破坏内部数据,并且进程和线程不共享内核栈。


3.内核栈

当一个进程系统调用的时候,必将会进入内核,但内核的函数调用用的不再是用户态的栈了,它用的是内核栈,一般大小来说是4K(一个页面的大小)

4.中断栈

这个只做简要了解,当系统产生中断时,它还在内核中,所以需要用到中断栈。










   

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值