Linux性能优化-内存基本原理

目录

内存映射

虚拟内存布局

内存分配和回收

查看内存使用情况

buffer和cache

测试Buffers和Cached

参考


 


内存映射

Linux内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的,这样进程就可以很方便的访问内存,也就是访问虚拟内存
虚拟地址空间的内部又被分为 内核空间和用户空间 两部分,不同字长(也就是单个CPU指令可以处理数据的最大长度)的处理器,地址空间的范围也不同,如32位和64位系统

进程在用户态时,只能访问用户空间内存,只有进入内核态后,才可以访问内核空间内存,虽然每个进程的地址空间都包含了内核空间,但这些内核空间,其实关联的都是相同的物理内存,这样进程切换到内核态后,就可以很方便的访问内核空间内存
并不是所有的虚拟内存都会分配物理内存,只有那些实际使用的虚拟内存才分配物理内存,并且分配后的物理内存,是通过内存映射来管理的
内存映射,就是将 虚拟内存地址 映射到 物理内存地址,为了完成内存映射,内核为每个进程都维护了一张页表,记录虚拟地址与物理地址的映射关系


页表存储在CPU的内存管理单元MMU中,处理器可以直接通过硬件,找出要访问的内存
当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常,进入内核空间分配物理内存,更新进程页表,最后再返回用户空间,恢复进程的运行
TLB Translation Lookaside Buffer 转译后备缓冲器 会影响CPU的内存访问性能
TLB是MMU中页表的高速缓存,由于进程的虚拟地址空间是独立的,而TLB的访问速度又比MMU快得多,所以通过减少进程的上下文切换,减少TLB的刷新次数,就可以提高TLB缓存的使用率,进而提高CPU的内存访问性能

MMU通常规定内存映射的最小单位是4KB,每一次内存映射到需要关联4BK或者4BK整数倍数的内存空间
页的大小只有4KB,导致另一个问题就是,整个页表会变得非常大,比如32位系统就需要100度万页表项目(4GB/4KB),才可以实现整个 地址空间的映射,为了解决页表项过多的问题,linux提供了两种机制
多级页表,大页(HugePage)

多级页表把内存分成区块来管理,将原来的映射关系盖茨区块索引和区块内的偏移,由于虚拟内存空间通常只用了很少一部分,那么多级页表就只保存这些使用中的区块,这样就可以大大减少页表的项数
linux使用四级页表来管理内存页的,虚拟地址被分成5个部分,前4个表项用于选择页,最后一个索引表示页内偏移

大页一般是2M和1G,大页通常用在使用大量内存的进程上,比如Oracle,DPDK等


虚拟内存布局

最上方的是内核空间,下方的是用户空间内存,用户空间又被分成多个不同的段

用户空间内存,从低到高分别是5种不同的内存段
1.只读段,包括代码和常量等
2.数据段,包括全景变量等
3.堆,包括动态分配的内存,从低地址开始向上增长
4.文件映射段,包括动态库,共享内存等,从高地址开始向下增长
5.栈,包括局部变量和函数调用的上下文等,栈的大小是固定的,一般是8M
这5个内存段中,堆和文件映射的内存是动态分配的,比如使用C标准库的malloc或mmap(),就可以分别在堆和文件映射段动态分配内存
64位系统的内存分布也是类似的,只是内存空间要大的多

内存分配和回收

malloc()是C标准库提供的内存分配函数,对应到系统调用上,有两种实现方式,brk()和mmap()
1.对于小内存(小于128K),C标准库使用brk()来分配,也即使通移动堆顶的位置来分配内存,这些内存
  释放后并不会立刻归还系统,而是被缓存起来,这样就可以重复使用
2.大块内存(大于128K),使用内存映射mmap()来分配,也就是在文件映射段找一块空闲内存分配出去

brk方式的缓存,可以减少缺页异常的发生,提高内存访问效率,但由于内存还没有归还系统,在内存工作繁忙时,频繁的内存分配和释放会导致内存碎片
mmap方式分配的内存,在释放时直接归还系统,所以每次mmap都会发生缺页异常,在内存工作繁忙时,频繁的内存分配会导致大量的缺页异常,使得内核的管理负担增大,这也就是malloc支队大块内存使用mmap的原因

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

linux使用伙伴系统来管理内存分配,这些内存在MMU中以页为单位进程管理,伙伴系统也一样,以页为单位来管理内存,并且会通过相邻页的合并,减少内存碎片化(比如brk方式造成的内存碎片)

如果遇到比页更小的对象,比如不到1K的时候,也会需要单独分配一个页,这就太浪费了
所以在用户空间,malloc通过brk分配的内存,在释放时并不会立即归还系统,而是缓存起来重复利用,在内核空间,linux则通过slab分配器来管理小内存,可以将slab看成构建在货币系统上的一个缓存, 主要作用就是分配并释放内核中的小对象


