分类: LINUX
2013-07-01 21:28:12
原文地址:Linux对内存结构的描述 作者:hbzjf
Linux在运行可执行文件时,该状态信息全部在/porc/${PID}中。
proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。
用户和应用程序可以通过 proc得到系统的信息,并可以改变内核的某些参数。由于系统的信息(如进程),是动态改变的,所以用户或应用程序读取proc文件时,proc文件系统是动态从系统内核读出所需信息并提交的。
例如,运行下面的简单程序,查看:
// Day01/1.1/TestDemo01.c
#include
#include int main(){ int a=40; int b; printf("&a=%p\n",&a); printf("&b=%p\n",&b); printf("PID=%d\n",getpid());//getpid为获取本程序运行时的进程号,相关知识后续章节 while(1);//禁止程序运行完毕退出,否则/proc/${PID}中没有任何数据 } |
除了使用getpid()函数外,还可以使用 ps aue 查看当前状态下,所有的进程信息。
进入对应的pid文件夹,程序运行的所有动态信息都在,程序一解锁,该pid文件夹中所有的信息都被删除了。
程序运行如下:(每台计算机中的PID都几乎不可能是相同的)
进入/proc/${PID}中查看相关内容:
先来简单补充下/proc文件夹的相关知识:
Linux 内核提供了一种通过 /proc 文件系统,在运行时访问内核内部数据结构、改变内核设置的机制。proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。
用户和应用程序可以通过 proc得到系统的信息,并可以改变内核的某些参数。由于系统的信息,如进程,是动态改变的,所以用户或应用程序读取proc文件时,proc文件系统是动态从系统内核读出所需信息并提交的。下面列出的这些文件或子文件夹,并不是都是在你的系统中存在,这取决于你的内核配置和装载的模块。另外,在 /proc下还有三个很重要的目录:net,scsi和sys。 Sys目录是可写的,可以通过它来访问或修改内核的参数,而net和scsi则依赖于内核配置。例如,如果系统不支持scsi,则scsi 目录不存在。
除了以上介绍的这些,还有的是一些以数字命名的目录,它们是进程目录。系统中当前运行的每一个进程都有对应的一个目录在/proc下,以进程的 PID号为目录名,它们是读取进程信息的接口。而self目录则是读取进程本身的信息接口,是一个link。
在proc文件系统中,主要包含三大类内容,进程相关部分,系统信息部分,以及系统子系统部分。procfs包含的内容:
* 进程相关部分 (只读)
这部分文件都是以数字为名的子目录,这个数字就是相关进程的进程ID,需要注意的是procfs中进程子系统部分的一个特殊点,就是/proc/self,它是指向当前执行进程的符号连接,或者说--是指向未来你将要执行指令。下面是进程文件中的某些内容说明:
cmdline ——执行进程的命令行参数
cpu ——在SMP系统中近程最后的执行CPU
fd ——到当前工作目录的符号链接
mem ——进程内存空间
root ——连接到进程执行时的 / (root)目录
stat ——进程状态
statm ——进程内存状态信息
exe——链接到进程对应的源可执行文件,指向这个执行程序
environ——当前程序运行的环境参数和变量
cwd——到当前工作目录的符号链接
fd——到当前工作目录的符号链接
? maps——进程内存映射,包含进程执行空间以及动态链接库信息,保存当前程序运行的所有内存结构,可以很清晰的看见该程序进程的内存分配情况:
p表示私有的,保护的。
从0076b00——0095300 理论上是1G的空间,程序映射到内核上的动态库和共享库。
stack编译期间变量空间放到全局栈里面
* 内核信息部分 (只读)
这部分文件同样处于/proc的顶层目录,不过它们大部分都是常规、只读的文本文件,可以直接用cat查看信息。作为系统内核执行体的抽象,我们也可以把它看作内核"进程"的信息部分,当然虽然并不存在这个进程实体。
* 内核各子系统相关部分 (部分可调)
这部分是系统内核参数调整的重头戏,在procfs中,除去上面所述的两部分内容外,还有很大一部分信息文件被存放在了一些并非以数字命名的特殊目录中,这些目录下的信息就是内核各个重要子系统的信息和可调参数,主要有:
bus 总线信息(只读)
drivers 驱动信息(只读)
fs 文件系统特别信息(只读)
ide IDE接口信息(只读)
irq IRQ信息(只读)
net 网络子系统信息(只读)
scsi SCSI系统信息(只读)
sysvipc IPC子系统信息(只读)
tty tty子系统信息(只读)
sys 系统内核可调参数 (可调)
作为Linux系统内核参数的抽象文件接口,Linux内核的大部分默认可调参数都被放在了 /proc/sys目录下,这些参数都以常规文件的形式体现,并且可以用echo/cat等文件操作命令进行调整,调整的效果是即时的,并且在系统运行的整个生命周期之间都有效(知道再次改变它们或者系统重启)。
当然Linux也提供了另外一种途径sysctl来调整这些参数,sysctl是从BSD系统继承而来的一种系统参数动态调整方法,sysctl 的使用更为简单,并且可以使用/etc/sysctl.conf保存配置以在下次启动时自动加载这些设置。
在本章节需要重点研究/proc/${PID}/maps文件中的,内存分配情况。
下面来分析下函数中各个变量的分布:
1.进程内存结构
每一个进程都有以下区域:
代码段:代码段是用来存放可执行文件的操作指令,也就是说是它是可执行程序在内存种的镜像。代码段需要防止在运行时被非法修改,所以只准许读取操作,而不允许写入(修改)操作——它是不可写的,拥有可执行权限。
数据段:数据段用来存放可执行文件中已初始化全局变量,换句话说就是存放程序静态分配[1]的变量和全局变量。
BSS段:BSS段包含了程序中未初始化全局变量,这些变量根据不同的c标准都有相应的初始值。
堆(heap):堆是用于存放进程运行中被动态分配的内存段,它大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)
栈:栈是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味这在数据段中存放变量)。除此以外在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也回被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上将我们可以把堆栈看成一个临时数据寄存、交换的内存区。
结构如下:
Linux从两个方面优化:
首先,这个段是用来存放没有初始化的数据,所以连接器实际并不会将特殊的值存储在对象文件, 这样可以减少二进制代码文件的小。
其次,当这个段被加载到内存时,内存只需要简单的根据写时复制的原则将它们 映射到一个全是0的页上,这样非常高效的设置了初值。
2.理解程序的变量与内存空间的关系
来看下面的示例:
// Day01/1.1/ Varmem.c
#include
#include
#include int add(int a,int b){ return a+b; } int a1=1; static int a2=2; const int a3=3;
main(){ int b1=4; static b2=5; const b3=6;
int *p1=malloc(4);
printf("a1:%p\n",&a1); printf("a2:%p\n",&a2); printf("a3:%p\n",&a3); printf("b1:%p\n",&b1); printf("b2:%p\n",&b2); printf("b3:%p\n",&b3); printf("p1:%p\n",p1); printf("main:%p\n",main); printf("add:%p\n",add); printf("%d\n",getpid()); while(1); } |
运行结果:
仔细对比maps的内容就会明确的看出各个变量在内存中的分布情况:
C语言中内存存储区变量的分配:
1.栈 - 由编译器自动分配释放
2.堆 - 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收
3.全局区(静态区),全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。- 程序结束释放
4.另外还有一个专门放常量的地方。- 程序结束释放
在函数体中定义的变量通常是在栈上,用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆上。
在所有函数体外定义的是全局量,加了static修饰符后不管在哪里都存放在全局区(静态区),在所有函数体外定义的static变量表示在该文件中有效,不能extern到别的文件用,在函数体内定义的static表示只在该函数体内有效。
另外,函数中的"adgfdf"这样的字符串存放在常量区。比如:
int a = 0; //全局初始化区 char *p1; //全局未初始化区 void main() { int b; //栈 char s[] = "abc"; //栈 char *p2; //栈 char *p3 = "123456"; //123456{post.content}在常量区,p3在栈上 static int c = 0; //全局(静态)初始化区 p1 = (char *)malloc(10); //分配得来得10字节的区域在堆区 p2 = (char *)malloc(20); //分配得来得20字节的区域在堆区 strcpy(p1, "123456"); //123456{post.content}放在常量区,编译器可能会将它与p3所指向的"123456"优化成一块 } |
在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区
1.栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储区。里面的变量通常是局部变量、函数参数等。
2.堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。
3.自由存储区,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。
4.全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。
5.常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(当然,你要通过非正当手段也可以修改)。
简单的总结下变量内存分配的特点和修饰符对变量内存分配的影响:
1、变量的内存分配方式有三种:
1) 从静态存储区(属于数据段)域分配
针对例如全局变量,static变量,程序在编译时编译器就已对其分配好内存地址,这块内存在程序的整个运行期间都存在(这些变量的值在程序运行期间一致存在并且在程序加载时将其load进静态存储区)。
2) 从栈上分配
针对局部变量(包括参数),在每次调用函数时,系统会动态为局部变量在栈中重新分配内存空间,变量值会被重新初始化成指定值或者随机值,在函数调用后这些局部变量占有的内存空间会被自动释放。
注: 编译器在编译时会对局部变量分配一个内存地址(若变量被赋值初始化),但是在程序运行每次函数被调用时系统还是会动态分配一个内存地址给局部变量。
3)从堆上分配或称动态内存分配
由程序员调用malloc等函数可以向系统申请任意指定大小的内存,并由程序员自己调用free等函数来释放内存,动态分配内存的生命期由程序员决定,使用非常灵活,但问题也最多。
2、变量的存储类型
1) auto:属于栈上分配内存,默认不会初始化。
2) static(分为局部和全局静态变量):属于静态存储区分配内存,默认会初始化并仅初始化一次。
3) extern(非静态全局变量): 同上
4) register:存放在寄存器中,只能是局部变量不能是全局变量并且不能加static修饰,默认不会初始化。
注:
* 静态局部变量作用域限于一个函数,静态全局变量限于一个文件
* 非静态全局变量作用域不限于一个文件,可以扩展到其它文件
* 存储类型决定了一个变量的生命周期和作用域