浅谈linux中的内存管理

作者: 小小小笑呵 | 来源:发表于2019-08-15 16:50 被阅读0次

    1 物理内存

    1.1 NUMA节点

    在多个CPU(指的是物理CPU[1])通过总线来访问系统中的内存,这种方式称之为多对称处理器(如图1左),由于每块CPU都需要通过总线访问内存,导致总线数据繁忙,降低了从内存读取的速率。因此提出了一种叫做非统一内存访问(NUMA)的方式(如图1右),这种方式将每块CPU(物理CPU)和内存绑定在一起,形成一个NUMA节点,这样就可以通过CPU之间访问内存,而不用通过总线去访问了,加快了对内存的访问速度。

    图1 非一致性内存访问

    小实验

    通过如下命令可以查看系统numa节点的个数
    1. lscpu
    2. numactl --hardware (需要进行安装yum install numactl)
    # lscpu的演示
    [root@compute3-trust ~]# lscpu
    Architecture:          x86_64
    CPU op-mode(s):        32-bit, 64-bit
    ...
    CPU(s):                40   # 逻辑CPU的个数
    ...
    NUMA node(s):          2
    ...
    L1d cache:             32K
    L1i cache:             32K
    L2 cache:              256K
    L3 cache:              10240K
    NUMA node0 CPU(s):     0-9,20-29  // 不同numa节点上,逻辑cpu的分布情况
    NUMA node1 CPU(s):     10-19,30-39
    
    # numactl 的演示
    [root@compute]# numactl --hardware
    available: 2 nodes (0-1)
    node 0 cpus: 0 1 2 3 4 5 6 7 8 9 20 21 22 23 24 25 26 27 28 29
    node 0 size: 32365 MB
    node 0 free: 29546 MB
    node 1 cpus: 10 11 12 13 14 15 16 17 18 19 30 31 32 33 34 35 36 37 38 39
    node 1 size: 32768 MB
    node 1 free: 30287 MB
    node distances:
    node   0   1 
      0:  10  21 
      1:  21  10 
    [root@compute]# free -h  // free命令显示的内存是系统中总内存的大小,而不是每个numa节点中内存的大小
                  total        used        free      shared  buff/cache   available
    Mem:            62G        2.0G         58G         25M        1.9G         59G
    Swap:           31G          0B         31G
    

    1.2 NUMA节点中的内存

    图2 NUMA节点中的内存

    从图2中,我们得知NUMA节点由一个叫做pg_data_t的结构体来描述从中我们大概可以得知:

    1. 该节点所包含的物理页面数等信息
    2. node_zones数组表示NUMA节点中的物理内存又细分为不同的区域,如(DMA, NORMAL, HIGHMEM, MOVAABLE)。(每个区域的含义自行百度)。
    3. node_mem_map数组,该节点所有的page对象

    针对每个区域,linux用一个名为zone的结构体来进行描述,从该结构体中,我们大概可以得知:

    1. 该区域的起始物理页号
    2. 该区域可用的物理页数
    3. free_area后面讲
    4. 真正的物理页面就存放了这些区域中

    接下来我们就终于看到真正的物理页面了,根据页的大小及作用,此时将页分为三类:

    1. 分配完整的一页,并且该物理页直接和虚拟空间建立映射关系。则称该页为匿名页(anonymous page)。
    2. 分配完整的一页,该物理页不仅与虚拟空间建立映射关系而且还关联一个文件。则称该页为文件映射页(自己取的名字)。
    3. 通过申请完整的一页,然后再通过slub,slab等方式将完整的一页再切分成更小的页,将这些页分配出去,称之为小页。

    匿名页和文件映射页的区别见图3

    图3 匿名页和文件映射页的区别

    1.2.1 伙伴系统分配物理页

    上面在将zone结构体的时候,其中的free_area变量我们没讲,现在我们来简述下它的作用。

    如图4所示,free_area变量负责组织当前区域可用的物理页面,当通过伙伴系统分配物理页面的时候就是根据该变量的值进行分配的。

    一般情况下,该free_area变量是一个长度为11的数组。当数组下标为0时,其只能一个连续的页面,当数组下标为1时,其只能分配两个连续的页面,当数组下标为2时,其只能分配4个连续的页面,当数组下标为10时,其能分配1024个连续的页面。即数组下标对应能连续分配物理页面的关系是:2数组下标。图中数组下标所对应连续的物理页称之为页块链表。

    比如当需要分配513个连续的物理页的时候(29 = 512),此时数组下标为9的页块链表不能分配这么多连续的物理页,则去数组下标为10的页块链表进行申请。当分配完毕后,伙伴系统将会把剩下的物理页插入的对应的页块链表中去。

    图4 伙伴系统分配物理页

    1.2.2 物理页面的回收

    当物理页面不够,而又需要分配新的物理页面的时候或者线程kswapd发现当前系统物理内存很少的时候,就会触发物理页面回收的机制了。{还有一种叫做out of memory机制了,简单来说就是直接通过kill进程的方式来释放内存}

    如图5所示,不管是分配新的物理内存还是kswapd线程,最终都用调用shrink_node_memcg函数来进行物理页面的回收。

    从图5中,我们发现系统维护了4个物理页面的链表(实际上不是4个,还有其他的),分别是匿名页(active),匿名页(inactive),文件映射页(active),文件映射页(inactive)。其通过LRU算法对inactive的页的链表进行回收,如果是匿名页(inactive),则系统将物理页中的数据写入到swap分区中,然后释放对应的物理内存,最后将部分匿名页(acitve)链表中的页插入到匿名页(inacitve)链表中。如果是文件映射页(inactive)则将物理内存中的数据写回到文件中,然后释放物理内存,最后将部分文件映射页(active)链表中的页插入到文件映射页(inactive)链表中。

    图5 物理页面的回收

    2 虚拟内存

    2.1 地址

    在linux中,每个进程都有逻辑上属于自己的内存空间,称之为虚拟内存,从而使得不同进程的内存空间可以相互进行隔离。每个进程虚拟空间的大小与操作系统的位数有关。对于32位操作系统而言,虚拟内存的逻辑地址范围为0-232,所以虚拟内存空间的大小为4GB。

    在谈到内存的时候,地址是最为重要的概念。因为有了地址,就可以寻找到对应的内存空间,就可以往里面存储数据和读取数据了。所以理解内存就需要对地址有清晰的认识,下面就围绕逻辑地址展开介绍。

    在linux中,一个32位的逻辑地址被分为页号和页偏移两个部分。其中页偏移的寻址范围了0 - 212,每个一个逻辑地址(如图中0,1,2)对应的内存空间为1Byte,所以其最大的存储空间为4Kb = 212 * 1Byte,即页(linux中内存分配的基本单位,一次最少分配一个页)。如图6-<逻辑地址和内存的关系>所示(图中例子假设逻辑地址对应的前20位页号全位0)。

    接下来只需要将逻辑地址转化为物理地址就可以真正的存储和读取数据了,其过程如图6-<逻辑地址转化为物理地址>所示,从页表中找到逻辑地址的页号P所对应的物理页号b,然后将得到的物理页号b与页偏移进行相加即得到物理地址。

    图6-<数组在内存中的存放>,显示了java定义了长度为2的int数据在内存中是如何存放,可以与图6-<逻辑地址和内存的关系>对应起来。

    图6 逻辑地址

    2.2 虚拟内存空间的划分

    对于整个虚拟内存空间,linux将其划分为两个部分,分别是用户内存空间,用来存放用户态的数据和内核态内存空间,用来内核态的数据。

    结构体struct mm_struct *mm 就是用来描述虚拟内存空间的。

    在struct mm_struct中的TASK_SIZE_MAX变量的值为用户空间和内核空间的分界线。其值如图7所示

    注:对于64位系统而言,用户空间地址的寻址范围只用到了前48位。从1左移了47位就能看出

    图7 虚拟内存空间的划分

    2.3 用户内存空间

    用户态内存空间的分布如图8所示。图中描述了有哪些区域,这些区域用于存放哪种类型的数据等信息。

    图8 用户内存空间

    现在我们以32位系统例,看看linux如何用代码来组织用户空间的

    在结构体struct mm_struct *mm定义了很多描述内存的相关变量,如图9所示。变量太多了,没法全部记住,但从中我们大概能知道其描述了该进程总共映射了多少页,存储数据的页面数是多少,存储的可执行代码的页数是多少,还有各个区域(数据段,代码段等)起始地址和终点地址分别是多少等信息。

    图9 用户内存空间

    结构体struct mm_struct中还有一个比较关键的结构体变量struct vm_area_struct *mmp,其主要作用是用于描述前面所说的段(代码段,数据段,堆,栈)等信息。如图10所示,我们大概可以知道其指定了某一段的开始和结束,即某一段所能存储的范围以及其通过链表的方式将段与段之间进行连接

    图10 vm_area_struct

    物理内存的分配以及页表的建立

    linux并不会满足每个进程虚拟内存(32位4G)的大小,如果有50个进程那不就要分配200G内存,很明显这是不可能的。而是当进程通过逻辑地址读取(存储)内存中的数据的时候,发现没有对应的物理内存时,产生缺页中断进行物理内存的分配。简而言之,就是当程序真正要访问物理内存的时候,linux在开始分配物理内存。

    那么在缺页中断函数中大概做了些什么呢?

    如下图10所示,发生缺页中断后,经过一系列调用,进入_do_page_fault函数,在该函数中,判断缺页是发生在内核还是在用户空间,若发生在内核,则进入内核的处理逻辑,若发生在用户态,则调用handle_mm_fault函数,在函数中,分配创建了pud,pmd目录项(顶级目录项不用创建,其存储在CR3寄存器中)。然后调用handle_pte_fault函数,此时又分为三种情况分别是:
    1))页表不存在,并且是匿名页缺页异常,则调用do_anoymous_page函数
    2)页表不存在,并且是文件映射页缺页异常,则调用do_fault函数
    3)页表之前存在,则调用do_swap_page

    三种函数具体的做了些什么请看图11。


    图10 缺页中断

    3 相关概念

    [1] 物理cpu
    • 物理cpu: 物理cpu指的是主板上cpu芯片的数量(通过physical id 来查看有几个物理cpu)
    • 逻辑cpu:逻辑cpu一般指的是各个物理cpu中的core的数量(通过processor可以查看系统共有多少个逻辑cpu)

    命令:cat /proc/cpuinfo 可查看系统cpu的情况

    参考
    zone参考
    numa参考

    相关文章

      网友评论

        本文标题:浅谈linux中的内存管理

        本文链接:https://www.haomeiwen.com/subject/vyzlkctx.html