对内存来说,只分配而不释放就会造成内存泄露,所以程序用完内存需要调用free或unmap来释放这些不用的内存,对于系统也会任由某个进程用完所有的内存,在发现内存紧张时,系统会有一些策略
1.回收缓存,比如使用LRU(least recently used)算法,回收最近使用最少的内存页面
2.回收不常访问的内存,把不常用的内存通过交换分区直接写到磁盘中
3.杀死进程,内存紧张时系统会通过OOM(out of memory)直接杀掉占用大量内存的进程

第二种方式回收不常访问的内存时,会用到交换分区swap,swap把一块磁盘空间当内存来用,可以把进程暂时不用的数据存储到磁盘中(换出),当进程访问这些内存时,再从磁盘读取这些数据到内存(换入)
所以swap把系统的可用内存变大了,但只有在内存不足时,才会发生swap交换,并且由于磁盘读写的速度远比内存慢,swap会导致严重的内存性能问题

第三种方式的OOM,是内核提供的一种保护机制,它监控进程的内存使用情况,并使用oom_score为每个进程的内存使用情况进行评分
a.一个进程消耗的内存越大,oom_score就越大
b.一个进程运行占用的CPU越多,oom_score就越小
这样进程的oom_score越大,就表示消耗的内存越多,也就越容易被OOM杀死,从而可以更好保护系统
为了实际工作的需要,管理员可以通过/proc文件系统,手动设置进程oom_adj,从而调整进程的oom_score,这个值的范围是[-17,15],值越大越容易被OOM杀死,数值越小表示进程越不容易被OOM杀死,-17表示禁止OOM
通过下面命令将sshd进程的oom_adj调整为-16,sshd进程就不容易被OOM杀死

echo -16 > /proc/$(pidof sshd)/oom_adj


查看内存使用情况

free命令的输出

free
              total        used        free      shared  buff/cache   available
Mem:        1016092       79144       84628         352      852320      761560
Swap:             0           0           0

free输出的是一个表格,其中数值都默认以字节为单位,两行分别是物理内存Mem和交换分区Swap的使用情况,6列中每列的数据的含义分别是
1.total,总内存大小
2.used,已使用内存的大小,包含了共享内存
3.free,未使用内存的大小
4.shared,共享内存的大小
5.buff/cache,缓存和缓冲区的大小
6.available,新进程可用内存的大小
注意,最后一列的可用内存available,不仅包含未使用的内存,还包括了可回收的缓存,所以一般会比未使用的内存更大,但并不是所有缓存都可以回收,因为有些缓存可能正在使用中

free是整个系统的内存使用情况,查看进程内存使用情况可以用ps或top

top - 09:02:38 up 18 days, 19:51,  1 user,  load average: 0.00, 0.01, 0.05
Tasks:  62 total,   1 running,  61 sleeping,   0 stopped,   0 zombie
%Cpu(s):  6.2 us,  0.0 sy,  0.0 ni, 93.8 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  1016092 total,    83976 free,    79664 used,   852452 buff/cache
KiB Swap:        0 total,        0 free,        0 used.   760992 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                                                                             
    1 root      20   0   43280   3760   2528 S  0.0  0.4   0:20.64 systemd                                                                                             
    2 root      20   0       0      0      0 S  0.0  0.0   0:00.24 kthreadd     

top的顶端也包含了系统整体内存使用情况跟free类似
其中一些列的含义
1.VIRT,进程虚拟内存的大小,只要进程申请过的内存,即便还没有真正分配物理内存,也会计算在内
2.RES,常驻内存的大小,也就是进程实际使用的物理内存大小,但不包含swap和共享内存
3.SHR,共享内的大小,比如和其他进程共同使用的共享内存,加载的功动态链接以及程序代码段等
4.%MEM,进程使用物理内存占系统总内存的百分比

需要注意的是
1.虚拟内存通常并不会全部分配物理内存,所以每个进程的虚拟内存都比常驻内存要大
2.共享内存SHR并不一定是共享的,如程序的代码段,非共享的动态链接库,也都算在SHR里,SHR也包括了
  进程真正共享的内存,所以在计算多个进程的内存使用时,不要把所有进程的SHR直接相加得出结果
 

buffer和cache

free命令中buffer和cache都表示缓存,但用途不一样
1.Buffer,是内核缓冲区用到的内存,对应的是/proc/meminfo中的Buffer值
2.Cache,是内核页缓存和Slab用到的内存,对应的是/proc/meminfo中的Cache和SReclaimable之和

man free 其中的描述部分如下
DESCRIPTION
       free  displays  the total amount of free and used physical and swap memory in the system, as well as the buffers and caches used by the kernel. The informa‐
       tion is gathered by parsing /proc/meminfo. The displayed columns are:

       total  Total installed memory (MemTotal and SwapTotal in /proc/meminfo)

       used   Used memory (calculated as total - free - buffers - cache)

       free   Unused memory (MemFree and SwapFree in /proc/meminfo)

       shared Memory used (mostly) by tmpfs (Shmem in /proc/meminfo, available on kernels 2.6.32, displayed as zero if not available)

       buffers
              Memory used by kernel buffers (Buffers in /proc/meminfo)

       cache  Memory used by the page cache and slabs (Cached and Slab in /proc/meminfo)

       buff/cache
              Sum of buffers and cache

       available
              Estimation of how much memory is available for starting new applications, without swapping. Unlike the data provided by the  cache  or  free  fields,
              this  field takes into account page cache and also that not all reclaimable memory slabs will be reclaimed due to items being in use (MemAvailable in
              /proc/meminfo, available on kernels 3.14, emulated on kernels 2.6.27+, otherwise the same as free)

man proc,meminfo相关的信息
1.Buffers是对原始磁盘块的临时存储,也就是用来缓存磁盘的数据,通常不会特别大(20M左右),这样内核就
  可以把分散的写集中起来,统一优化磁盘的写入,比如把多次小的写合并成单次大的写
2.Cached是从磁盘读取文件的页缓存,也就是用来 缓存从文件读取的数据,这样下次再访问这些文件数据时,
  就可以直接从内存中快速获取,而不需要再次访问磁盘
3.SReclaimable是Slab的一部分,Slab包括两部分,其中的可回收部分用SReclaimable记录,不可回收部分
  用SUnreclaim记录

测试Buffers和Cached

测试Cached

先将缓存情况

echo 3 > /proc/sys/vm/drop_caches

用dd写入一段数据

dd if=/dev/urandom of=/data0/hehe.log bs=1M count=500 
500+0 records in
500+0 records out
524288000 bytes (524 MB) copied, 3.96521 s, 132 MB/s

vmstat观察,其中的io中的bo有不断的变化,另外cache不断有变化,但buff基本不变

vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0      0 834112    240  98516    0    0     2     3   10    8  1  0 99  0  0
 0  0      0 833988    248  98512    0    0     0    48  165  325  0  0 100  0  0
 1  0      0 762892    380 168224    0    0   208     0  547  346  0 41 59  0  0
 1  1      0 599192    384 332080    0    0     0 163960 1080  453  0 96  0  4  0
 0  1      0 484668    388 446532    0    0     0 102400  875  499  1 66  0 33  0
 1  1      0 371152    388 560052    0    0     0 116136  855  468  0 66  0 34  0
 0  0      0 311824    408 620064    0    0    16 64088  554  422  0 35 43 22  0
 0  0      0 311904    408 620000    0    0     0     0  156  314  1  0 99  0  0

测试读取,多次执行下面的dd操作

echo 3 > /proc/sys/vm/drop_caches
dd if=/data0/hehe.log of=/dev/null 

vmstat观察,可以发现一开始bi的值在不断变化,后面都是0了,一开始cache的值不断增大,后面就不变了,说明是在读缓存中的值了

vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0      0 834416    236  98412    0    0     2     3   10    8  1  0 99  0  0
 0  0      0 834292    236  98420    0    0     8     0  184  345  0  1 99  0  0
 0  1      0 727688    372 204696    0    0 106436     0 1558 1819  7 17  7 69  0
 0  1      0 618188    372 314124    0    0 109440     0 1639 1918  5 19  0 76  0
 1  0      0 507888    372 424476    0    0 110336     0 1621 1842  5 19  0 76  0
 0  1      0 400392    372 531948    0    0 107392     0 1577 1788  7 19  0 74  0
 0  1      0 337480    372 594884    0    0 62848     0  979 1170  4 12  0 84  0
 0  0      0 321624    380 610720    0    0 15760    12  373  555  2  2 84 12  0
 0  0      0 321624    380 610724    0    0     0     0  151  284  0  1 99  0  0
 0  0      0 321624    380 610724    0    0     0     0  158  297  0  0 100  0  0
 1  0      0 321600    380 610724    0    0     0     0  562  332 12 30 58  0  0
 0  0      0 321624    380 610724    0    0     0     0  718  333 16 44 40  0  0
 0  0      0 321624    380 610724    0    0     0     0  154  302  0  0 100  0  0
 0  0      0 321624    380 610724    0    0     0    64  167  311  1  1 98  0  0
 0  0      0 321624    380 610724    0    0     0     0  158  306  0  0 100  0  0
 1  0      0 321624    380 610724    0    0     0     0  143  284  0  0 100  0  0
 1  0      0 321600    380 610724    0    0     0     0  916  305 24 58 18  0  0
 0  0      0 321624    380 610724    0    0     0     0  354  333  8 13 79  0  0

