博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
内存管理基础学习笔记 - 1. 概述
阅读量:144 次
发布时间:2019-02-28

本文共 9797 字,大约阅读时间需要 32 分钟。

目录

1. 前言

本专题文章承接之前专题文章,本专题我们开始学习内存管理部分,本文是概述部分。本文主要参考了《奔跑吧, Linux内核》、ULA、ULK的相关内容。

kernel版本:5.10

平台:arm64

2. 回顾说明

地址空间映射

在我们已经看到如下图的地址空间映射:

在这里插入图片描述

也即是说在内存初始化阶段已经为物理地址空间创建了映射关系,包括memblock.memory区域,kernel image, DTB,swapper_pg页表区域。
如上图,其中:
PAGE_OFFSET: 决定了用户空间与内核空间的划分界限;

关于物理地址空间分布可以通过cat /proc/iomem查看如下:

/ # cat /proc/iomem 00000000-03ffffff : 0.flash flash@004000000-07ffffff : 0.flash flash@009000000-09000fff : pl011@9000000  09000000-09000fff : 9000000.pl011 pl011@900000009010000-09010fff : pl031@901000009030000-09030fff : pl061@9030000  09030000-09030fff : 9030000.pl061 pl061@90300000a003e00-0a003fff : a003e00.virtio_mmio virtio_mmio@a003e0010000000-3efeffff : pcie@10000000  10000000-1003ffff : 0000:00:01.0  10040000-10040fff : 0000:00:01.040000000-7fffffff : System RAM  40200000-4138ffff : Kernel code  41390000-4192ffff : reserved  41930000-4212ffff : Kernel data  48000000-480fffff : reserved  7b000000-7bffffff : reserved  7cc3d000-7cdeafff : reserved  7cded000-7cdeefff : reserved  7cdef000-7cdeffff : reserved  7cdf0000-7cdf5fff : reserved  7cdf6000-7cdfefff : reserved  7cdff000-7fffffff : reserved4010000000-401fffffff : PCI ECAM8000000000-ffffffffff : pcie@10000000  8000000000-8000003fff : 0000:00:01.0    8000000000-8000003fff : virtio-pci-modern

从以上可知

40000000-7fffffff : System RAM
40200000-4138ffff : Kernel code
41930000-4212ffff : Kernel data

关于虚拟地址的划分可以通过kernel_page_tables 查看:

/sys/kernel/debug # cat kernel_page_tables 0x0000000000000000-0xffff0000000000000xffff000000000000-0xffff0000002000000xffff000000200000-0xffff0000012000000xffff000001200000-0xffff0000013400000xffff000001340000-0xffff000002643000 0xffff000002643000-0xffff0000026440000xffff000002644000-0xffff000040000000 0xffff000040000000-0xffff008000000000 0xffff008000000000-0xffff800000000000---[ Linear Mapping end ]------[ BPF start ]---0xffff800000000000-0xffff800008000000---[ BPF end ]------[ Modules start ]---0xffff800008000000-0xffff800010000000---[ Modules end ]------[ vmalloc() area ]---0xffff800010000000-0xfffffdffbfff0000---[ vmalloc() end ]---0xfffffdffbfff0000-0xfffffdffc00000000xfffffdffc0000000-0xfffffdfffe4000000xfffffdfffe400000-0xfffffdfffe5f9000---[ Fixmap start ]---0xfffffdfffe5f9000-0xfffffdfffea00000---[ Fixmap end ]---0xfffffdfffea00000-0xfffffdfffec00000---[ PCI I/O start ]---0xfffffdfffec00000-0xfffffdffffc00000---[ PCI I/O end ]---0xfffffdffffc00000-0xfffffdffffe00000---[ vmemmap start ]---0xfffffdffffe00000-0x0000000000000000

内存管理启动部分

在分析启动代码的时候会有一些关于内存管理初始化相关的内容,在此专门将其提取出来,做一个简单的总结。

