前言
本篇文章是自己在学习xv6操作系统内核时,发现自己对进程在内存中的布局知识点上还是有一些混淆不清,所以在这里做一些补充整理。
一、内存堆栈模型
简要地分类,进程在内存中可以分为4个部分,从低地址到高地址分别是:
-
程序代码区:该区域在程序运行时存放程序的二进制代码。
-
全局数据区:该区域主要存放全局变量,静态变量和各种常量。
-
堆:堆用于在程序运行时动态分配内存,比如new一个新的对象,或者malloc一个新数组,就是在堆中分配存储空间的,一般由程序员手动控制,但也容易造成内存泄漏。
-
栈:该区域主要存放程序运行时函数的参数与局部变量等,当程序员完成某个软件的编译时,一般该软件对应内存栈的大小也就由编译器确定了,但直到程序真正运行时,操作系统才会在内存栈中为其分配空间。
具体细分还可以见下图:
下面做一些常见的内存名词解释:
-
栈:一种操作受限制的线性表,一般元素只能先进后出,在内存模型中栈是一块按后进先出规则访问的存储区域,用来实现中断嵌套和子程序调用的参数和返回断点。
-
堆:堆在数据结构中表示一种树结构,但在内存模型中是表示一块存储区域,对该存储区域的访问是任意的,没有后进先出的要求,堆主要用来为动态变量分配存储空间。
-
ESP:ESP(Extended stack pointer)扩展栈指针寄存器,是指针寄存器的一种,用于存放函数栈顶指针。
-
EBP:EBP(Extended Base Pointer),扩展基址指针寄存器,也被称为帧指针寄存器,用于存放函数栈底指针。
-
栈帧:每个函数的每次调用,都会在内存栈中开辟一段存储空间给这个函数使用,这段栈存储空间就叫做栈帧,包含着调用时的各种信息,包括传递的参数,返回地址,局部变量。
-
内存泄漏:内存泄漏(Memory
Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。 -
调用规范:也称调用约定,决定了函数调用时,实参压栈、退栈及堆栈释放方式,以及编译后的函数命名规范。
值得注意的是,每一个进程的程序栈一定是从高地址向低地址延伸,相反地,用于动态分配的堆是从低地址向高地址进行延伸。也就是说内存可详细分为5个部分,从低地址到高地址分别是:
-
只读区(read only):存放二进制代码与常量,在linux系统中常常是ELF文件格式。
-
可读写区(read write):存放全局变量和静态变量。
-
堆(heap):同前述。
-
共享库的内存映像区(memory mapped region for shared libraries):共享库可以是各种标准库,也可以是自定义DLL等,通过链接完成,链接可以发生在编译阶段,也可以发生在程序加载阶段或者是程序运行时(run-time)的动态链接。
-
栈(stack):同前述。
具体分配时,还要注意字符串的分配,系统会创建一个字符串常量存储在只读区,然后再在栈中创建一个指针变量(局部变量)指向该字符串常量,如下图所示(这张图是高地址在下,低地址在上,与之前的图相反):
char s[]="Hello";
下面是一个小的代码示例来演示常见的变量存放位置:
int a = 0; // 可读写区/全局数据区
char *p1; // 可读写区/全局数据区
int main(int argc,char *argv[])
{
int b; //栈
char str[] = "abc"; //栈
char *p2; //栈
char *p3 = "6666"; //6666\0在只读区,p3在栈上
static int c =0; // 可读写区/全局数据区
p1 = (char *)malloc(6); //堆
p2 = (char *)malloc(6); //堆
return 0;
}
二、系统栈和用户栈
操作系统从某个角度也可以近似看做是一个进程,只不过这个进程负责管理其它进程而已,所以操作系统也会有自己的内存空间,和用户的内存空间相互隔离,其中一部分叫做系统栈,也叫内核栈,作用主要有两个:
-
保存中断现场信息
-
保存操作系统的子程序调用信息
然后一个进程也有用户态和内核态的的区分,当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(内核态)。此时处理器处于特权级最高的内核代码中执行。
当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。即此时处理器在特权级最低的用户代码中运行。
当进程处于内核态时,执行的内核代码会使用当前进程的内核栈,而当进程处于用户态时,进程本身的代码会使用当前进程的用户栈。
每个进程都有自己的内核栈和用户栈,用户栈的地址空间会随着进程不同而不同,而对于所有的进程其系统栈的地址空间则不会改变。原因很简单,进程运行时,只能运行在一个操作系统上,不同的进程运行在同一个操作系统上,也就共享一个内核空间了。
三、函数调用时的内存栈分配
现代的应用程序都运行在一个虚拟内存空间里,在32位的系统里,这个内存空间拥有4GB的寻址能力。现代的应用程序