系统优化-内存-Linux内存工作原理

日常生活中我们常提起的内存主要指物理内存,又称为主存。一般指DRAM。linux的进程出于系统保护的目的不能直接访问物理能存,而是为每一个进程开辟了一块虚拟内存,它的地址是连续的,进程可以很方便的访问内存。

虚拟内存又分为用户空间和内核空间。

  • 32位系统中虚拟内存最大支持4gb,系统空间大小是1gb占虚拟内存的高位,用户空间大小是0~3gb占虚拟内存的低位
  • 64位操作系统则是内核空间大小是128T占虚拟内存的高位,中间是未定义,用户空间大小是128T占虚拟内存的低位

    如图:
    avator

    进程处于用户态只能访问虚拟内存的用户空间,比如程序的赋值等简单操作,当进程处于内核态时才能操作虚拟内存的内核空间。

内存映射

这样如果所有的进程的虚拟内存加起来可能会远远大于物理内存,而linux实际上并不是给所有的虚拟内存分配物理内存,只会在进程需要时候才会分配物理内存。并且分配后的物理内存是通过内存映射来管理的。

由于进程不能直接操作物理内存,所以每一个进程就会持有一个内存页,用来维护虚拟内存和物理内存的映射关系,可以很方便的操作物理内存。这些内存页被保存在CPU的MMU模块中,CPU可以通过MMU模块来访问进程需要的内存。如图:

avator

注意:由于分配虚拟内存的进程并不会直接去分配物理内存,而是MMU发现没有找到内存的物理地址时候出现缺页异常,这时候进程会切换到内核态分配内存,再刷新MMU和内存页返回到用户态继续执行用户程序。

MMU的单位是4KB成为PTE(page table entity),每一次内存映射,都需要关联4KB或者4KB整数倍的内存空间。这样有个问题,会需要大量的页表项比如一个4gb的内存就要100万个PTE。一般用多级表和大表来解决。多级表实际上就是索引的概念,linux采用4级表来解决。

avator

内存分配

内核空间我们暂不讨论,用户空间分为

avator

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

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

  1. 对于小于(128k)的内存采用brk()来分配,通过移动堆顶的位置来分配内存。这些内存释放后并不会立刻归还系统,而是被缓存起来,这样就可以重复使用。优点:不会立刻归还系统可以重复利用,缺点:频繁申请、释放会造成内存碎片
  2. 对于大于(128k)的内存采用mmap()来分配,则直接使用内存映射 mmap() 来分配,也就是在文件映射段找一块空闲内存分配出去。优点:减少磁盘碎片。缺点:释放归还系统所以频繁出现缺页异常增大系统负担

对内存来说,如果只分配而不释放,就会造成内存泄漏,甚至会耗尽系统内存。所以,在应用程序用完内存后,还需要调用 free() 或 unmap() ,来释放这些不用的内存。当然,系统也不会任由某个进程用完所有内存。在发现内存紧张时,系统就会通过一系列机制来回收内存,比如下面这三种方式:

  1. 回收缓存,比如使用 LRU(Least Recently Used)算法,回收最近使用最少的内存页面;
  2. 回收不常访问的内存,把不常用的内存通过交换分区直接写到磁盘中;
  3. 杀死进程,内存紧张时系统还会通过 OOM(Out of Memory),直接杀掉占用大量内存的进程。

    可以通过以下文件调整oom的权重,越大越容易杀死

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

如何查看内存使用

Swap,资源紧张时候Linux会将内存数据映射到磁盘上,这种情况会严重的影响系统性能,要尽量避免。

top or ps:主要查看VIRT-虚拟内存 RES–常驻内存 SHR–共享内存 SWAP–文件交换区 %MEM–使用率