start_kernel    |--setup_arch(&command_line)    |      |- -early_fixmap_init    |      |- -early_ioremap_init    |      |- -setup_machine_fdt    |      |- -parse_early_param    |      |- -arm64_memblock_init    |      |- -paging_init    |      |- -bootmem_init    |      \- -kasan_init    |--setup_per_cpu_areas()    |--build_all_zonelists(NULL)    |--page_alloc_init()    |--mm_init()    |--kmem_cache_init_late()    |--setup_per_cpu_pageset()    \--numa_policy_init()
  1. setup_arch
    (1)early_fixmap_init
    在物理地址映射前,为了访问dtb,通过early_fixmap_init用来创建映射框架,使用时通过fixmap_remap_fdt来填充pte;
    (2)early_ioremap_init
    为访问IO内存区域创建映射框架;
    (3)setup_machine_fdt
    通过fixmap_remap_fdt填充dtb的映射,同时在memblock.resrved中保留这部分区域,将fdt的memory中描述的区域添加到memblock.memory;
    (4)parse_early_param
    主要与以下两个参数相关:
    early_param(“mem”, early_mem)
    early_param(“numa”, numa_parse_early_param)
    (5)arm64_memblock_init
    arm64_memblock_init实际就是将该保留的区间保留到memblock.reserved区域
    (6)paging_init
    为kernel image,swapper_pg页表本身,memblock.memory区域创建映射。经过paging_init将内核空间页表真正从init_pg_dir转换到swapper_pg_dir.
    (7)bootmem_init
    遍历memblock.memory的每个range,并划分为section(本例大小为1G),保存在全局mem_section数组,标记section为present; 遍历每个标记为present的mem_section, 为每个mem_section创建page结构体数组,并初始化每个node, node下的zone, 以及node下的每一个pfn对应的page
  2. setup_per_cpu_areas
    为每个cpu的per-cpu变量副本分配空间
  3. build_all_zonelists
    build_all_zonelists主要的工作就是在当前处理的结点和系统中其它节点的内存域zone之间建立一种等级次序,之后将根据这种次序来分配内存
  4. page_alloc_init
    内存页初始化,主要是在cpu发生热插拔时将CPU管理的页面移动到空闲列表
  5. mm_init
    主要功能就是将memblock管理的空闲内存释放到伙伴系统
  6. setup_per_cpu_pageset
    完成对每个cpu每个zone的pageset遍历,利用pageset_init()初始化pcp链表,和利用pageset_set_high_and_batch为每个pageset计算每次在高速缓存中将要添加或被删去的页框个数

3.基本对象

内存模型基本对象

  • struct memblock

    memblock是Linux内核启动早期使用的内存管理方案,通过struct memblock结构体来记录物理内存使用情况
    memory:记录完整的内存资源
    reserved:记录已经分配或者预留的内存资源

  • mem_section

    将物理内存划分成一个个的section,然后再划分成page,这样可以减少为物理内存的空洞创建page实例

  • struct pg_data_t

    1.每个NUMA node节点都有此数据结构来描述物理内存布局
    2.基本成员
    node_zones:用于记录本节点有哪些zone;
    node_zonelists:用于记录zone的分配顺序,包含ZONELIST_FALLBACK和 ZONELIST_NOFALLBACK,分别记录本地node和远端node的zone分配顺序;
    wait_queue_head_t kswapd_wait:每个pg_data_t都有一个此结构,由free_area_init_core()初始化
    __lruvec:每个节点都有一整套lru链表用于记录页面使用情况,__lruvec指向这些链表,根据匿名/文件,活跃/不活跃,是否可回收,分为5类LRU链表

  • struct lruvec

    用于管理页面回收的LRU链表,包括5个链表

  • struct pagevec

    借用一个数组来保存特定数目的页,可以对这些页执行相同的操作,页向量以批处理执行,比单独处理一个页的方式效率要高

  • struct zoneref

    是zonelist中某个zone的引用

  • struct zonelist

    1.代表一组zone
    2.伙伴分配器会从zonelist开启分配内存
    3.基本成员:
    struct zoneref数组:zoneref数组成员位于zone, struct zoneref数组的第一个成员指向的zone是页面分配器的第一个候选者,在第一个候选zone分配失败会从struct zoneref数组的第二个成员指向的zone进行分配,依次类推

  • struct zone

    1.代表对物理内存进行的分区,通常包括ZONE_DMA,ZONE_DMA32,ZONE_NORMAL,ZONE_HIGHMEMORY
    2.low memory包含ZONE_DMA和ZONE_NORMAL
    3.为保证性能会cache line对齐,且对访问比较频繁的锁之间填充,使得分布在不同的cache line,提高性能
    4.基本成员:
    pageblock_flags:指向每个pageblock的MIGRATE_TYPES类型的内存空间
    watermark:记录了zone的水位:WMARK_MIN, WMARK_LOW, WMARK_HIGH
    lowmem_reserve:zone中预留的内存,在紧急情况下使用,如内存回收本身需要分配的少量内存
    free_area[MAX_ORDER]: 每个free_area元素,管理一个2^order的页面,有MIGRATE_TYPES个类型链表
    在这里插入图片描述
    物理内存在linux内核中分出几个zone来管理,zone根据内核的配置来划分,zone数组大小是在struct pglist_data中定义的(pglist_data代表一个内存节点,对于UMA,内存节点个数为1)数组成员个数为MAX_NR_ZONES个,也就是表示由MAX_NR_ZONES个zone。
    zone数据结构中有一个free_area数组,数组的大小是MAX_ORDER。每个free_area数组成员维护着MIGRAGE_TYPES个链表,类型包括:

