`

Linux内存使用详解

阅读更多
一提到内存管理,我们头脑中闪出的两个概念,就是虚拟内存,与物理内存。这两个概念主要来自于linux内核的支持。

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

这里要提到一个很重要的概念,内存的延迟分配。Linux内核在用户申请内存的时候,只是给它分配了一个线性区(也就是虚存),并没有分配实际物理内存;只有当用户使用这块内存的时候,内核才会分配具体的物理页面给用户,这时候才占用宝贵的物理内存。内核释放物理页面是通过释放线性区,找到其所对应的物理页面,将其全部释放的过程。

       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顶的位置,将占用的内存返回给系统。这时,内核会通过删除相应的线性区,来释放占用的物理内存。



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

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

很遗憾,不能。

这也就是说,只要堆顶的部分申请内存还在占用,我在下面释放的内存再多,都不会被返回到系统中,仍然占用着物理内存。为什么会这样呢?

这主要是与内核在处理堆的时候,过于简单,它只能通过调整堆顶指针的方式来调整调整程序占用的线性区;而又只能通过调整线性区的方式,来释放内存。所以只要堆顶不减小,占用的内存就不会释放。



提一个问题:

char *p=malloc(2);

free(p)

为什么申请内存的时候,需要两个参数,一个是内存大小,一个是返回的指针;而释放内存的时候,却只要内存的指针呢?

这主要是和glibc的内存管理机制有关。glibc中,为每一块内存维护了一个chunk的结构。glibc在分配内存时,glibc先填写chunk结构中内存块的大小,然后是分配给进程的内存。

chunk ------size

p------------ content

在进程释放内存时,只要  指针-4 便可以找到该块内存的大小,从而释放掉。

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



glibc提供的调试工具:

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

对应的是一个函数指针,

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

其中 caller 是调用 malloc 返回值的接受者(一个指针的地址)。另外有 __malloc_initialize_hook函数指针,仅仅会调用一次(第一次分配动态内存时)。(malloc.h)



一些使用 malloc 的统计量(SVID 扩展)可以用 struct mallinfo 储存,

可调用获得。

struct mallinfo mallinfo (void)



如何检测 memory leakage?glibc 提供了一个函数

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

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

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

#ifdef DEBUGGING 包裹以便在非调试态下减少开销。产生的文件据说不建议自己去读,

而使用 mtrace 程序(perl 脚本来进行分析)。下面用一个简单的例子说明这个过程,这是

源程序:

#include <stdio.h>

#include <stdlib.h>

#include <mcheck.h>

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

该文件内容如下

= Star

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

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

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



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



代码占用的内存

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

在linux中,程序的加载,涉及到两个工具,linker 和loader。Linker主要涉及动态链接库的使用,loader主要涉及软件的加载。



1、  exec执行一个程序

2、  elf为现在非常流行的可执行文件的格式,它为程序运行划分了两个段,一个段是可以执行的代码段,它是只读,可执行;另一个段是数据段,它是可读写,不能执行。

3、  loader会启动,通过mmap系统调用,将代码端和数据段映射到内存中,其实也就是为其分配了虚拟内存,注意这时候,还不占用物理内存;只有程序执行到了相应的地方,内核才会为其分配物理内存。

4、  loader会去查找该程序依赖的链接库,首先看该链接库是否被映射进内存中,如果没有使用mmap,将代码段与数据段映射到内存中,否则只是将其加入进程的地址空间。这样比如glibc等库的内存地址空间是完全一样。



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



运行过程中链接动态链接库与编译过程中链接动态库的区别。

我们调用动态链接库有两种方法:一种是编译的时候,指明所依赖的动态链接库,这样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

输出解释

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

参数 解释 /proc//status

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



查看机器可用内存

/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的方式,来准确计算某一段虚拟地址所占用的内存,可能对我们有用。
分享到:
评论

相关推荐

    嵌入式Linux内存与性能详解-史子旺

    《嵌入式Linux内存与性能详解》一书由史子旺撰写,专注于深入解析Linux内存管理机制以及如何在嵌入式环境中优化系统性能。Linux内存管理是操作系统的核心部分,理解和掌握这一领域对于开发者来说至关重要,尤其是在...

    Linux内存管理详解.ppt

    Linux内存管理是操作系统的核心组成部分,尤其对于服务器和嵌入式设备等依赖稳定高效内存操作的环境至关重要。在Linux内核中,内存管理涉及到多个层面,包括分配、释放、碎片控制以及高速缓存优化等。 首先,Linux...

    嵌入式Linux内存与性能详解

    本资源《嵌入式Linux内存与性能详解》详细阐述了这些关键领域,为理解嵌入式系统的内在机制提供了深入见解。 1. **嵌入式系统概述**:嵌入式系统通常具有特定功能,它们在硬件限制下运行,如处理能力、内存大小和...

    linux 共享内存详解

    ### Linux共享内存详解 #### 一、概述 在Linux及Unix环境下,进程间通信(IPC, Inter-Process Communication)是一项重要的技术,它允许不同进程之间交换数据和信息。System V IPC提供了三种通信机制:消息队列、...

    Linux内存管理详解

    Linux内存管理是操作系统的核心组成部分,它负责有效地分配和管理系统的物理和虚拟内存资源。Linux内存管理系统包括多个层次和算法,旨在提高内存利用率,减少碎片,并确保系统的高效运行。 首先,Linux对外提供了...

    linux内存分析详解

    缺页是Linux内存管理中的一个重要概念,它发生在当一个进程试图访问尚未在物理内存中加载的虚拟页面时。此时,内核会触发缺页异常,然后根据页表找到对应的磁盘页,将其加载到内存中,并更新页表。缺页处理机制是...

    Linux内存管理详解PPT课件.pptx

    Linux内存管理详解PPT课件.pptx

    ARM嵌入式Linux系统开发详解.zip

    《ARM嵌入式Linux系统开发详解》是一本深入探讨如何在ARM处理器上构建和开发Linux系统的资源集合。ARM架构因其低功耗、高性能的特点,在嵌入式领域广泛应用,而Linux作为开源的操作系统,为开发者提供了丰富的工具和...

    Linux C 函数详解

    《Linux C 函数详解》是一本专为程序员和系统开发者设计的工具书,全面而深入地探讨了在Linux环境下使用C语言进行程序开发时所涉及的各种函数。这本书旨在帮助读者理解并熟练掌握C语言的标准库函数,以及在Linux系统...

    Linux编程技术详解

    总之,"Linux编程技术详解"资料涵盖了广泛的Linux编程主题,包括系统调用、库函数、插件开发、文档查阅、开发工具的使用以及高级编程概念。通过深入学习和实践,开发者能够掌握Linux环境下的程序设计技巧,为开发高...

    Linux内存管理详解学习教案.pptx

    Linux内存管理是操作系统的核心组成部分,尤其对于服务器和嵌入式设备等依赖Linux系统的平台来说,高效、稳定的内存管理至关重要。本教程将深入讲解Linux内存管理的相关知识点。 首先,Linux对外提供的内存管理接口...

    Linux top 命令详解

    "Linux top 命令详解" Linux top 命令是 Linux 下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于 Windows 的任务管理器。下面详细介绍它的使用方法。 认识 top 的显示结果 --------------...

    Linux设备驱动开发详解:基于最新的Linux4.0内核,linux设备驱动开发详解pdf,LINUX

    《Linux设备驱动开发详解》是一本深度探讨Linux内核驱动程序开发的专业书籍,它以Linux 4.0内核为背景,为驱动工程师提供了一条深入理解与实践Linux设备驱动的路径。这本书涵盖了许多关键的知识点,对于想要在Linux...

    Linux设备驱动详解第二版

    《Linux设备驱动开发详解(第《Linux设备驱动开发详解(第2版)》内容全面,实例丰富,操作性强,语言通俗易懂,适合广大Linux开发人员、嵌入式工程师参考使用。 图书目录 第1篇 Linux设备驱动入门 第1章 Linux...

    Linux 内存泄露查找

    ### Linux 内存泄露查找详解 #### 一、引言 在进行Linux C语言编程时,内存管理一直是程序员关注的重点之一。特别是在动态内存分配场景下,如果不妥善处理,很容易出现内存泄露的问题。内存泄露不仅会消耗系统资源...

    linux网络编程详解.pdf

    ### Linux网络编程详解知识点 #### 一、网络基础与历史 - **网络的历史**:介绍计算机网络的发展历程,包括早期的计算机网络是如何形成的,以及它们如何逐渐演变为现代互联网。 - **OSI模型**:详细解释开放系统...

    Linux查看CPU和内存使用情况

    ### Linux查看CPU和内存使用情况详解 在Linux系统管理中,了解CPU和内存的使用情况是至关重要的。这不仅有助于日常的系统监控,还能在问题出现时迅速定位并解决。本文将详细介绍如何在Linux环境下使用`top`命令查看...

Global site tag (gtag.js) - Google Analytics