美文网首页
7、内存

7、内存

作者: eesly_yuan | 来源:发表于2017-03-15 01:21 被阅读45次

    本节内容比较简单,主要介绍了栈与调用管理,并简单说明了程序的内存布局和堆的管理方式,对于一个linux进程的内存布局可以用如下图表示


    linux进程地址空间布局
    栈和函数调用

    通常来说栈是一种特殊的容器,先进后出,进程地址空间的栈是指具备该特性的动态内存区域,如上图所示,向低地址空间生长。栈通常用于保存一个函数调用所需要的维护的信息(堆栈帧或者活动记录),具体内容包括(函数的返回地址和参数---临时变量---上下文,即寄存器值),具体的结构示意图如下图所示:

    栈上记录的活动记录

    下面具体介绍一下i386标准函数调用、执行和返回过程中栈的使用方式

    • 函数调用,过程可分为下面三个步骤,其中2、3通常的call命令一起完成的
      1、将所有参数或者部分参数压入栈中(没有压入栈中的参数通常是使用寄存器传递)
      2、把当前指令的下一条指令地址压入栈中
      3、跳转到函数执行体中
    • 执行函数开头,开始执行函数通常有如下的一个标准形式
      1、push ebp,保存上一个活动记录的ebp
      2、mov ebp esp,即ebp = esp设置当前的活动记录的栈基址为当前栈位置
      3、sub esp, xxx,在栈上预留分配空间(可选)
      4、push xxx,保存寄存器值(可选)
    • 函数返回,与执行函数的开头相反
      1、pop xxx,恢复寄存器值(可选)
      2、mov esp ebp,即esp = ebp,恢复esp并回收局部变量空间
      3、pop ebp,恢复上一个活动记录的栈基址
      4、ret 从栈中取得返回地址

    下面以一个例子具体说明一下上述流程,如下代码,其对应的汇编代码如下

    int foo()
    {return 123;}
    
    函数调用流程
    调用惯例

    通过上述函数调用过程的分析可以发现函数调用的正常执行需要函数调用方和被调用方具有一个对调用过程的统一理解才能正常进行,这里统一的理解的调用惯例,通常包括以下几个方面

    • 1、函数参数的传递顺序和方式
    • 2、栈的维护方式,通常是指调用结束后参数的出栈是谁执行的,可以是调用方也可以是被调用者
    • 3、名字修饰策略,即不同的调用管理,对函数的名称的修饰会有一定的差别,保证链接时候的正确区分。
      c语言的默认的调用管理是cdecl,其具体内容为从右往左进行参数入栈,函数调用方负责参数出栈,直接在函数名称下面加一个下划线。除了cdecl外,还有很多其他的函数调用惯例,如下表所示
      调用惯例

    下图展示的是一个多级调用的栈布局,这里不在详细介绍。


    多级调用栈布局

    除了调用惯例所关注的几个点之外,函数调用方和函数还有一个交互的通道,即返回值,通常以寄存器eax进行传递。通常eax只能返回4个字节的内容,对于大于4个字节的内容其回传方式如下所示

    • 1、在调用方的栈上分配一个临时空间
    • 2、将该临时空间以隐含的参数方式传递给函数
    • 3、函数中将数据拷贝到该空间上,并最终将该临时空间的起始地址通过eax返回给调用方
    • 4、调用方获取eax后,在将地址空间的内容复制到真正需要复制的对象的栈空间上


      大返回值的返回方式
    堆和内存管理

    由于栈无法将参数传递的函数外部,并且全局变量无法动态的生成,由此引入了堆,堆是一块巨大的内存,可以动态分配,并自由使用,由程序维护堆上分配的内存的生命周期。
    c语言中堆的分配和释放通常是通过调用运行库中的malloc和free进行,且这些函数内部一般都调用的系统调用进行内存的获取和释放,但是为了避免每次调用这两个函数都调用系统调用,减少用户内核态的切换,通常运行库会先向内核批发一段大内存,然后按需提供给程序使用,避免程序每次需要内存时候都进行系统调用
    上述的过程实际上涉及到了两个问题

    • 1、如何向内核申请释放一块内存?
      在linux中,通常提供了以下两个系统调用
    int brk(void* end_data_segment)
    void *mmap(void* start, size_t length, int prot, int flags, int fd, off_t offset)
    

    glibc运行库进行内存管理时何时以何种方式向内核申请内存的方式比较复杂,后续在进行详细介绍

    • 2、堆分配算法
      申请内存后,需进行管理并提供给进程使用,这里就涉及到了一个堆的分配算法,这里简单介绍两种方式:
      (1)空闲链表;
      把堆中空闲的内存以链表的形式管理起来,当需要分配内存的时候遍历整个链表,直至找到合适的大小的块并将它拆分,当用户释放空间时再将其合并到链表中,简单的示意图如下所示


      空闲链表分配

      (2)位图法;
      位图法更加的稳定,其思想是将内存分为固定的大小的块,但进程请求内存时,总是分配整数个块给它,每一个以分配的块s的第一个块标记为H其他的标记为B,未使用的标记为F,这样就可以使用整数数组表示一个内存的使用情况,每个块使用两个bit位即可。


      位图法
      实际应用中,堆的分配算法通常比较复杂,是多种分配算法的组合,后续在进行详细介绍。

    end~

    相关文章

      网友评论

          本文标题:7、内存

          本文链接:https://www.haomeiwen.com/subject/fyfcnttx.html