enum {
MIGRATE_UNMOVABLE, MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE,#ifdef CONFIG_CMA MIGRATE_CMA,#endif MIGRATE_PCPTYPES, /* the number of types on the pcp lists */ MIGRATE_RESERVE = MIGRATE_PCPTYPES,#ifdef CONFIG_MEMORY_ISOLATION MIGRATE_ISOLATE, /* can't allocate from here */#endif MIGRATE_TYPES};

通过cat /proc/pagetypeinfo可以查看页面的分配状态

在这里插入图片描述
可以看到大部分物理内存页面都存放在MIGRATE_MOVABLE链表中;大部分物理内存页面初始化时存放到2的10次幂的链表中。

  • enum MIGRATE_TYPES

    页面类型,包含MIGRATE_UNMOVABLE, MIGRATE_RECLAIMABLE,MIGRATE_MOVABLE等

  • struct per_cpu_pageset

    每个cpu都会初始化一个per_cpu_pageset变量,位于zone结构体, 当释放order为0的页面首先放到pageset->pcp.list对应的链表中

  • struct per_cpu_pages

    1.per_cpu_pageset的成员变量,用于描述当前zone per cpu page的状况
    2.基本成员:
    count表示当前zone 每个cpu的页面数量
    high表示当空闲页面达到此值则将回收到BUDDY
    batch表示每次回收多少个页面,通过zone_batchsize计算得出

  • struct page

    1.用于描述一个物理页面
    2.基本成员
    __count:跟踪page页面的引用情况
    PG_locked:
    mapping:指定页面所在的地址空间:文件映射 or 匿名映射

伙伴系统

伙伴系统(Buddy system)是操作系统常用的动态存储管理方法,用户提出申请时,分配一块大小合适的内存块给用户,反之在用户释放内存块时回收。在伙伴系统中,内存块是2的order次幂,Linux内核中最大的order是MAX_ORDER,通常是11,也就是把所有的空闲页面分组成11个内存块链表,order分为为:0,1,2,3,4,5,6,7,8,9,10。每个内存块链表分别包含1, 2,4, 8, 16, 32… 1024个连续页面,最大的1024个连续页面就是4M.

  • pageblock
  1. zone会将空闲内存划分为很多个pageblock, 一个pageblock最大大小为2^(MAX_ORDER-1)个page, pageblock是物理连续的页面;
  2. 如果定义了HUGETLB_PAGE特性则每个pageblock大小为2^(HUGETLB_PAGE_ORDER-1)个page;
  3. 每个pageblock有一个MIGRATE_TYPES类型:
    MIGRATE_MOVABLE(可移动),
    MIGRATE_RECLAIMABLE(可回收),
    MIGRAE_UNMOVABLE(不可移动)
    zone->pageblock_flags指针指向存放每个pageblock的MIGRATE_TYPES类型的内存空间?而free_area数组记录的是页面的MIGRATE_TYPES类型

注:MAX_ORDER一般为11,则pageblock大小为4M

  • struct free_area

    1.在struct zone中是一个数组,大小为MAX_ORDER
    2.每个数组成员管理三个链表,每个链表管理一种类型,每个链表将相同大小相同类型的页面链接起来
    3.通过cat /proc/pagetypeinfo查看各个内存块的类型及分配情况

  • struct alloc_context

    伙伴系统分配函数用于保存参数的数据结构
    zonelist:指向每个内存及诶单中对应的zonelist;
    nodemask: 内存节点的掩码;
    preferred_zoneref:表示首选zone的zoneref
    migratetype: 表示迁移类型
    high_zoneidx:分配掩码计算zone的zoneidx,表示这个分配掩码允许内存分配的最高zone
    spread_dirty_pages:用于指定是否传播脏页

SLAB

  • slab

    1.slab用来解决小内存块分配问题,不同与buddy以页为单位分配
    2.slab实际也是通过buddy来分配,只不过可以在buddy上层实现自己的分配算法,来对小内存块进行管理
    3.slab已经没有专门的数据结构表征它,一个slab是通过此slab的首个page来代表,page中有专门表征slab的成员,如slab_list用于链接到slab节点的三个链表之一。

  • struct kmem_cache

