Linux 内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的,所以进程可以方便地访问虚拟内存。虚拟地址空间的内部又被分为内核空间和用户空间两部分,不同字长(也就是单个 CPU 指令可以处理数据的最大长度)的处理器,地址空间的范围也不同。进程在用户态时,只能访问用户空间内存;只有进入内核态后,才可以访问内核空间内存。进程在用户态时,只能访问用户空间内存;只有进入内核态后,才可以访问内核空间内存。
虚拟内存的大小可能远大于物理内存大小,因此,并不是每个虚拟内存都在物理内存中有对应,而是只有使用时才会为其分配内存,物理内存和虚拟内存之间的关系,通过内存映射完成。物理内存和虚拟内存都被分成一页一页的,MMU以页为单位管理内存。
页表中记录了虚拟内存到物理内存的映射关系,当然也有一些附加信息,包括页是否修改、数据是否有效、是否在物理内存上等诸多信息。实际上还有一个TLB用来加速地址映射速度,可以把TLB当作一个缓存即可,TLB命中的话就不需要再去访问页表,否则需要再次访问页表。
Linux通过多级页表机制实现内存映射,多级页表大大减少了页表的大小,节省了内存空间。
一个进程占用的空间该怎样算?一堆进程占用的空间该怎么算?
一个进程占用的空间,有一个有意思细节:malloc或者全局申请的变量,没有进行初始化,那么,这个变量实际上是不占用真实地址(物理地址)的,只是占用了虚拟地址,只有当真正写的时候才会占用真实的地址。因此一个程序占用的虚拟地址通常来说是要大于这个程序占用的物理地址。
计算一堆进程占用空间时,也有一个有意思的细节:一堆进程之间会存在一些共享库,也包括一些copy-on-write的内存,但是直接在shell中查看proc的内存时会算上这个,因此假设这样一个场景:系统出现问题,然后直接将所有进程的信息list出来,如果直接将每个proc的内存加起来当作总的内存和,这就会产生一个比较大的问题,因为共享的区域会加起来很多次。可能会产生一个误解,即明明proc的占用内存可能并不高,但是因为重复计算太多可能导致评估proc占用的总内存爆了,这就会产生一个误判,但是直接free展现的话就会发现系统还有内存。因此计算一堆proc占用的总内存就需要注意这里。
Linux主要通过使用伙伴系统来管理内存分配,以页为单位来管理内存,并且会通过相邻页的合并,减少内存碎片化。Linux 则通过 slab 分配器来管理小内存。
当内存不够用时,系统需要回收内存:
通过对proc文件系统查询,可以知道Linux上的buffer和cache的定义:
buffer通常是解决速度差异的问题,为了避免速度快的一方受限于速度慢的一方,用一个buffer进行协调。cache通常是用来加快访问,将常用的内容放到cache里加快访问。
通过一个案例来看buffer和cache。
先清理系统缓存避免影响 :
echo 3 > /proc/sys/vm/drop_caches
用vmstat看一下当前的buffer和cache情况:
[root@jessy /]# 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
1 0 0 7720256 5548 105876 0 0 0 6 4 2 0 0 100 0 0
0 0 0 7719900 5548 105908 0 0 0 24 671 1107 0 0 100 0 0
0 0 0 7721276 5548 105908 0 0 0 0 204 242 0 0 100 0 0
0 0 0 7721452 5548 105912 0 0 0 0 395 762 0 0 100 0 0
基本情况下空闲时buffer和cache是几乎不变的,或者说变化甚微。
在一个终端通过dd命令随机读取设备生成一个500MB的文件:
[root@jessy /]# dd if=/dev/urandom of=/tmp/file bs=1M count=500
500+0 records in
500+0 records out
524288000 bytes (524 MB) copied, 3.37052 s, 156 MB/s
另一个终端用vmstat一直监视:
[root@jessy ~]# 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
1 0 0 7625712 7512 194584 0 0 0 6 4 2 0 0 100 0 0
0 0 0 7625264 7512 194588 0 0 0 0 283 313 0 0 100 0 0
0 0 0 7624564 7528 194576 0 0 0 84 219 289 0 0 100 0 0
0 0 0 7623772 7528 194592 0 0 0 0 369 419 0 1 99 0 0
0 0 0 7623292 7536 194588 0 0 8 0 678 1149 0 0 100 0 0
1 0 0 7498880 7536 318348 0 0 76 0 2057 851 0 21 79 0 0
1 0 0 7343648 7536 474560 0 0 0 4 3902 1497 0 25 74 0 0
1 0 0 7188836 7544 629716 0 0 0 92 4131 943 0 26 74 0 0
0 0 0 7103560 7544 716164 0 0 0 8 2376 656 0 14 86 0 0
发现一个很奇怪的现象,那就是buffer没有明显变化,但是cache缺增长非常明显。
再进行一个场景,对磁盘进行写入:
dd if=/dev/sda1 of=/dev/null bs=1M count=1024
另一个终端监控:
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 7225880 2716 608184 0 0 0 0 48 159 0 0 100 0 0
0 1 0 7199420 28644 608228 0 0 25928 0 60 252 0 1 65 35 0
0 1 0 7167092 60900 608312 0 0 32256 0 54 269 0 1 50 49 0
0 1 0 7134416 93572 608376 0 0 32672 0 53 253 0 0 51 49 0
0 1 0 7101484 126320 608480 0 0 32748 0 80 414 0 1 50 49 0
可以看到buffer增长非常迅速,cache无明显变化。
因此Linux上的cache和buffer基本可以这样理解,Buffer 既可以用作“将要写入磁盘数据的缓存”,也可以用作“从磁盘读取数据的缓存“,Cache 既可以用作“从文件读取数据的页缓存”,也可以用作“写文件的页缓存”。Buffer 是对磁盘数据的缓存,而 Cache 是文件数据的缓存,它们既会用在读请求中,也会用在写请求中
缓存的效果取决于其命中率:缓存的命中率,是指直接通过缓存获取数据的请求次数,占所有数据请求次数的百分比。缓存命中率高,应用程序的性能就越好。
主要原理是把经常访问的数据,提前放入到内存中,在下次访问时就可以直接从内存读取数据,而不需要经过硬盘,从而加快应用程序的响应速度。
对普通进程来说,所谓分配的内存实际上是虚拟内存,用malloc申请虚拟内存后,系统不会立即分配物理内存,而是在首次缺页异常陷入内核中分配内存。
内存泄漏会发生了哪些内存区?