linux 内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的。这样,进程就可以很方便地访问内存,更确切地说是访问虚拟内存。
虚拟地址空间的内部又被分为内核空间和用户空间两部分,不同字长(也就是单个 cpu 指令可以处理数据的最大长度)的处理器,地址空间的范围也不同。比如最常见的 32 位和 64 位系统,如图:
并不是所有的虚拟内存都会分配物理内存,只有那些实际使用的虚拟内存才分配物理内存,并且分配后的物理内存,是通过内存映射来管理的。
内存映射,其实就是将虚拟内存地址映射到物理内存地址。为了完成内存映射,内核为每个进程都维护了一张页表(页表实际上存储在 cpu 的内存管理单元 mmu 中),记录虚拟地址与物理地址的映射关系。
当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常,进入内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行。(内存调用,都只在首次访问时才分配,也就是通过缺页异常进入内核中,再由内核来分配内存。)
mmu 规定了一个内存映射的最小单位,也就是页,通常是 4 kb 大小。这样,每一次内存映射,都需要关联 4 kb 或者 4kb 整数倍的内存空间。
多级页表(multilevel page tables)就是把内存分成区块来管理,将原来的映射关系改成区块索引和区块内的偏移。由于虚拟内存空间通常只用了很少一部分,那么,多级页表就只保存这些使用中的区块,这样就可以大大地减少页表的项数。linux 用的正是四级页表来管理内存页。如下图所示,虚拟地址被分为 5 个部分,前 4 个表项用于选择页,而最后一个索引表示页内偏移。
大页(hugepage)就是比普通页更大的内存块,常见的大小有 2mb 和 1gb。大页通常用在使用大量内存的进程上,比如 oracle、dpdk 等。
在这五个内存段中,堆和文件映射段的内存是动态分配的。比如说,使用 c 标准库的 malloc() 或者 mmap() ,就可以分别在堆和文件映射段动态分配内存
系统不会任由某个进程用完所有内存。在发现内存紧张时,系统就会通过一系列机制来回收内存:
回收不常访问的内存时,会用到交换分区(以下简称 swap)。swap 其实就是把一块磁盘空间当成内存来用。它可以把进程暂时不用的数据存储到磁盘中(这个过程称为换出),当进程访问这些内存时,再从磁盘读取这些数据到内存中(这个过程称为换入)。
oom它监控进程的内存使用情况,并且使用 oom_score 为每个进程的内存使用情况进行评分。管理员可以通过 /proc 文件系统,手动设置进程的 oom_adj ,从而调整进程的 oom_score。
[root@k8s ~]# watch -d free every 2.0s: free wed apr 8 15:59:31 2020 total used free shared buff/cache available mem: 8173864 4094104 276572 436676 3803188 3333024 swap: 0 0 0
[root@k8s ~]# top ………… kib mem : 8173864 total, 275696 free, 4094212 used, 3803956 buff/cache kib swap: 0 total, 0 free, 0 used. 3332920 avail mem pid user pr ni virt res shr s %cpu %mem time+ command 3482 root 20 0 2430460 1224 760 s 85.1 0.0 3557:06 kswapd0 …………
man proc 对 proc 文件系统的说明
buffer 是对磁盘数据的缓存,而 cache 是文件数据的缓存,它们既会用在读请求中,也会用在写请求中。
对普通进程来说,它能看到的其实是内核提供的虚拟内存,这些虚拟内存还需要通过页表,由系统映射为物理内存。
当进程通过 malloc() 申请内存后,内存并不会立即分配,而是在首次访问时,才通过缺页异常陷入内核中分配内存。
由于进程的虚拟地址空间比物理内存大很多,linux 还提供了一系列的机制,应对内存不足的问题,比如缓存的回收、交换分区 swap 以及 oom 等。
buffer 和 cache 分别缓存磁盘和文件系统的读写数据。
整理自极客时间:《linux性能优化实战》
如对本文有疑问, 点击进行留言回复!!
linux下文本编辑器vim的使用方法(复制、粘贴、替换、行号、撤销、多文件操作)
网友评论