测试Buffer
因为只有一块盘,所以写Buffer就没法测试
具体的命令如下

echo 3 > /proc/sys/vm/drop_cacches
dd if=/dev/urandom of=/dev/sdb1 bs=1M count=2048


写数据的时候Buffer和Cache都在增长,但明显Buffer增长的快的多,说明写磁盘用到了大量的Buffer

写Buffer测试

echo 3 > /proc/sys/vm/drop_caches 
dd if=/dev/vda1 of=/dev/null bs=1M count=1024

用vmstat观察,发现如果是多次读磁盘的话,第二次的内容不会被缓存到,bi还是在不断变化

vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0      0 836924     92  98400    0    0     2     3   11    8  1  0 99  0  0
 0  1      0 813848  21852  98468    0    0 21844     0  448  681  0  2 80 18  0
 0  1      0 708936 126812  98464    0    0 104960     0 1284 1878  1  4  0 95  0
 0  1      0 621316 214364  98500    0    0 87552     0 1287 1673  0  4  0 96  0
 0  1      0 552408 283356  98448    0    0 68992     0 1056 1379  0  3  0 97  0
 0  1      0 464044 371684  98452    0    0 88320    20 1314 1694  0  4  0 96  0
 0  1      0 381912 453732  98448    0    0 82048     0 1245 1554  1  2  0 97  0
 0  1      0 298560 536932  98700    0    0 83200     0 1206 1615  0  4  0 96  0
 0  1      0 220884 614500  98892    0    0 77568     0 1181 1556  0  4  0 96  0
 0  1      0 192548 642660  98888    0    0 28160     0  514  744  0  1  0 99  0
 0  1      0 118972 716132  99092    0    0 73472     0 1157 1443  1  3  0 96  0
 0  1      0  83428 751588  99128    0    0 35456     0  622  862  0  2  0 98  0
 0  1      0  67816 768812  97612    0    0 98304     0 1425 1884  0  4  0 96  0
 0  1      0  62936 774016  97340    0    0 87680     0 1330 1692  0  3  0 97  0
 0  1      0  64172 773076  96952    0    0 108288    80 1529 2023  1  4  0 95  0
 0  0      0  73716 764204  96980    0    0  2980     0  222  404  0  0 97  3  0
 0  0      0  73700 764204  96980    0    0     0     0  179  332  0  1 99  0  0
 0  0      0  73716 764204  96980    0    0     0     0  180  328  1  0 99  0  0
 0  0      0  73716 764204  96980    0    0     0     0  159  289  0  0 100  0  0
 0  1      0  61692 775320  97140    0    0 11284     0  294  481  0  1 95  4  0
 0  1      0  70096 767616  96500    0    0 110592     0 1325 1970  0  4  0 96  0
 0  1      0  63176 775108  96012    0    0 110592     0 1465 1998  0  4  0 96  0
 0  1      0  73312 764844  95560    0    0 90076     0 1266 1712  1  3  0 96  0
 0  1      0  67892 771324  95028    0    0 106764     0 1471 1918  0  7  0 93  0
 0  1      0  69504 770396  94428    0    0 110888     0 1521 1995  0  7  0 93  0
 0  1      0  69788 770668  93872    0    0 112000     0 1533 2026  1  6  0 93  0
 0  1      0  63752 777112  93448    0    0 88960     0 1290 1676  0  6  0 94  0
 0  0      0  73372 768472  93056    0    0 44164     0  716 1058  0  3 59 38  0

对Buffer和Cache的总结
1.Buffer是对磁盘数据的缓存,包含读和写
2.Cache是对文件数据的缓存,包括读和写

从写的角度来看,不仅可以优化磁盘和文件的写入,对应用程序也有好处,应用程序可以在数据真正落盘前,就返回去做其他的工作
从读的角度看,可以加速读取那些需要频繁访问的数据,也降低了频繁I/O对磁盘的压力
 

Linux中磁盘是一个块设备,可以划分成不同的分区,在分区之上再创建文件系统,挂载到某个目录,之后才可以在这个目录中读写文件
正常看到的文件都是普通文件,磁盘是块设备文件
读写普通文件时,会经过文件系统,由文件系统负责与磁盘交互
读写磁盘或分区时,就会跳过文件系统,也就是裸I/O
这两种读写方式所使用的缓存是不同的,也几句是Cache和Buffer的区别

参考

每个程序员都应该了解的内存知识

细说 new与 malloc 的 10 点区别

如何实现一个malloc

malloc 背后的系统知识

tcmalloc 介绍

Linux内存管理中的slab分配器

伙伴分配器的一个极简实现

多核心Linux内核路径优化的不二法门之-slab与伙伴系统

Linux内核同步机制之(七):RCU基础

猜你喜欢

转载自blog.csdn.net/hixiaoxiaoniao/article/details/85232652