现在的位置: 首页 > 综合 > 正文

Linux内存布局

2013年06月30日 ⁄ 综合 ⁄ 共 1372字 ⁄ 字号 评论关闭

Linux下一个进程里典型的内存布局:

一般来讲,程序使用的内存空间里有如下默认的区域:
  • 栈:用于维护程序上下文
  • 堆:用来容纳应用程序动态分配的内存区域
  • 可执行文件映像:可执行文件在内存里的映像
  • 保留区:内存中受到保护而禁止访问的内存区域的总称
    栈,在程序运行中具有举足轻重的地位。它保存了一个函数调用所需要的维护信息,这常常被称为堆栈帧或活动区域。堆栈帧一般包括如下几方面内容:
  • 函数的返回地址和参数
  • 临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量
  • 保存的上下文:包括函数调用前后需要保持不变的寄存器
i386中,一个函数的活动记录用ebp和esp两个寄存器划定范围。esp寄存器始终指向栈的顶部,同时也就指向了当前函数的活动记录的顶部。而相对的,ebp寄存器指向了函数活动记录的一个固定位置,ebp寄存器又被称为帧指针,ebp说直接指向的数据是调用该函数前ebp的值,这样函数返回的时候,ebp可以通过读取这个值恢复到调用前的值。一个i386下的函数总是这样调用:
  • 把所有或一部分参数压入栈中,如果有其他参数没有入栈,那么使用某些特定的寄存器传递
  • 把当前指令的下一条指令的地址压入栈中
  • 跳转到函数体执行
跳转到函数体之后开始执行函数,函数体的“标准”开头是这样的:
  • push ebp:把ebp压入栈中
  • move ebp,esp:ebp=esp
  • 【可选】sub esp,XXX:在栈上分配XXX字节的临时空间
  • 【可选】push XXX:如有必要,保存名为XXX寄存器(可重复多个)
函数体的“标准”结尾与开头正好相反:
  • pop XXX:如有必要,恢复保存的寄存器
  • move esp,ebp:恢复ESP同时回收局部变量空间
  • pop ebp从栈中恢复ebp的值
  • ret:从栈中取回返回地址
    一个简单的示例:
//stack.c
int foo()
{
    return 123;
}
其反汇编结果如下:


    上述过程约定称为调用惯例,一个调用惯例一般会规定如下几个方面的内容:函数参数的传递顺序和方式、栈的维护方式、名字修饰的策略。在C语言里,默认的调用惯例是cdecl。

    从上面反汇编结果可以看出,eax是传递返回值的通道。函数将返回值存储到eax中,返回后函数的调用方再读取eax。对于返回5~8字节对象返回的情况,采用eax和edx联合返回的方式进行的。对于超过8字节的返回类型:
  1. 首先调用方在栈上额外开辟了一片空间,并将这块空间的一部分作为传递返回值的临时对象,称为temp
  2. 将temp对象的地址作为隐藏参数传递给被调用方
  3. 被调用方将数据拷贝给temp对象,并将temp对象的地址用eax传出
  4. 被调用方返回后,调用方将eax指向的temp对象的内容拷贝给相应的对象
    Linux下的进程堆管理提供了两种堆空间分配方式:brk()和 mmap()系统调用:
  • brk()的作用实际上就是设置进程数据段的结束地址
  • mmap()的作用就是向操作系统申请一段虚拟地址空间,当然这块虚拟地址空间可以映射到某个文件,当它不将地址空间映射到某个文件时,我们又称这块空间为匿名空间。

glibc的malloc函数是这样处理用户的空间请求的:对于小于128kb的请求,他会在现有堆里面,按照堆分配算法为它分配一块空间并返回;对于大于128kb的请求来说,它会使用mmap函数为它分配一块匿名空间,然后在匿名空间中为用户分配空间。

抱歉!评论已关闭.