进程地址空间:
从上图进程地址空间分布中可以看到,Android进程的地址空间从低地址开始分别为:代码段(Text)、数据段(Data)、BSS段、堆、内存映射段、栈。下面就分别对这些属性做下解析。
一、代码段
它一般用于存放程序执行的代码(即CPU执行的机器指令)。一般C、C++代码执行语句都会编译成机器代码保存在代码段。通常代码段是可共享的,因此频繁执行的程序只需要在内存中拥有一份拷贝即可。代码段属性通常是只读状态,以防止其他第三方程序意外地修改其指令(对该段的写操作将导致段错误)。
代码段指令根据程序设计流程依次执行,对于顺序指令,只会执行一次(每个进程);若有反复,则需使用跳转指令;若进行递归,则需要借助栈来实现。
代码段指令中包括操作码和操作对象(或对象地址引用)。若操作对象是立即数(具体数值),将直接包含在代码中;若是局部数据,将在栈区分配空间,然后引用该数据地址;若位于BSS段和数据段,同样引用该数据地址。
二、数据段
数据段通常用于存储程序中,已初始化且初值不为0的全局变量和静态局部变量。数据段属于静态内存分配(静态存储区),属性为可读可写。
数据段保存在目标文件中(在嵌入式系统里一般固化在镜像文件中),其内容由程序初始化。例如,对于全局变量 int gvar = 10,必须在目标文件数据段中保存10这个数据,然后在程序加载时复制到相应的内存。
三、BSS段
BSS(Block Started by Symbol) 段中通常存储程序中以下的符号:
1.未初始化的全局变量和静态局部变量
2.初始值为0的全局变量和静态局部变量(依赖于编译器实现)
3.未定义且初值不为0的符号(该初值即common block的大小)
在C或C++开发中,未显式初始化的静态分配变量被初始化为0(算术类型)或空指针(指针类型)。由于程序加载时,BSS会被操作系统清零,所以未赋初值或初值为0的全局变量都在BSS中。BSS段仅为未初始化的静态分配变量预留位置,在目标文件中并不占据空间,这样可减少目标文件体积。但程序运行时需为变量分配内存空间,故目标文件必须记录所有未初始化的静态分配变量大小总和(通过start_bss和end_bss地址写入机器代码)。当加载器(loader)加载程序时,将为BSS段分配的内存初始化为0。在嵌入式软件中,进入main()函数之前BSS段被C运行时系统映射到初始化为全零的内存(效率较高)。
四、堆(Heap)
堆用于存放进程运行时动态分配的内存段,可动态扩张或缩减。堆中内容是匿名的,不能按名字直接访问,只能通过指针间接访问。当进程调用malloc(C)/new(C++)等函数分配内存时,新分配的内存动态添加到堆上(扩张); 当调用free(C)/delete(C++) 等函数释放内存时,被释放的内存从堆中剔除(缩减) 。
在Android中分配的堆内存是经过字节对齐的空间,以适合原子操作。堆管理器通过链表管理每个申请的内存,由于堆申请和释放是无序的,最终会产生内存碎片。堆内存一般由应用程序分配释放,回收的内存可供重新使用。若程序员不释放,程序结束时操作系统可能会自动回收。
使用堆时经常出现以下两大问题:
1. 释放或改写仍在使用的内存(“内存破坏”);
2.未释放不再使用的内存(“内存泄漏”)。当释放次数少于申请次数时,可能已造成内存泄漏。
五、内存映射段(mmap)
在Android程序中如果内核将硬盘文件的内容直接映射到内存, 在Android应用程序都可通过mmap()系统调用请求这种映射。内存映射是一种方便高效的文件I/O方式, 因而被用于装载动态共享库。
在Android中,用户也可创建匿名内存映射,该映射没有对应的文件, 可用于存放程序数据。若通过malloc()请求一大块内存,C运行库将创建一个匿名内存映射,而不使用堆内存。”大块” 意味着比阈值 MMAP_THRESHOLD还大,缺省为128KB,可通过mallopt()调整。
六、栈(stack)
栈又称堆栈,由编译器自动分配释放,行为类似数据结构中的栈(先进后出), 栈的大小在运行时由内核动态调整。
堆栈主要有三个用途:
1.为函数内部声明的非静态局部变量(C语言中称“自动变量”)提供存储空间。
2.记录函数调用过程相关的维护性信息,称为栈帧(Stack Frame) 或过程活动记录(Procedure Activation Record)。它包括函数返回地址,不适合装入寄存器的函数参数及一些寄存器值的保存。除递归调用外,堆栈并非必需。因为编译时可获知局部变量,参数和返回地址所需空间,并将其分配于BSS段。
3.临时存储区,用于暂存长算术表达式部分计算结果或alloca()函数分配的栈内内存。
持续地重用栈空间有助于使活跃的栈内存保持在CPU缓存中,从而加速访问。进程中的每个线程都有属于自己的栈。向栈中不断压入数据时,若超出其容量就会耗尽栈对应的内存区域,从而触发一个页错误。此时若栈的大小低于堆栈最大值RLIMIT_STACK(通常是8M),则栈会动态增长,程序继续运行。映射的栈区扩展到所需大小后,不再收缩。
参考: