您的位置 首页 观点

具体介绍Linux 内存使用方法

详细介绍Linux 内存使用方法-Linux在内存管理上份为两级,一级是线性区,类似于00c73000-00c88000,对应于虚拟内存,它实际上不占用实际物理内存;一级是具体的物理页面,它对应我们机器上的物理内存。

一说到内存办理,咱们头脑中闪出的两个概念,便是虚拟内存,与物理内存。这两个概念首要来自于linux内核的支撑。

Linux在内存办理上份为两级,一级是线性区,类似于00c73000-00c88000,对应于虚拟内存,它实践上不占用实践物理内存;一级是详细的物理页面,它对应咱们机器上的物理内存。

这儿要说到一个很重要的概念,内存的推迟分配。Linux内核在用户请求内存的时分,仅仅给它分配了一个线性区(也便是虚存),并没有分配实践物理内存;只需当用户运用这块内存的时分,内核才会分配详细的物理页面给用户,这时分才占用名贵的物理内存。内核开释物理页面是经过开释线性区,找到其所对应的物理页面,将其悉数开释的进程。

[cpp] view plain copy

print?

char *p=malloc(2048) //这儿仅仅分配了虚拟内存2048,并不占用实践内存。  

strcpy(p,”123”) //分配了物理页面,尽管仅仅运用了3个字节,但内存仍是为它分配了2048字节的物理内存。  

free(p) //经过虚拟地址,找到其所对应的物理页面,开释物理页面,开释线性区。  

咱们知道用户的进程和内核是运转在不同的等级,进程与内核之间的通讯是经过体系调用来完结的。进程在请求和开释内存,首要经过brk,sbrk,mmap,unmmap这几个体系调用,传递的参数首要是对应的虚拟内存。

留意一点,在进程只能拜访虚拟内存,它实践上是看不到内核物理内存的运用,这关于进程是彻底通明的。

glibc内存办理器

那么咱们每次调用malloc来分配一块内存,都进行相应的体系调用呢?

答案是否定的,这儿我要引进一个新的概念,glibc的内存办理器。

咱们知道malloc和free等函数都是包含在glibc库里面的库函数,咱们试想一下,每做一次内存操作,都要调用体系调用的话,那么程序将多么的低效。

实践上glibc选用了一种批发和零售的办法来办理内存。glibc每次经过体系调用的办法请求一大块内存(虚拟内存),当进程请求内存时,glibc就从自己取得的内存中取出一块给进程。

内存办理器面对的困难

咱们在写程序的时分,每次请求的内存块巨细不规则,而且存在频频的请求和开释,这样不可避免的就会发生内存碎块。而内存碎块,直接会导致大块内存请求无法满意,然后更多的占用体系资源;假如进行碎块收拾的话,又会添加cpu的负荷,许多都是互相矛盾的目标,这儿我就不细说了。

咱们在写程序时,触及内存时,有两个概念heap和stack。传统的说法stack的内存地址是向下增加的,heap的内存地址是向上增加的。

函数malloc和free,首要是针对heap进行操作,由程序员自主操控内存的拜访。

在这儿heap的内存地址向上增加,这句话不彻底正确。

glibc关于heap内存请求大于128k的内存请求,glibc选用mmap的办法向内核请求内存,这不能确保内存地址向上增加;小于128k的则选用brk,关于它来讲是正确的。128k的阀值,能够经过glibc的库函数进行设置。

这儿我先讲大块内存的请求,也即对应于mmap体系调用。

关于大块内存请求,glibc直接运用mmap体系调用为其划分出另一块虚拟地址,供进程独自运用;在该块内存开释时,运用unmmap体系调用将这块内存开释,这个进程中心不会发生内存碎块等问题。

针对小块内存的请求,在程序发动之后,进程会取得一个heap底端的地址,进程每次进行内存请求时,glibc会将堆顶向上增加来扩展内存空间,也便是咱们所说的堆地址向上增加。在对这些小块内存进行操作时,便会发生内存碎块的问题。实践上brk和sbrk体系调用,便是调整heap顶地址指针。

那么heap堆的内存是什么时分开释呢?

当glibc发现堆顶有接连的128k的空间是闲暇的时分,它就会经过brk或sbrk体系调用,来调整heap顶的方位,将占用的内存回来给体系。这时,内核会经过删去相应的线性区,来开释占用的物理内存。

下面我要讲一个内存空泛的问题:

一个场景,堆顶有一块正在运用的内存,而下面有很大的接连内存现已被开释掉了,那么这块内存是否能够被开释?其对应的物理内存是否能够被开释?

很惋惜,不能。

这也便是说,只需堆顶的部分请求内存还在占用,我在下面开释的内存再多,都不会被回来到体系中,依然占用着物理内存。为什么会这样呢?

这首要是与内核在处理堆的时分,过于简略,它只能经过调整堆顶指针的办法来调整调整程序占用的线性区;而又只能经过调整线性区的办法,来开释内存。所以只需堆顶不减小,占用的内存就不会开释。

提一个问题:

1

2

char *p=malloc(2);

free(p)

为什么请求内存的时分,需求两个参数,一个是内存巨细,一个是回来的指针;而开释内存的时分,却只需内存的指针呢?

这首要是和glibc的内存办理机制有关。glibc中,为每一块内存保护了一个chunk的结构。glibc在分配内存时,glibc先填写chunk结构中内存块的巨细,然后是分配给进程的内存。

1

2

chunk ——size

p———— content

在进程开释内存时,只需 指针-4 便能够找到该块内存的巨细,然后开释掉。

注:glibc在做内存请求时,最少分配16个字节,以便能够保护chunk结构。

glibc供给的调试东西:

为了便利调试,glibc 为用户供给了 malloc 等等函数的钩子(hook),如 __malloc_hook

对应的是一个函数指针,

1

void *function (size_t size, const void *caller)

其间 caller 是调用 malloc 回来值的接受者(一个指针的地址)。别的有 __malloc_iniTIalize_hook函数指针,仅仅会调用一次(榜首次分配动态内存时)。(malloc.h)

一些运用 malloc 的核算量(SVID 扩展)能够用 struct mallinfo 贮存,可调用取得。

1

struct mallinfo mallinfo (void)

怎么检测 memory leakage?glibc 供给了一个函数

void mtrace (void)及其反作用void muntrace (void)

这时会依靠于一个环境变量 MALLOC_TRACE 所指的文件,把一些信息记录在该文件中

用于侦测 memory leakage,其本质是安装了前面说到的 hook。一般将这些函数用

#ifdef DEBUGGING 包裹以便在非调试态下削减开支。发生的文件听说不主张自己去读,

而运用 mtrace 程序(perl 脚原本进行剖析)。下面用一个简略的比方阐明这个进程,这是

源程序:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

#include

#include

#include

intmain( int argc, char *argv[] )

