内存堆栈管理

内存堆栈管理

1.1程序运行的“马甲”:进程

程序安装在磁盘上的某个路径下的二进制文件,而进程则是一个程序运行的实例:操作系统会从磁盘上加载这个程序到内存,分配相应的资源、初始化相关的环境,然后调度运行。一个进程实例包括汇编指令代码、数据,还包括进程上下文环境、cpu寄存器状态、打开的文件描述符、信号、分配的物理内存等相关资源。

在一个进程的地址空间中,地址在整个运行期间不再发生变化,这部分内存称为静态内存。而在程序中使用malloc申请内存、函数调用过程中栈在程序运行期间不断变化,这部分内存称为动态内存。用户使用malloc申请的内存一般称为堆内存,函数调用过程中使用的内存一般被称为栈内存。

1.2Linux环境下的内存管理

计算机上运行的程序包括两种,操作系统和应用程序。每一个应用程序进程都有4G大小的虚拟地址空间,这部分地址空间分为两部分:用户空间、内核空间。0-3G地址空间给应用程序使用,而操作系统一般运行在3-4G内核空间。通过内存权限管理,应用程序没有权限访问内核空间,只能通过中断或系统调用来访问内核空间。

在这里插入图片描述

通过这种地址管理,每个进程都可以独享一份独立的私有的3G用户空间。编译器在编译程序时,不用考虑每个程序在实际物理内存中的地址分配问题。

在这里插入图片描述

1.3栈的管理

栈是一种数据结构,特点是先进后出。满栈的栈指针SP总是指向栈顶元素,而空栈的栈指针则指向栈顶元素上方的可用空间。sp一开始指向栈顶元素,当有新的元素入栈时,会先移动栈指针,然后把新元素放入sp指向的空间,出栈的顺序相反,先弹出栈顶元素,然后移动栈指针,指向下一个栈顶元素。

1.3.1栈的初始化

栈的初始化其实就是栈指针SP的初始化。在系统启动过程中,内存初始化后,将栈指针指向内存中的一段空间,就完成了栈的初始化,栈指针指向的这片内存空间被称为栈空间。

在栈的初始化过程中,除了指定栈的起始地址,还要指定栈空间的大小。为了防止栈溢出,可以参考一些原则:

  1. 尽量不要在函数内使用大数组,如果确实需要大块内存,则可以使用malloc申请动态内存。
  2. 函数的嵌套层数不宜过深
  3. 递归的层数不要太深
1.3.2函数调用

每个函数都会有自己专门的栈空间来保存这些数据,每个函数的栈空间都被称为栈帧。每一个栈帧都是用两个寄存器FP、SP来维护,FP指向栈帧的底部,SP指向栈帧的顶部。一个程序往往存在多级函数调用,每一级调用都会运行不同的函数,每个函数都有自己栈帧空间,无论如何,SP总是指向当前正在运行的函数栈帧的栈顶,FP始终指向当前运行函数的底部。

1.3.3参数传递

函数调用过程中的参数传递,一般都是通过栈来完成的, 根据ATPCS规则,在函数调用过程中,当传递参数个数小于4时,直接使用R0-R3寄存器传递即可;当要传递的参数个数大于4时,前4个参数使用寄存器传递,剩余的参数则压入堆栈保存。

1.3.4形参与实参

函数的参数传递是值的传递,形参保存的是实参的副本,改变形参并不会改变实参。

形参只是在函数被调用时才会在栈中分配临时的存储单元,用来保存传递过来的实参值。例如在swap的函数的栈帧内,无论如何改变形参变量的值,也不会改变main函数的栈帧内变量。在函数运行结束时,形参单元随着栈帧的销毁而被释放。

1.3.5栈与作用域

全局变量的作用域如下:

  1. 全局变量的作用域由文件来限定
  2. 可使用extern进行扩展,被其他文件引用。
  3. 使用static进行限制,只能在本文件中使用。

局部变量的作用域如下:

  1. 局部变量的作用域由{}限定
  2. 可以使用static修饰局部变量来改变它们 的存储属性(生命周期),但不能改变作用域
1.3.6栈溢出的攻击原理

Linux进程的栈空间是有固定大小的。C语言有一个特点,就是对语法检查的宽松性。默认编程者都是高手,在操作内存用不犯错。正式这种编程的灵活性给了黑客可乘之机,利用语法检查宽松性,利用栈溢出植入自己的指令代码,夺取程序的控制权,进行恶意攻击。

1.4堆内存管理

使用malloc、free函数申请、释放的动态内存就是堆内存。

堆内存与栈内存区别:

  1. 堆内存是匿名的,不能像变量那样使用名字直接访问,一般通过指针间接访问
  2. 在函数运行期间,对函数帧栈内的内存访问也不能像变量那样通过变量名直接访问,一般通过FP、SP相对寻址访问
  3. 堆内存有程序员自己申请和释放,如果程序员没有释放内存,就会造成内存泄漏
  4. 栈内存由编译器维护,函数运行时会开辟一个栈帧空间,结束时,自动销毁释放
1.4.1裸机环境下的堆内存管理

以keil为例,keil自带的启动文件startxx.s会初始化堆内存,并设置堆的大小,然后由main函数调用_user_initial_stackheap来获取堆栈地址。堆空间地址的设置一般由编译器默认获取,将堆地址设置在ARM ZI区的后面。

在裸机环境下,因为缺少堆内存管理,经过多次申请和释放后堆内存会引起内存碎片化,当内存碎片化过多时,再去申请一片连续的大块内存时,就会失败。

1.4.2uC/OS的堆内存管理

操作系统来管理堆内存,可以有效改善上述状况。

原理很简单,就是将堆内存分成若干分区,每个分区分成了若干大小相等的内存块,程序以内存块为单位堆内存进行申请和释放。

操作系统下的堆内存管理虽然在一定程度上防止内存碎片化的产生,但是还有一些弊端。例如,内存块大小必须大于4字节,因为每个内存块要耗费前4字节作为构建链表节点的指针域,当申请较小内存时,就会造成内存浪费,性价比不高,另外,当用户申请堆内存时,必须要求对堆内存十分了解,要首先知道内存块的大小,以防越界。

1.4.3Linux堆内存管理

Linux堆内存管理不仅仅包括对内存管理,还包括读写权限管理、地址映射等。

1.5mmap映射区域探秘

分了解,要首先知道内存块的大小,以防越界。

1.4.3Linux堆内存管理

Linux堆内存管理不仅仅包括对内存管理,还包括读写权限管理、地址映射等。

1.5mmap映射区域探秘

​ 当用户使用malloc申请大于128k的堆内存时,内存分配器会通过mmap系统调用,在Linux虚拟空间直接映射一片内存给用户使用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值