内存管理也是操做系统最核心的功能之一,内存主要用来存储系统和应用程序的指令、数据、缓存等api
一、咱们通说的内存指的是物理内存仍是虚拟内存?缓存
咱们一般说的内存容量,其实这指的是物理内存,物理内存也称为主存,大多数计算机用的主存都是动态随机访问内存(DRAM)。只有内核才能够直接访问物理内存。bash
那么,进程要访问内存时,该怎么办呢?ssh
二、进程是如何访问内存的?函数
Linux 内核给每一个进程都提供了一个独立的虚拟地址空间,而且这个地址空间是连续的。这样,进程就能够很方便地访问内存,更确切地说是访问虚拟内存性能
三、虚拟内存的内核空间和用户空间分布图spa
四、进程是如何访问内核空间内存的?操作系统
进程在用户态时,只能访问用户内存;只有进入内核态后,才能够访问内核空间内存,虽然每一个进程的地址空间都包含了内核空间,但这些内核空间,其实关联的都是想同的物理内存3d
这样、进程切换到内核态后,就能够很方便地访问内核空间内存对象
五、并非全部的虚拟机内存都会分配物理内存
既然每一个进程都有一个这么大的地址空间,那么全部进程的虚拟内存加起来,天然要比实际的物理内存大得多
因此并非全部的虚拟机内存都会分配物理内存,只有那些实际使用的虚拟机内存才分配物理内存,而且分配后的物理内存,是经过内存映射来管理的
六、什么是内存映射?
内存映射,其实就是将虚拟内存地址映射到物理内存地址,为了完成内存映射,内核为每一个进程都维护了一张页表,
记录虚拟地址与物理地址的映射关系,以下图所示:
页表实际上存储在 CPU 的内存管理单元 MMU 中,这样,正常状况下,处理器就能够直接经过硬件,找出要访问的内存
七、进程访问的虚拟地址在页表中查不到,怎么办?
当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常,进入内核空间分配物理内存、更新进程页表,最后再返回用户空间、恢复进程运行
八、什么是TLB?TLB的做用是什么
TLB 其实就是 MMU 中页表的高速缓存,因为进程的虚拟地址空间是独立的,而 TLB 的访问速度又比MMU 快得多,
因此,经过减小进程的上下文切换,减小 TLB 的刷新次数,就能够提升 TLB 缓存的使用率,进而提升 CPU 的内存访问性能。
九、MMU 是以什么为单位来管理内存?
不过要注意,MMU 并不以字节为单位来管理内存,而是规定了一个内存映射的最小单位,也就是页,一般是 4 KB大小,
这样,每一次内存映射,都须要关联 4 KB 或者 4KB 整数倍的内存空间
十、如何减小页表的项数
页的大小只有 4 KB ,致使的另外一个问题就是,整个页表会变得很是大。比方说,仅 32 位系统就须要 100 多万个页表项(4GB/4KB),才能够实现整个地址空间的映射。
为了解决页表项过多的问题,Linux 提供了两种机制,也就是多级页表和大页(HugePage)。
十一、多级页
多级页表就是把内存分红区块来管理,将原来的映射关系改为区块索引和区块内的偏移。因为虚拟内存空间一般只用了不多一部分,
那么多级页表就只保存这些使用中的区块,这样就能够大大地减小页表的项数
Linux 用的正是四级页表来管理内存页,以下图所示,虚拟地址被分为 5 个部分,前 4 个表项用于选择页,而最后一个索引表示页内偏移。
十二、大页
再看大页,顾名思义,就是比普通页更大的内存块,常见的大小有 2MB 和 1GB。大页一般用在使用大量内存的的进程上,好比 Oracle、DPDK 等
经过这些机制,在页表的映射下,进程就能够经过虚拟地址来访问物理内存了。那么具体到一个 Linux 进程中,这些内存又是怎么使用的呢
经过这张图你能够看到,用户空间内存,从低到高分别是五种不一样的的内存段
一、在这五个内存段中,堆和文件映射段的内存是动态分配的,好比说,使用 C 标准库的 malloc() 或者mmap() ,就能够分别在堆和文件映射段动态分配内存
二、其实 64 位系统的内存分布也相似,只不过内存空间要大得多,那么,更重要的问题来了,内存到底是怎么分配的呢?
malloc() 是 C 标准库提供的内存分配函数,对应到系统调用上,有两种实现方式,即 brk() 和 mmap()。
都只在首次访问时才分配,也就是经过缺页异常进入内核中,再由内内核来分配内存。
一、如何减小内存碎片?
总体来讲,Linux 使用伙伴系统来管理内存分配。前面咱们提到过,这些内存在 MMU 中以页为单位进行管理,伙伴系统也一系统也同样,以页为单位来管理内存,而且会经过相邻页的合并,
减小内存碎片化(好比 brk 方式形成的内存碎片)。
二、比页更小的内存(不到 1K ),该怎么分配内存呢?
实际系统运行中,确实有大量比页还小的对象若是为它们也分配单独的页,那就太浪费内存了。因此,在用户空间,malloc 经过 brk() 分配的内存,在释放时并不当即归还系统,
而是缓存起来重复利用。在内核空间,Linux 则经过 slab 分配器来管理小内存。你能够把slab 当作构建在伙伴系统上的一个缓存,主要做用就是分配并分配并释放内核中的小对象。
对内存来讲,若是只分配而不释放,就会形成内存泄漏,甚至会耗尽系统内存。因此,在应用程序用完内存后,还须要调用free() 或 unmap() ,来释放这些不用的内存。
固然,系统也不会任由某个进程用完全部内存,在发现内存紧张时,系统就会经过一系列机制来回收内存,好比下面下面这三种方式:
第三种方式提到的 OOM(Out of Memory),实际上是内核的一种保护机制它监控进程的内存使用状况,而且使用 oom_score 为每一个进程的内存使用状况进行评分:
一个进程消耗的内存越大,oom_score 就越大;
一个进程运行占用的 CPU 越多,oom_score 就越小
这样,进程的 oom_score 越大,表明消耗的内存越多,也就越容易被 OOM 杀死,从而能够更好保护系统。
固然,为了实际工做的须要,管理员能够经过 /proc 文件系统,手动设置进程的 oom_adj ,从而调整进程的 oom_score。
oom_adj 的范围是 [-17, 15],数值越大,表示进程越容易被 OOM 杀死;数值越小,表示进程越不容易被 OOM 杀死,其中 -17 表示禁止 OOM。
好比用下面的命令,你就能够把 sshd 进程的调小为 -16,这样, sshd 进程就不容易被 OOM 杀死
echo -16 > /proc/$(pidof sshd)/oom_adj
[root@api ~]# free total used free shared buff/cache available Mem: 8010968 935180 5968260 16668 1107528 6835728 Swap: 4194300 0 4194300
这里尤为注意一下,最后一列的可用内存 available,available 不只包含未使用内存,还包括了可回收的缓存,因此通常会比未使用内存更大。
不过,并非全部缓存均可以回收,因此通常会比未使用内存更大
top - 11:52:35 up 16 days, 55 min, 1 user, load average: 0.00, 0.01, 0.05 Tasks: 101 total, 1 running, 100 sleeping, 0 stopped, 0 zombie %Cpu(s): 0.1 us, 0.1 sy, 0.0 ni, 99.8 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 14.7/8010968 [||||||||||||||| ] KiB Swap: 0.0/4194300 [ ] PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 1 root 20 0 41348 3580 2332 S 0.0 0.0 0:36.59 systemd 2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd 3 root 20 0 0 0 0 S 0.0 0.0 0:00.00 ksoftirqd/0
VIRT 是进程虚拟内存的大小:只要是进程申请过的内存,即使尚未真正分配物理内存,也会计算在内。
RES 是常驻内存的大小:也就是进程实际使用的物理内存大小,但不包括 Swap 和共享内存。
SHR 是共享内存的大小:好比与其余进程共同使用的共享内存、加载的动态连接库以及程序的代码段等。
%MEM 是进程使用物理内存占系统总内存的百分比。
第一:虚拟内存一般并不会所有分配物理内存。从上面的输出,你能够发现每一个进程的虚拟内存都比常驻内存大得多。
第二:共享内存 SHR 并不必定是共享的,比方说,程序的代码、非共享的动态连接库,也都算在 SHR 里。固然,
SHR 也包括了进程间真正共享的内存。因此在计算多个进程的内存使用时,不要把全部进程的 SHR 直接相加得出结果。