Linux在内存管理上份为两级,一级是线性区,对应于虚拟内存,它实际上不占用实际物理内存;一级是具体的物理页面,对应机器上的物理内存。
这
里要提到一个很重要的概念,内存的延迟分配。Linux内核在用户申请内存的时候,只是给它分配了一个线性区(也就是虚存),并没有分配实际物理内存;只
有当用户使用这块内存的时候,内核才会分配具体的物理页面,这时候才占用真实的物理内存。内存释放是通过虚拟地址,找到其所对应的物理页面,释放物理页
面,然后释放线性区。
我们知道用户的进程和内核是运行在不同的级别,进程与内核之间的通讯是通过系统调用来完成的。进程在申请和释放内
存,主要通过 brk/sbrk,mmap/unmmap这几个系统调用,传递的参数主要是对应的虚拟内存。
注意一点,在进程只能访问虚
拟内存,它实际上是看不到内核物理内存的使用,这对于进程是完全透明的。
glibc内存管理器
那么我们每次调用
malloc来分配一块内存,都进行相应的系统调用呢?
答案是否定的,这里我要引入一个新的概念,glibc的内存管理器。
我们
知道malloc和free等函数都是包含在glibc库里面的库函数,试想一下,每做一次内存操作,都要调用系统调用的话,那么会多么的低效。
实
际上glibc的内存管理器采用了二次分配的方式来管理内存。glibc每次通过系统调用的方式申请一大块内存(虚拟内存),当进程申请内存
时,glibc就从自己获得的内存中取出一块给进程。
内存管理器面临的困难
我们在写程序的时候,每次申请的内存块大小不
规律,而且存在频繁的申请和释放,这样不可避免会产生内存碎片。而内存碎片会导致大块内存申请无法满足,从而更多的占用系统资源;如果进行碎片整理的话,
又会增加cpu的负荷。
malloc和free主要是针对heap进行操作,由程序员自主控制内存的访问。
glibc对
于大于128k的heap内存申请,采用mmap的方式向内核申请内存;小于128k的则采用brk。128k的阈值,可以通过glibc的库函数进行设
置。
对于大块内存申请,glibc直接使用mmap系统调用为其划分出另一块虚拟地址,供进程单独使用;在该块内存释放时,使用
unmmap系统调用将这块内存释放,这个过程中间不会产生内存碎片等问题。
针对小块内存的申请,在程序启动之后,进程会获得一个
heap底端的地址,进程每次进行内存申请时,glibc会将堆顶向上增长来扩展内存空间,也就是我们所说的堆地址向上增长。在对这些小块内存进行操作
时,便会产生内存碎块的问题。实际上brk和sbrk系统调用,就是调整heap顶地址指针。
那么heap堆的内存是什么时候释放呢?
当
glibc发现堆顶有连续的128k的空间是空闲的时候,它就会通过brk或sbrk系统调用,来调整heap顶的位置,将占用的内存返回给系统。这时,
内核会通过删除相应的线性区,来释放占用的物理内存。
考虑一个内存空洞的场景,堆顶有一块正在使用的内存,而下面有很大的连续内存已经被
释放掉了,那么这块内存是否能够被释放?其对应的物理内存是否能够被释放?
很遗憾,不能。
这也就是说,只要堆顶的部分申
请内存还在占用,我在下面释放的内存再多,都不会被返回到系统中,仍然占用着物理内存。为什么会这样呢?
这主要是与内核在处理堆的时候过
于简单,它只能通过调整堆顶指针的方式来调整调整程序占用的线性区;而又只能通过调整线性区的方式,来释放内存。所以只要堆顶不减小,占用的内存就不会释
放。
为什么而释放内存的时候只要内存的指针呢?
这主要是和glibc的内存管理机制有关。glibc中,为每一
块内存维护了一个chunk的结构。glibc在分配内存时,glibc先填写chunk结构中内存块的大小,然后是分配给进程的内存。
在
进程释放内存时,只要指针-4 便可以找到该块内存的大小,从而释放掉。
注:glibc在做内存申请时,最少分配16个字节,以便能够维护
chunk结构,这也是为什么分配大小为0的内存能够成功的原因。
glibc提供的调试工具:
为了方便调试,glibc
为用户提供了malloc函数的钩子__malloc_hook,其实是一个特定类型的函数指针。
例子如下
#include
<stdio.h>
#include <malloc.h>
/* Prototypes for
our hooks. */
static void my_init_hook(void);
static void
*my_malloc_hook(size_t, const void *);
/* Variables to save original
hooks. */
static void *(*old_malloc_hook)(size_t, const void *);
/*
Override initialising hook from the C library. */
void
(*__malloc_initialize_hook) (void) = my_init_hook;
static void
my_init_hook(void) {
old_malloc_hook = __malloc_hook;
__malloc_hook = my_malloc_hook;
}
static void * my_malloc_hook
(size_t size, const void *caller) {
void *result;
/*
Restore all old hooks */
__malloc_hook = old_malloc_hook;
/* Call recursively */
result = malloc (size);
/* Save
underlying hooks */
old_malloc_hook = __malloc_hook;
/*
'printf' might call 'malloc', so protect it too. */
printf
("malloc(%u) called from %p returns %p0,
(unsigned int) size,
caller, result);
/* Restore our own hooks */
__malloc_hook = my_malloc_hook;
return result;
}
其中
caller 是调用 malloc 返回值的接受者(一个指针的地址)。
__malloc_initialize_hook仅仅会调用一次
(第一次分配动态内存时)。
如何检测 memory leakage?
glibc 提供了函数void
mtrace (void)以及void muntrace (void)
这时会依赖于一个环境变量 MALLOC_TRACE
所指的文件,把一些信息记录在该文件中用于侦测memory leakage,其本质是安装了前面提到的
hook。一般将这些函数用DEBUG宏包裹以便在非调试态下减少开销。产生的文件据说不建议自己去读,而使用 mtrace 程序(perl
脚本来进行分析)。
下面用一个简单的例子说明这个过程,这是源程序:
#include <stdio.h>
#include
<stdlib.h>
#include <mcheck.h>
int main( int argc,
char *argv[] )
{
int *p, *q ;
#ifdef DEBUG
mtrace();
#endif
p = malloc(sizeof(int));
q = malloc(sizeof(int));
printf( "p =
%p/nq = %p/n", p, q );
*p = 1;
*q = 2;
free(p);
return 0;
}
很简单的程序,其中 q 没有被释放。我们设置了环境变量后export
MALLOC_TRACE=~/trace.txt
结果如下:
p = 0x8ee2378
q = 0x8ee2388
trace.txt
内容如下
= Start
@ ./test:[0x8048678] + 0x8ee2378 0x4
@
./test:[0x8048687] + 0x8ee2388 0x4
@ ./test:[0x80486c1] - 0x8ee2378
代
码占用的内存
数据部分占用内存,那么我们写的程序是不是也占用内存呢?
在linux中,程序的加载,涉及到两个工
具,linker 和loader。linker主要涉及动态链接库的使用,loader主要涉及程序的加载。
1.
exec执行一个程序
2.
elf为现在非常流行的可执行文件的格式,它为程序运行划分了两个段,一个段是可以执行的代码段,它是只读,可执行;另一个段是数据段,它是可读写,不能
执行。
3.
loader会启动,通过mmap系统调用,将代码段和数据段映射到内存中,其实也就是为其分配了虚拟内存,注意这时候,还不占用物理内存;只有程序执行
到了相应的地方,内核才会为其分配物理内存。
4.
loader会去查找该程序依赖的链接库,首先看该链接库是否被映射进内存中,如果没有则使用mmap,将代码段与数据段映射到内存中,否则只是将其加入
进程的地址空间。这样比如glibc等库的内存地址空间是完全一样。
运行过程中链接动态链接库与编译过程中链接动态库的区别。
我
们调用动态链接库有两种方法:一种是编译的时候,指明所依赖的动态链接库,这样loader可以在程序启动的时候,来所有的动态链接映射到内存中;一种是
在运行过程中,通过dlopen/dlfree的方式加载动态链接库,动态将动态链接库加载到内存中。
这两种方式,从编程角度来讲,第一
种是最方便的,效率上影响也不大,在内存使用上有些差别。
第一种方式,一个库的代码,只要运行过一次,便会占用物理内存,之后即使再也不
使用,也会占用物理内存,直到进程的终止。
第二中方式,库代码占用的内存,可以通过dlfree的方式,释放掉,返回给物理内存。
这
个差别主要对于那些寿命很长,但又会偶尔调用各种库的进程有关。如果是这类进程,建议采用第二种方式调用动态链接库。
占用内存的测量
测
量一个进程占用了多少内存,linux为我们提供了一个很方便的方法,/proc目录为我们提供了所有的信息,实际上top等工具也通过这里来获取相应的
信息。
/proc/meminfo 机器的内存使用信息
/proc/pid/maps 进程(PID)所占用的虚拟地址。
/proc/pid/statm
进程所占用的内存
[root@localhost ~]# cat /proc/self/statm
654 57 44 0 0
334 0
查看机器可用内存
[root@localhost ~]# free
total used free shared buffers cached
Mem:
1026860 753136 273724 0 191284 417336
-/+
buffers/cache: 144516 882344
Swap: 2031608 132408
1899200
我们通过free命令查看机器空闲内存时,会发现free的值很小。这主要是因为在linux中尽可能的
cache和buffer一些数据,以方便下次使用。但实际上这些内存也是可以立刻拿来使用的。
所以空闲内存 =
free+buffers+cached = total-used
查看进程使用的内存
查看一个进程使用的内存,是一个
很令人困惑的事情。因为我们写的程序,必然要用到动态链接库,将其加入到自己的地址空间中,但是/proc/pid
/statm统计出来的数据,会将这些动态链接库所占用的内存也算进来。
这样带来的问题,动态链接库占用的内存有些是其他程序使用时占用
的,却算在了你这里。如果你的程序中包含了子进程,那么有些动态链接库重用的内存会被重复计算。
因此要想准确的评估一个程序所占用的内存
是十分困难的,通过写一个module的方式,来准确计算某一段虚拟地址所占用的内存,可能对我们有用。
[NOTE]
在Linux下,glibc
的malloc提供了下面两种动态内存管理的方法:堆内存分配和mmap的内存分配,此两种分配方法都是通过相应的Linux
系统调用来进行动态内存管理的。具体使用哪一种方式分配,根据glibc的实现,主要取决于所需分配内存的大小。一般情况中,应用层面的内存从进程堆中分
配,当进程堆大小不够时,可以通过系统调用brk来改变堆的大小,但是在以下情况,一般由mmap系统调用来实现应用层面的内存分配:A、应用需要分配大
于特定数值的内存,B、在没有连续的内存空间能满足应用所需大小的内存时。
(1)、调用brk实现进程里堆内存分配
在glibc中,当进程所需要的内存较小时,该内存会从进程的堆中分配,但是堆分配出来的内存空间,系统一般不会回收,只有当进程的堆大小到达最大限额时
或者没有足够连续大小的空间来为进程继续分配所需内存时,才会回收不用的堆内存。在这种方式下,glibc会为进程堆维护一些固定大小的内存池以减少内存
碎片。
(2)、使用mmap的内存分配
在glibc中,一般在比较大的内存分配时使用mmap系统调用,它以页为单位来分配内存的,这不可避免会带来内存浪费,但是当进程调用free释放所分
配的内存时,glibc会立即调用unmmap,把所分配的内存空间释放回系统。
注意:这里我们讨论的都是虚拟内存的分配
(即应用层面上的内存分配),主要由glibc来实
现,它与内核中实际物理内存的分配是不同的层面,进程所分配到的虚拟内存可能没有对应的物理内存。如果所分配的虚拟内存没有对应的物理内存时,操作系统会
利用缺页机制来为进程分配实际的物理内存。
http://www.ibm.com/developerworks/cn/linux/l-memory/
分享到:
相关推荐
嵌入式Linux内存与性能详解-史子旺 本书非常详尽地讲解了 linux 内存相关知识与 linux 系统调优相关的工具
嵌入式Linux内存与性能详解.pdf 300多页 文本版 不是扫描版
详细介绍linux内核内存管理,从内核思想详细介绍伙伴算法、slab内存管理
linux下面的共享内存详解,详细介绍了共享内存的使用和药注意的事项
很好的嵌入式教材 讲解了: 进程,内存测量,linux内核的内存管理,内存分配和释放,以及内存空洞,内存优化等内容
linux 内存镜像图 Linux如何防止内存碎片 Linux 内存管理算法介绍 Linux对外提供的内存管理接口
Linux内存管理详解PPT课件.pptx
Linux内存管理详解学习教案.pptx
堆内存和栈内存详解.doc 堆内存和栈内存详解.doc 堆内存和栈内存详解.doc
详细讲述了Linux内存分配的机制以及如何优化嵌入式Linux系统的性能。
Linux内存管理详解PPT学习教案.pptx
《Linux PowerPC详解:核心篇》分8章,第1章讲述Linux PowerPC的组成;第2~4章讲述了有关PowerPC处理器的基础知识,包括指令集、寄存器、内存体系结构等;第5~8章讲述Linux系统在PowerPC处理器中的运行,包括进程...
此文档为结合网页上专业人士写的博客,总结提炼加笔记,以飨读者。
linux查看内存和CPU详解.pdflinux查看内存和CPU详解.pdf
1.在linux系统下安装跨系统传输文件工具 root用户下 根目录输入 yum -y install lrzsz 2.把apache-jmeter-4.0zip包 用rz命令上传到linux系统的根目录下 解压 3.配置jmeter环境变量 vim /etc/profile 添加 export...
详解linux内存磁盘初始化技术实用.pdf
Linux内核内存管理,很全的内存管理讲解,自己手动整理的,绝对原创。