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

随机访问存储器

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


物理架构


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


numactl 命令查看 ,指定应用程序运行在哪些CPU核心上,同时也可以绑定应用程序可以在哪些NUMA节点上分配内存。
    
    numactl --membind=nodes --cpunodebind=nodes command
    
        -membind 指定应用程序只能在哪些NUMA节点上分配内存,如果这些节点内存不足,则分配失败;
        -cpunodebind 执行应用程序只能在哪些NUMA上运行

管理内存节点 
    struct pglist_data 的全局数组node_data[] 来管理所有NUMA节点。
    (arch/arm64/include/asm/mmzone.h)
    
     #define CONFIG_NUMA
     extern struct pglist_data *node_data[];
     #define NODE_DATA(nid) (node_data[(nid)])
    
 NODE_DATA(nid) 宏可以通过NUMA节点的nodeId 找到对应的struct pglist_data结构

物理内存划分

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

ZONE_DMA
ZONE_DMA32
ZONE_NORMAL
ZONE_HIGHMEM
ZONE_MOVABLE

 node ->zone->page

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

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

 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 依次降级申请;

分配行为

  1. 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

  1. ALLOC*标志

#define ALLOC_WMARK_MIN     WMARK_MIN
#define ALLOC_WMARK_LOW     WMARK_LOW
#define ALLOC_WMARK_HIGH    WMARK_HIGH
#define ALLOC_NO_WATERMARKS 0x04 /* don't check watermarks at all */

#define ALLOC_HARDER         0x10 /* try to alloc harder */
#define ALLOC_HIGH       0x20 /* __GFP_HIGH set */
#define ALLOC_CPUSET         0x40 /* check for correct cpuset */

#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(页高阈值)。

 

 

当物理内存剩余容量高于_watermark[WMARK_HIGH]时,说明此物理内存充足

当剩余内存在WMARK_LOW,WMARK_HIGH]时,说明内存有消耗,能满足分配需求;

当剩余内存在WMARK_MIN,WMARK_LOW时,说明内存有压力,但能满足进程此时的内存分配要求,当给进程分配好后,会唤醒kswapd进程开始回收内存,直到剩余内存在WMARK_HIGI之上。

当剩余内存低于 WMARK_MIN时,说明此时的内存容量已经非常危险,直接回收内存;

内存分配伪代码

快速路径分配

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:

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

你可能感兴趣的:(linux内核分析,linux,运维,服务器)