Linux内存简单汇总

Linux内存主要用来存储系统和应用程序的指令,数据,缓存等

一,内存映射

1,内核给每个进程提供一个独立的虚拟机地址空间,并且这个地址空间是连续的

2,虚拟地址空间内部又被分为内核空间和用户空间

3,32位和64位系统的虚拟地址空间

32 位系统的内核空间占用 1G,位于最高处,剩下的 3G 是用户空间。而 64 位系统的内核空间和用户空间都是 128T,分别占据整个内存空间的最高和最低处,剩下的中间部分是未定义的

4,进程在用户态时,只能访问用户空间内存;只有进入内核态后,才可以访问内核空间内存

5,只有实际使用的虚拟内存才会被分配物理内存,通过内存映射来管理

6,内存映射,就是将虚拟内存地址映射到物理内存地址。为了完成内存映射,内核为每个进程都维护了一张 表,记录虚拟地址与物理地址的映射关系

7,页表存储在内存管理单元[MMU](https://blog.csdn.net/u010442934/article/details/79900449

8,进程访问虚拟地址在页表中查不到时,系统会产生一个缺页异常

9,TLB(Translation Lookaside Buffer,转译后备缓冲器)会影响 CPU 的内存访问性能 ,TLB是MMU中页表的高速缓存,。于进程的虚拟地址空间是独立的,而 TLB 的访问速度又比 MMU 快得多,所以,通过减少进程的上下文切换,减少 TLB 的刷新次数,就可以提高 TLB 缓存的使用率,进而提高 CPU 的内存访问性能

10,MMU 并不以字节为单位来管理内存,而是规定了一个内存映射的最小单位,也就是页,通常是 4 KB 大小,每一次内存映射,都需要关联 4 KB 或者 4KB整数倍的内存空间

11, 为了解决页表项过多的问题,Linux 提供了两种机制,也就是多级页表大页(HugePage)

多级页表

多级页表是把内存分成区块来管理,将原来的映射关系改成区块索引和区块内的偏移,由于虚拟内存空间通常只使用了一小部分,那么多级页表就只保存这些使用中的区块,这样就可以减少页表的项数

其中Linux就用了四级页表来管理内存页

虚拟地址被分了5部分,前四个表项用于选择页,最后一部分用于页内偏移

此处可参考内核必须懂(七): Linux四级页表(x64

大页

就是比普通页更大的内存块,常见的大小有 2MB 和 1GB。大页通常用在使用大量内存的进程上,比如 Oracle、DPDK 等

大页内存使用相关指令

cat /sys/devices/system/node/node*/meminfo | fgrep Huge
#查看各个numa节点的大页内存情况。

grep Huge /proc/meminfo
#查看大页内存使用情况

numactl --hradware
#查看系统numa架构,cpu分配情况

echo 64 > /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages
#为numa0节点分配64个2m的大页

mkdir -p /mnt/huge
mount -t hugetlbfs nodev /mnt/huge
#挂载大页,重启后失效

#永久挂在大页内存
vim /etc/fstab
nodev /mnt/huge hugetlbfs defaults 0 0 #挂载2M大页
nodev /mnt/huge_1GB hugetlbfs pagesize=1GB 0 0 #挂载1G的大页

#查看大页内存挂载情况
cat /proc/mounts

#查找正在使用大页的进程
find /proc/*/smaps | xargs grep -ril "anon_hugepage"

#取消挂载
umount /dev/hugepages
umount /mnt/huge

参考 linux大页内存

二,虚拟内存空间分布

1,32 位系统 内存地址空间分布

用户空间内存,从低到高分别是五种不同的内存段

  1. 只读段:包括代码和常量等。
  2. 数据段:包括全局变量等。
  3. 堆:包括动态分配的内存,从低地址开始向上增长。
  4. 文件映射段,包括动态库、共享内存等,从高地址开始向下增长。
  5. 栈,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB

在这五个内存段中,堆和文件映射段的内存是动态分配的。使用 C 标准库的malloc() 或者 mmap() ,就可以分别在堆和文件映射段动态分配内存

其他地方有以下的布局介绍

2,代码段

代码段中存放可执行的指令,在内存中,为了保证不会因为堆栈溢出被覆盖,将其放在了堆栈段下面。通常来讲代码段是共享的,这样多次反复执行的指令只需要在内存中驻留一个副本即可,比如 C 编译器,文本编辑器等。代码段一般是只读的,程序执行时不能随意更改指令,也是为了进行隔离保护。

3、初始化数据段

初始化数据段有时就称之为数据段。数据段是一个程序虚拟地址空间的一部分,包括一全局变量和静态变量,这些变量在编程时就已经被初始化。数据段是可以修改的,不然程序运行时变量就无法改变了,这一点和代码段不同。

数据段可以细分为初始化只读区和初始化读写区。这一点和编程中的一些特殊变量吻合。比如全局变量 int global n = 1就被放在了初始化读写区,因为 global 是可以修改的。而 const int m = 2 就会被放在只读区,很明显,m 是不能修改的。

4、未初始化数据段

未初始化数据段有时称之为 BSS 段,BSS 是英文 Block Started by Symbol 的简称,BSS 段属于静态内存分配。存放在这里的数据都由内核初始化为 0。未初始化数据段从数据段的末尾开始,存放有全部的全局变量和静态变量并被,默认初始化为 0,或者代码中没有显式初始化。比如 static int i; 或者全局 int j; 都会被放到BSS段。

5、栈

栈 (stack) 是现代计算机程序里最为重要的概念之一,几乎每一个程序都使用了栈,没有栈就没有函数,没有局部变量,也就没有我们如今能够看见的所有的计算机语言。在

传统的栈的定义:

在经典的计算机科学中,栈被定义为一个特殊的容器,用户可以将数据压入栈中(入栈,push,也可以将已经压入栈中的数据弹出(出栈, pop),但栈这个容器必须遵守一条规则:先入栈的数据后出栈(First In Last Out, FIFO),多多少少像叠成一叠的书:先叠上去的书在最下面:因此要最后才能取出。

在计算机系统中,栈则是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出。压栈操作使得栈增大,而弹出操作使栈减小

在i386下,栈顶由称为 esp 的寄存器进行定位。压栈的操作使栈顶的地址减小,弹出的操作使栈顶地址增大。

这里栈底的地址是 0xbffff,而 esp 寄存器标明了栈顶,地址为 0xbifff4。

在栈上压入数据会导致 esp 减小,弹出数据使得 esp 增大。

栈在程序运行中具有举足轻重的地位。最重要的,栈保存了一个函数调用所需要的维护信息,这常常被称为堆栈帧(Stack Frame)或活动记录(Activate Record),堆栈帧一般包括如下几方面内容:

1、函数的返回地址和参数。
2、临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量。
3、保存的上下文:包括在函数调用前后需要保持不变的寄存器。

6、堆

相对于栈,堆这片内存面临着一个稍微复杂的行为模式:在任意时刻,程序可能发出请求,要么申请一段内存,要么释放一段已经申请过的内存,而且申请的大小从几个字节到数 GB 都是有可能的,我们不能假设程序会一次申请多少堆空间,因此,堆的管理显得较为复杂。

为什么需要堆?

光有栈,对于面向过程的程序设计还远远不够,因为栈上的数据在函数返回的时候就会被释放掉,所以无法将数据传递至函数外部。而全局变量没有办法动态地产生,只能在编译的时候定义,有很多情况下缺乏表现力,在这种情况下,堆(Heap)是一种唯一的选择。

堆是一款巨大的内存空间,常常占据整个虚拟空间的绝大部分,在这片空间里,程序可以请求一块连续的内存,并自由地使用,这块内存在程序主动放弃之前都活一直保持有效,下面是一个申请堆空间最简单的例子:

int main()
{
    char* p = (char*) malloc(233);
    free(p);
    return 0;
}

在第 3 行用 malloc 申请了 233 个字节的空间之后,程序可以自由地使用这 233个字节,直到程序用free函数释放它。

那么 malloc 到底是怎么实现的呢?

有一种做法是,把进程的内存管理交给操作系统内核去做,既然内核管理着进程的地址空间,那么如果它提供一个系统调用,可以让程序使用这个系统调用申请内存,不就可以了吗?

当然这是一种理论上可行的做法,但实际上这样做的性能比较差,原因在于每次程序申请或者释放堆空间都需要进行系统调用。

我们知道系统调用的性能开销是很大的,当程序对堆的操作比较频繁时,这样做的结果是会严重影响程序的性能的。

比较好的做法就是:程序向操作系统申请一块适当大小的堆空间,然后由程序自己管理这块空间,而具体来讲,管理着堆空间分配的往往是程序的运行库。

运行库相当于是向操作系统 “批发” 了一块较大的堆空间,然后 “零售” 给程序用。

当全部“售完”或程序有大量的内存需求时,再根据实际需求向操作系统“进货”。

当然运行库在向程序零售堆空间时,必须管理它批发来的堆空间,不能把同一块地址出售两次,导致地址的冲突。

7、Linux 进程堆管理

进程的地址空间中,除了可执行文件,共享库和栈之外,剩余的未分配的空间都可以用来作为堆空间。

Linux 系统下,提供两种堆空间分配方式,两个系统调用:brk() 系统调用 和 mmap() 系统调用

这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。

在标准 C 库中,提供了malloc/free函数分配释放内存,这两个函数底层是由 brk,mmap,munmap 这些系统调用实现的。

brk() 系统调用

C 语言形式声明:int brk() {void* end_data_segment;}
brk() 的作用实际上就是设置进程数据段的结束地址,即它可以扩大或者缩小数据段(Linux 下数据段和 BBS 合并在一起统称数据段)。
如果我们将数据段的结束地址向高地址移动,那么扩大的那部分空间就可以被我们使用,把这块空间拿过来使用作为堆空间是最常见的做法。

mmap() 系统调用

和 Windows 系统下的 VirtualAlloc 很相似,它的作用就是向操作系统申请一段虚拟地址空间,(堆和栈中间,称为文件映射区域的地方)这块虚拟地址空间可以映射到某个文件。
glibc 的 malloc 函数是这样处理用户的空间请求的:对于小于 128KB 的请求来说,它会在现有的堆空间里面,按照堆分配算法为它分配一块空间并返回;对于大于128KB 的请求来说,它会使用 mmap() 函数为它分配一块匿名空间,然后在这个匿名空间中为用户分配空间。

声明如下:

void* mmap{
    void* start;
    size_t length;
    int prot;
    int flags;
    int fd;
    off_t offset;
}

mmap 前两个参数分别用于指定需要申请的空间的起始地址和长度,如果起始地址设置 0,那么 Linux 系统会自动挑选合适的起始地址。
prot/flags 参数:用于设置申请的空间的权限(可读,可写,可执行)以及映射类型(文件映射,匿名空间等)。
最后两个参数用于文件映射时指定的文件描述符和文件偏移的。

了解了 Linux 系统对于堆的管理之后,可以再来详细这么一个问题,那就是 malloc 到底一次能够申请的最大空间是多少?

为了回答这个问题,就不得不再回头仔细研究一下之前的图一。我们可以看到在有共享库的情况下,留给堆可以用的空间还有两处。第一处就是从 BSS 段结束到 0x40 000 000 即大约 1GB 不到的空间;

第二处是从共享库到栈的这块空间,大约是 2GB 不到。这两块空间大小都取决于栈、共享库的大小和数量。

于是可以估算到 malloc 最大的申请空间大约是 2GB 不到。(Linux 内核 2.4 版本)。

还有其它诸多因素会影响 malloc 的最大空间大小,比如系统的资源限制(ulimit),物理内存和交换空间的总和等。mmap 申请匿名空间时,系统会为它在内存或交换空间中预留地址,但是申请的空间大小不能超过空闲内存+空闲交换空间的总和。

内存分配与回收

分配

malloc() 是 C 标准库提供的内存分配函数,对应到系统调用上,有两种实现方式,即brk() 和 mmap()。、

对小块内存(小于 128K),C 标准库使用 brk() 来分配,也就是通过移动堆顶的位置来分配内存。这些内存释放后并不会立刻归还系统,而是被缓存起来,这样就可以重复使用。

而大块内存(大于 128K),则直接使用内存映射 mmap() 来分配,也就是在文件映射段找一块空闲内存分配出去。

brk() 方式的缓存,可以减少缺页异常的发生,提高内存访问效率。不过,由于这些内存没有归还系统,在内存工作繁忙时,频繁的内存分配和释放会造成内存碎片。

mmap() 方式分配的内存,会在释放时直接归还系统,所以每次 mmap 都会发生缺页异常。在内存工作繁忙时,频繁的内存分配会导致大量的缺页异常,使内核的管理负担增大。这也是 malloc 只对大块内存使用 mmap 的原因。

当这两种调用发生后,其实并没有真正分配内存。这些内存,都只在首次访问时才分配,也就是通过缺页异常进入内核中,再由内核来分配内存。

回收

系统也不会任由某个进程用完所有内 存,在发现内存紧张时,系统就会通过一系列机制来回收内存,比如下面这三种方式

1,回收缓存。比如使用LRU算法,回收最近使用最少的内存页

2,回收不常访问的内存,把不常用的内存通过交换分区写进磁盘

3,杀死进程,内存紧张时系统通过OOM,直接杀死占用大量的内存的进程

如何查看内存使用情况

1,free

free 输出的是一个表格,其中的数值都默认以字节为单位。表格总共有两行六列,这两行分别是物理内存 Mem 和交换分区 Swap 的使用情况,而六列中,每列数据的含义分别为:

第一列,total 是总内存大小;
第二列,used 是已使用内存的大小,包含了共享内存;
第三列,free 是未使用内存的大小;
第四列,shared 是共享内存的大小;
第五列,buff/cache 是缓存和缓冲区的大小;
最后一列,available 是新进程可用内存的大小

最后一列的可用内存 available 。available 不仅包含未使用内存,还包括了可回收的缓存,所以一般会比未使用内存更大。不过,并不是所有缓存都可以回收,因为有些缓存可能正在使用中

2,top

top 输出界面的顶端,也显示了系统整体的内存使用情况,这些数据跟 free 类似,我就不再重复解释。我们接着看下面的内容,跟内存相关的几列数据,比如 VIRT、RES、SHR 以及 %MEM 等。这些数据,包含了进程最重要的几个内存使用情况:

VIRT 是进程虚拟内存的大小,只要是进程申请过的内存,即便还没有真正分配物理内存,也会计算在内。
RES 是常驻内存的大小,也就是进程实际使用的物理内存大小,但不包括 Swap 和共享内存。
SHR 是共享内存的大小,比如与其他进程共同使用的共享内存、加载的动态链接库以及程序的代码段等。
%MEM 是进程使用物理内存占系统总内存的百分比 

另外还需要注意:

第一,虚拟内存通常并不会全部分配物理内存。从上面的输出,你可以发现每个进程的虚拟内存都比常驻内存大得多。
第二,共享内存SHR并不一定是共享的,比方说,程序的代码段、非共享的动态链接库,也都算在SHR里。当然,SHR也包括了进程间真正共享的内存。所以在计算多个进程的内存使用时,不要把所有进程的SHR 直接相加得出结果。

本文来源

1,https://zhuanlan.zhihu.com/p/77122692

2,极客时间-Linux性能优化

原文地址:https://www.cnblogs.com/mrwuzs/p/11372969.html

时间: 2024-10-30 02:54:49

Linux内存简单汇总的相关文章

linux命令简单汇总,直通车

curl nohup jobs screen exec   pk    xargs linux 系统日志 kill ntsys    chkconfig crontab     linux  任务日志 netfilter   iptables selinux     getenforce wireshark tcpdump   抓包工具 netstat   - lnp free pid ps aux ps aux | grep  nginx sar top w uptime vmstat 1 p

Linux 常用命令汇总

Linux 常用命令汇总 1. 显示日期与时间:date 可修改显示日期格式,如:date +%Y/%m/%d %H:%M à2016/12/25 17:05(%Y:年:%m:月:%d:日:%H:24小时制:%M:分) date –d 参数后可加:today/yesterday/tomorrow 来显示今天.昨天.明天日期 2. 显示日历:cal(默认显示本月月历) cal 2001 可显示2001年整年日历:cal 10 2001 可具体显示某月月历 3.  计算器:bc 4. Tab键:用在

linux内存管理

一.Linux 进程在内存中的数据结构 一个可执行程序在存储(没有调入内存)时分为代码段,数据段,未初始化数据段三部分:    1) 代码段:存放CPU执行的机器指令.通常代码区是共享的,即其它执行程序可调用它.假如机器中有数个进程运行相同的一个程序,那么它们就可以使用同一个代码段.     2) 数据段:存放已初始化的全局变量.静态变量(包括全局和局部的).常量.static全局变量和static函数只能在当前文件中被调用.     3) 未初始化数据区(uninitializeddata s

Linux内存管理 【转】

转自:http://blog.chinaunix.net/uid-25909619-id-4491368.html Linux内存管理 摘要:本章首先以应用程序开发者的角度审视Linux的进程内存管理,在此基础上逐步深入到内核中讨论系统物理内存管理和内核内存的使用方法.力求从外到内.水到渠成地引导网友分析Linux的内存管理与使用.在本章最后,我们给出一个内存映射的实例,帮助网友们理解内核内存管理与用户内存管理之间的关系,希望大家最终能驾驭Linux内存管理. 前言 内存管理一向是所有操作系统书

linux内存管理浅析

[地址映射](图:左中)linux内核使用页式内存管理,应用程序给出的内存地址是虚拟地址,它需要经过若干级页表一级一级的变换,才变成真正的物理地址.想一下,地址映射还是一件很恐怖的事情.当访问一个由虚拟地址表示的内存空间时,需要先经过若干次的内存访问,得到每一级页表中用于转换的页表项(页表是存放在内存里面的),才能完成映射.也就是说,要实现一次内存访问,实际上内存被访问了N+1次(N=页表级数),并且还需要做N次加法运算.所以,地址映射必须要有硬件支持,mmu(内存管理单元)就是这个硬件.并且需

启动期间的内存管理之初始化过程概述----Linux内存管理(九)

日期 内核版本 架构 作者 GitHub CSDN 2016-06-14 Linux-4.7 X86 & arm gatieme LinuxDeviceDrivers Linux内存管理 在内存管理的上下文中, 初始化(initialization)可以有多种含义. 在许多CPU上, 必须显式设置适用于Linux内核的内存模型. 例如在x86_32上需要切换到保护模式, 然后内核才能检测到可用内存和寄存器. 而我们今天要讲的boot阶段就是系统初始化阶段使用的内存分配器. 1 前景回顾 1.1

linux内存源码分析 - 内存压缩(同步关系)

本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 概述 最近在看内存回收,内存回收在进行同步的一些情况非常复杂,然后就想,不会内存压缩的页面迁移过程中的同步关系也那么复杂吧,带着好奇心就把页面迁移的源码都大致看了一遍,还好,不复杂,也容易理解,这里我们就说说在页面迁移过程中是如何进行同步的.不过首先可能没看过的朋友需要先看看linux内存源码分析 - 内存压缩(一),因为会涉及里面的一些知识. 其实一句话可以概括页面迁移时是如何进行同步的,就是:我要开始对这

Cgroup - Linux 内存资源管理

Hi ,我是 Zorro .这是我的微博地址,我会不定期在这里更新文章,如果你有兴趣,可以来关注我呦. 另外,我的其他联系方式: Email: [email protected] QQ: 30007147 本文PDF 在聊 cgroup 的内存限制之前,我们有必要先来讲解一下: Linux 内存管理基础知识 free 命令 无论从任何角度看, Linux 的内存管理都是一坨麻烦的事情,当然我们也可以用一堆.一片.一块.一筐来形容这个事情,但是毫无疑问,用一坨来形容它简直恰当无比.在理解它之前,我

linux内存管理---物理地址、线性地址、虚拟地址、逻辑地址之间的转换

linux内存管理---虚拟地址.逻辑地址.线性地址.物理地址的区别(一) 这篇文章中介绍了四个名词的概念,下面针对四个地址的转换进行分析 CPU将一个虚拟内存空间中的地址转换为物理地址,需要进行两步(如下图): 首先,将给定一个逻辑地址(其实是段内偏移量,这个一定要理解!!!),CPU要利用其段式内存管理单元,先将为个逻辑地址转换成一个线程地址, 其次,再利用其页式内存管理单元,转换为最终物理地址. 这样做两次转换,的确是非常麻烦而且没有必要的,因为直接可以把线性地址抽像给进程.之所以这样冗余