• linux内核源码分析之物理内存


    目录

    随机访问存储器

    物理架构

    从 CPU 角度看物理内存架构

    查看NUMA相关信息

    绑定NUMA节点

    管理内存节点(node) 

    node节点以及page数据结构之间关系

    物理内存划分(zone)

    物理内存页(page)

    内存分配行为

    内存分配ALLOC*标志 

    页面阈值 

    内存紧张时

    内存分配流程


    随机访问存储器

    1、静态RAM SRAM
    用于CPU高速缓存的L1Cache,L2Cache,L3Cache  访问周期 1-30个时钟周期,容量小,价高。
    2、动态RAM DRAM
    主存,访问周期 50-200个时钟周期,造价便宜,容量大。

    内核以页为单位对物理内存进行管理,每页大小4K  ,使用struct page结构体来进行管理
    内核为内个物理页定义了一个索引编号PFN(Page Frame Number)

    page_to_pfn与pfn_to_page完成PFN与物理页page结构之间的相互转换

    node,内核使用struct pglist_data 表示用于管理连续物理内存的node节点

    通过 arch_pfn_to_nid 可以根据物理页的PFN定位到物理页所在的Node
    通过 page_to_nid 可以根据物理页page定义到page所在node


    物理架构

    从 CPU 角度看物理内存架构

     一致性内存访问 SMP
        CPU通过总线访问内存,随着数量增加,总线带宽成为瓶颈
    非一致性内存访问 NUMA
        CPU访问本地内存更快,不通过总线。访问其他节点的内存,根据距离远近,速度不同。

            在NUMA架构下,可控制CPU如何请求内存分配策略

    MPOL_BIND必须在绑定的节点进行内存分配,如果内存不足,则进行 swap
    MPOL_INTERLEAVE本地节点和远程节点均可允许分配内存
    MPOL_PREFERRED优先在指定节点分配内存,当指定节点内存不足时,选择离指定节点最近的节点分配内存
    MPOL_LOCAL (默认)优先在本地节点分配,当本地节点内存不足时,可以在远程节点分配内存

    API接口

    1. #include
    2. long set_mempolicy(int mode, const unsigned long *nodemask,
    3. unsigned long maxnode);
    查看NUMA相关信息

    numactl -H 查看NUMA的配置

            

    numactl -s查看NUMA的内存分配策略

            

    numastat 查看各个NUMA节点的内存访问命中率

             

    绑定NUMA节点

    numactl 命令查看 ,指定应用程序运行在哪些CPU核心上,同时也可以绑定应用程序可以在哪些NUMA节点上分配内存

        numactl --membind=nodes --cpunodebind=nodes command


            -membind 指定应用程序只能在哪些NUMA节点上分配内存,如果这些节点内存不足,则分配失败;
            -cpunodebind 执行应用程序只能在哪些NUMA上运行

       -physcpubind 将我们的应用程序绑定到具体的物理 CPU 上

     将进程 a.out绑定到 0~15 CPU 上执行

    numactl --physcpubind= 0-15 ./a.out

    管理内存节点(node) 


    struct pglist_data 的全局数组node_data[] 来管理所有NUMA节点。
    源码位置(arch/arm64/include/asm/mmzone.h)

    1. #define CONFIG_NUMA
    2. extern struct pglist_data *node_data[];
    3. #define NODE_DATA(nid) (node_data[(nid)])

    NODE_DATA(nid) 宏可以通过NUMA节点的node id 找到对应的struct pglist_data结构

    1. typedef struct pglist_data {
    2. // NUMA 节点id
    3. int node_id;
    4. // 指向 NUMA 节点内管理所有物理页 page 的数组
    5. struct page *node_mem_map;
    6. // NUMA 节点内第一个物理页的 pfn
    7. unsigned long node_start_pfn;
    8. // NUMA 节点内所有可用的物理页个数(不包含内存空洞)
    9. unsigned long node_present_pages;
    10. // NUMA 节点内所有的物理页个数(包含内存空洞)
    11. unsigned long node_spanned_pages;
    12. // 保证多进程可以并发安全的访问 NUMA 节点
    13. spinlock_t node_size_lock;
    14. }
    node节点以及page数据结构之间关系

    • node_mem_map 中包含了 NUMA节点内的所有的物理内存页
    • node_start_pfn 指向 NUMA 节点内第一个物理页的 PFN
    • node_present_pages 用于统计 NUMA 节点内所有真正可用的物理页面数量

     

    物理内存划分(zone)

    物理内存区域的功能不同,物理内存划分为

    • ZONE_DMA DMA  时的内存分配
    • ZONE_DMA32      该区域只在 64 位系统中起作用
    • ZONE_NORMAL   物理页都可以直接映射到内核中的虚拟内存,线性映射,可直接访问
    • ZONE_HIGHMEM 这些物理页需要动态映射进内核虚拟内存空间中
    • ZONE_MOVABLE 是内核定义的一个虚拟内存区域,防止内存碎片和支持内存的热插拔
    • ZONE_DEVICE  支持热插拔设备而分配的非易失性内存( Non Volatile Memory ),也可用于内核崩溃时保存相关的调试信息

    1. struct zone {
    2. // 防止并发访问该内存区域
    3. spinlock_t lock;
    4. // 内存区域名称:Normal ,DMA,HighMem
    5. const char *name;
    6. // 指向该内存区域所属的 NUMA 节点
    7. struct pglist_data *zone_pgdat;
    8. // 属于该内存区域中的第一个物理页 PFN
    9. unsigned long zone_start_pfn;
    10. // 该内存区域中所有的物理页个数(包含内存空洞)
    11. unsigned long spanned_pages;
    12. // 该内存区域所有可用的物理页个数(不包含内存空洞)
    13. unsigned long present_pages;
    14. // 被伙伴系统所管理的物理页数
    15. atomic_long_t managed_pages;
    16. // 伙伴系统的核心数据结构
    17. struct free_area free_area[MAX_ORDER];
    18. // 该内存区域内存使用的统计信息
    19. atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
    20. } ____cacheline_internodealigned_in_smp;

    伙伴系统有个特点,所分配的物理内存页都是物理上连续的,并且只能分配2的整数幂个页(阶)
     node ->zone->page

    物理内存页(page)

    page(页)是linux内核管理物理内存的最小单位

     alloc_page
     alloc_pages

     get_free_pages
     get_zeroed_page

     free_page

    分配的节点 gfp 
    #define ___GFP_DMA      0x01u
    #define ___GFP_HIGHMEM  0x02u
    #define ___GFP_DMA32    0x04u
    #define ___GFP_MOVABLE  0x08u

    如果最高级内存区域不足时, 按照ZONE_HIGHMEM->ZONE_NORMAL->ZONE_DMA 依次降级申请。

    内存分配行为

    • GFP_ATOMIC 表示内存必须是原子的,最高优先级。任何情况下都不许睡眠,如果空闲内存不足则从紧急内存中分配。

            适用于中断程序,以及持有自旋锁的进程上下文中。

    • GFP_KERNEL 常用,内核分配内存可能阻塞睡眠,可以允许内核置换出一些不活跃的内存页到磁盘中。

            适用于安全调度的进程上下文中

    • GFP_NOIO和GFP_NOFS 分别禁止内核在分配内存时进行磁盘IO和文件系统IO操作
    • GFP_USER 用于映射到用户空间的内存分配,通常这些内存可以被内核或者硬件直接访问,如硬件设备会将Buffer直接映射到用户空间中。
    • GFP_DMA和GFP_DMA32 表示需要从NODE_DMA和NODE_DMA32内存区域申请适用于DMA的内存。
    • GFP_HIGHUSER 给用户空间分配高端内存,因为在用户虚拟内存空间中,都是通过页表来访问非直接映射的高端内存区域,所有用户空间一般使用的是高端内存区ZONE_HIGHMEM 

    内存分配ALLOC*标志 

    1. #define ALLOC_WMARK_MIN     WMARK_MIN
    2. #define ALLOC_WMARK_LOW     WMARK_LOW
    3. #define ALLOC_WMARK_HIGH    WMARK_HIGH
    4. #define ALLOC_NO_WATERMARKS 0x04 /* don't check watermarks at all */
    5. #define ALLOC_HARDER         0x10 /* try to alloc harder */
    6. #define ALLOC_HIGH       0x20 /* __GFP_HIGH set */
    7. #define ALLOC_CPUSET         0x40 /* check for correct cpuset */
    8. #define ALLOC_KSWAPD        0x800 /* allow waking of kswapd, __GFP_KSWAPD_RECLAIM set */
    • ALLOC_NO_WATERMARKS 表示在内存分配过程中,不考虑上述三个水位线的影响
    • ALLOC_WMARK_HIGH ,表示当前物理内存区域zone中剩余内存页的数量至少要达到WMARK_HIGI水位线才能分配内存
    • ALLOC_WMARK_LOW ,ALLOC_WMARK_MIN 表示当前物理内存区域zone中剩余内存页的数量至少要达到WMARK_LOW或者MIN才能分配。
    • ALLOC_HARDER 表示分配内存时,放宽分配规则,就是降低WMARK_MIN水位线,努力使内存分配最大可能成功
    • ALLOC_HIGH 需要设置__GFP_HIGH 才有效,表内存请求是最高优先级,表不允许失败,如果内存不足才紧急预留内存中分配。
    • ALLOC_CPUSET 表示内存只能在当前进程所允许的CPU关联的NUMA节点中进行分配。比如cgroup限制进程只能在某些特定的CPU上运行,那么进程所发起的内存分配请求,只能在这些特定的NUMA节点中进行。
    • ALLOC_KSWAPD表示允许唤醒NUMA节点中的KSWAPD进程,异步进行内存回收。内核为每个NUMA节点分配一个kswapd进程用于回收不经常使用的页面。

    页面阈值 

    WMARK_MIN(页最小阈值), WMARK_LOW (页低阈值),WMARK_HIGH(页高阈值)。

    1. 当物理内存剩余容量高于_watermark[WMARK_HIGH]时,说明此物理内存充足
    2. 当剩余内存在WMARK_LOW,WMARK_HIGH]时,说明内存有消耗,能满足分配需求
    3. 当剩余内存在WMARK_MIN,WMARK_LOW时,说明内存有压力,但能满足进程此时的内存分配要求,当给进程分配好后,会唤醒kswapd进程开始回收内存,直到剩余内存在WMARK_HIGI之上。
    4. 当剩余内存低于 WMARK_MIN时,说明此时的内存容量已经非常危险,直接回收内存; 

    内存紧张时

    1. 产生 OOM,将 OOM 优先级最高的进程干掉
    2. 内存回收,将不经常使用到的内存回收
    3. 内存规整,将可迁移的物理页面进行迁移规整,消除内存碎片。

    文件页:物理内存页中的数据来自磁盘文件,当对文件读取时候,内核会根据局部性原理将读取磁盘数据缓存在page cache中,cacha page就是存放的文件页。再次读取时,从cache中读。

            回收时,直接回收即可,再次读取时,从磁盘重新读取。但,文件页修改还没来得及回写到磁盘,此时的文件页是脏页,不能进行回收,需要将脏页回写到磁盘才能回收。

    匿名页:背后没有一个磁盘作为数据源,数据时通过进程运行过程中产生,比如动态分配的堆内存。

            回收时,都需要将匿名页中的数据先保存在磁盘中,然后再回收。当进程再次访问这块内存时,重新把数据从磁盘空间读到内存,这块磁盘空间是可以单独是swap分区或者是一个特殊的swap文件。匿名页的回收机制就是常看到的swap机制。

    内核回收的是文件页还是匿名页

    cat /proc/sys/vm/swappiness 命令查看,

            swappiness 选项的取值范围为 0 到 100,默认为 60。

            swappiness 用于表示 Swap 机制的积极程度,

    数值越大,Swap 的积极程度越高,内核越倾向于回收匿名页。

    数值越小,Swap 的积极程度越低。内核就越倾向于回收文件页。

    内存分配流程

    快速路径分配

            get_page_from_freelist

    慢速路径分配

            __alloc_pages_slowpath

    慢速路径初始化参数

             retry_cpuset:

            调整内存分配策略alloc_flags,采用更加激进方式

            内存分配主要在允许的CPU相关联的NUMA节点上

            内存水位线下调至WMARK_LOW

            唤醒所有kswapd进程进行异步内存回收

            触发直接内存整理direct_compact获取更多内存

     retry:

            进一步调整内存分配aloc_flags,使用更加激进的内存分配手段

            在内存分配时忽略水位线

            直接触发内存回收direct_reclaim

            再次触发直接内存整理direct_compact

            OOM机制

     nopage:

            以上仍然不能分配,如果设置__GFP_NOFAIL不允许失败,则不停重试以上分配过程

     fail:

            分配失败,输出经过信息。

     got_pg

            内存分配成功,返回新申请的内存块

    return page;

     direct_compact:

            在页面回收时,把可移动的聚在一起,不可以移动的聚在一起,去碎片化,然后进行成块回收。

    学习链接:

    kernel学习链接

     参考

    https://course.0voice.com/v1/course/intro?courseId=2&agentId=0

  • 相关阅读:
    真香,Java架构进阶全靠这份阿里大佬整理的笔记,图文并茂
    IRC/ML:金融智能风控—信贷风控场景简介、两大场景(贷款场景+信用卡场景)、信用卡评分模型设计、反欺诈检测技术的简介、案例应用之详细攻略
    TIOBE 5 月榜单揭晓:哪些编程语言正在上升?
    电子信息工程专业课复习知识点总结:(五)通信原理
    字符串入门十八讲合集四
    selenium报错解决
    typescript80-使用配置文件编译文件
    nginx.3——local的优先级和匹配方式
    多人协同开发,git workflow 提高团队协作能力
    【计算机视觉40例】案例32:定位人脸
  • 原文地址:https://blog.csdn.net/WANGYONGZIXUE/article/details/132818238