    1.它给每个CPU提供一个对象缓冲池array_cache
    2.batchcount:当array_cache本地缓冲池为空,从共享缓冲池或slabs_partial/slabs_free链表中获取对象的数目
    3.limit:当本地缓冲池空闲对象数目大于limit,将释放batchcount个对象以便于内核回收或销毁slab
    4.shared:用于多核系统
    5.size:对象的长度,要加上对齐长度
    6.flags:对象的分配掩码,影响通过伙伴系统寻找空闲页的行为
    7.num:一个slab中最多可以有多少个对象
    8.gfporder:一个slab占用2^gfporder个页面
    9.color:一个slab有多少个不同的cache line
    10.color_off:一个cache line的长度,与L1 cache line同
    11.freelist_size:每个slab对象在freelist管理区中占用1个字节,这里指freelist管理区的大小
    12:name:slab描述符的名称
    13.object_size:对象的实际大小
    14.align:对齐长度
    15.node: slab节点,struct kmem_cache_node类型,在NUMA系统中每个节点都有一个struct kmem_cache_node结构,vexpress只有一个

  • struct array_cache

    1.对象缓冲池(本地对象缓冲池和共享对象缓冲池),记录对象缓冲池可用对象数目、记录增减变化,释放的阀值等,slab描述符给每个cpu提供一个本地对象缓冲池
    2.基本成员:
    avail:对象缓冲池中可用对象数目
    limit: 对象管缓冲池可用数目的最大阀值
    batchcount:迁移对象的数目,如从共享对象缓冲池或partial/free链表迁移到本地对象缓冲池对象的数目
    touched:从缓冲池移除一个对象置1,收缩对象置0?
    entry:指向存储对象的变长数组,每个成员存放一个对象的指针。这个数组最初最多有limit个成员

  • struct kmem_cache_node

    1.简称为slab节点,包含3个slab链表:部分空闲链表、空闲链表、完全用尽链表,链表的每个成员是一个slab
    2.free_objects表示三个链表中空闲对象的总和
    3.fee_limit表示slab上空闲对象的允许的最大数目

VMALLOC

  • struct vmap_area

    代表一个vmalloc区块

  • struct vm_struct

    虚拟地址空间

进程地址空间

  • struct vm_area_struct

    1.也称为VMA, 是内核中用于表示进程虚拟地址空间的数据结构
    2.由于这些地址空间归属各个进程,因此在用户进程的struct mm_struct中也有相关成员
    3.它有一个红黑树节点,用于加入到mm_struct的红黑树中,通过红黑树查找可以加快速度
    4.它也会按照起始地址递增的方式链入到mm_struct的mmap单链表中

  • struct mm_struct

    1.描述进程内存管理的核心结构,也提供了VMA管理的相关信息
    2.mmap:进程中VMA链表的头
    mm_rb:进程中VMA红黑树的根
    mm_users:用户空间的用户个数
    mm_count:内核中引用了该数据结构的个数
    mmap_sem:保护地址空间读写的信号量
    page_table_lock:保护进程页表的spin lock

页面回收

  • struct lruvec

    用于指向一套lru链表,这些链表用于记录页面的使用情况,它位于内存节点

  • struct scan_control

    1.表示内存回收扫描的参数
    2.nr_to_reclain: 要回收页面的数量
    order: 分配页面的数量,从分配器传递过来的参数
    reclaim_idx: 表示最高允许页面回收的zone
    nr_reclaimed: 已回收的页面数量
    nr_scanned:扫描不活跃页的数量
    priority: 扫描LRU链表的所有页面的LRU页面数量>>priority个页面,值越小,扫描数越大

页表映射

  • struct mm_struct init_mm
    其中记录了PGD页表的基地址

4. ARM64页表映射

以4K页为例,基于ARMV8架构的处理器虚拟地址页表结构如下:

在这里插入图片描述

关于虚拟地址各段的说明:

在这里插入图片描述

5.内存管理总体框图

在这里插入图片描述

  • 用户空间层

    主要是一些libc库封装的API,他们通过系统调用访问内核空间,这些常用的API包括malloc, mmap, mlock, madvise, mremap等。

  • 内核空间层

    内核空间层主要提供了系统调用给到用户空间,相关系统调用包括sys_brk, sys_mmap,sys_madvise。提供了如下的功能:VMA管理, 匿名页面,page cache, 页面回收,反向映射,slab分配器,页表管理等。

  • 硬件层

    包括MMU,TLB和cache部件,以及板载内存

参考文档

  1. 《奔跑吧,Linux内核》
  2. 《深入Linux内核架构》

转载地址:http://iiqd.baihongyu.com/

你可能感兴趣的文章