{

int *p, *q ;

#ifdef DEBUGGING

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 没有被开释。咱们设置了环境变量后而且 touch 出该文件

履行成果如下:

p = 0x98c0378q = 0x98c0388

该文件内容如下

1

2

3

4

= Start

@./test30:[0x8048446] + 0x98c0378 0x4

@./test30:[0x8048455] + 0x98c0388 0x4

@./test30:[0x804848f] – 0x98c0378

到这儿我基本上讲完了,咱们写程序时,数据部分内存运用的问题。

代码占用的内存

数据部分占用内存,那么咱们写的程序是不是也占用内存呢?

在linux中,程序的加载,触及到两个东西,linker 和loader。Linker首要触及动态链接库的运用,loader首要触及软件的加载。

exec履行一个程序

elf为现在十分盛行的可履行文件的格局,它为程序运转划分了两个段,一个段是能够履行的代码段,它是只读,可履行;另一个段是数据段,它是可读写,不能履行。

loader会发动,经过mmap体系调用,将代码端和数据段映射到内存中,其实也便是为其分配了虚拟内存,留意这时分,还不占用物理内存;只需程序履行到了相应的当地,内核才会为其分配物理内存。

loader会去查找该程序依靠的链接库,首先看该链接库是否被映射进内存中,假如没有运用mmap,将代码段与数据段映射到内存中,不然仅仅将其参加进程的地址空间。这样比方glibc等库的内存地址空间是彻底相同。

因而一个2M的程序,履行时,并不意味着为其分配了2M的物理内存,这与其运转了的代码量,与其所依靠的动态链接库有关。

运转进程中链接动态链接库与编译进程中链接动态库的差异

咱们调用动态链接库有两种办法:一种是编译的时分,指明所依靠的动态链接库,这样loader能够在程序发动的时分,来一切的动态链接映射到内存中;一种是在运转进程中,经过dlopen和dlfree的办法加载动态链接库,动态将动态链接库加载到内存中。

这两种办法,从编程视点来讲,榜首种是最便利的,功率上影响也不大,在内存运用上有些不同。

榜首种办法,一个库的代码,只需运转过一次,便会占用物理内存,之后即便再也不运用,也会占用物理内存,直到进程的停止。

第二中办法,库代码占用的内存,能够经过dlfree的办法,开释掉,回来给物理内存。

这个不同首要关于那些寿数很长,但又会偶然调用各种库的进程有关。假如是这类进程,主张选用第二种办法调用动态链接库。

占用内存的丈量

丈量一个进程占用了多少内存,linux为咱们供给了一个很便利的办法,/proc目录为咱们供给了一切的信息,实践上top等东西也经过这儿来获取相应的信息。

1

2

3

4

5

/proc/meminfo 机器的内存运用信息

/proc/pid/maps pid为进程号,显现当时进程所占用的虚拟地址。

/proc/pid/statm 进程所占用的内存

[root@localhost ~]# cat /proc/self/statm

654 57 44 0 0 334 0

输出解说

CPU 以及CPU0。。。的每行的每个参数意思(以榜首行为例)为:

参数 解说 /proc//status

1

2

3

4

5

6

7

Size (pages) 使命虚拟地址空间的巨细 VmSize/4

Resident(pages) 应用程序正在运用的物理内存的巨细 VmRSS/4

Shared(pages) 同享页数 0

Trs(pages) 程序所具有的可履行虚拟内存的巨细 VmExe/4

Lrs(pages) 被映像到使命的虚拟内存空间的库的巨细 VmLib/4

Drs(pages) 程序数据段和用户态的栈的巨细 (VmData+ VmStk )4

dt(pages) 04

检查机器可用内存

1

2

3

4

5

/proc/28248/>free

total used free shared buffers cached

Mem: 1023788 926400 97388 0 134668 503688

-/+ buffers/cache: 288044 735744

Swap: 1959920 89608 1870312

咱们经过free指令检查机器闲暇内存时,会发现free的值很小。这首要是由于,在linux中有这么一种思维,内存不用白不用,因而它尽可能的cache和buffer一些数据,以便利下次运用。但实践上这些内存也是能够马上拿来运用的。

所以 闲暇内存=free+buffers+cached=total-used

检查进程运用的内存

检查一个进程运用的内存,是一个很令人困惑的工作。由于咱们写的程序,必定要用到动态链接库,将其参加到自己的地址空间中,可是/proc/pid/statm核算出来的数据,会将这些动态链接库所占用的内存也简略的算进来。

这样带来的问题,动态链接库占用的内存有些是其他程序运用时占用的,却算在了你这儿。你的程序中包含了子进程,那么有些动态链接库重用的内存会被重复核算。

因而要想精确的评价一个程序所占用的内存是好不容易的,经过写一个module的办法,来精确核算某一段虚拟地址所占用的内存,可能对咱们有用。

 

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/news/guandian/99819.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部