本文共 9797 字,大约阅读时间需要 32 分钟。
本专题文章承接之前专题文章,本专题我们开始学习内存管理部分,本文是概述部分。本文主要参考了《奔跑吧, Linux内核》、ULA、ULK的相关内容。
kernel版本:5.10
平台:arm64在我们已经看到如下图的地址空间映射:
关于物理地址空间分布可以通过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()
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个类型链表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可以查看页面的分配状态
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.
注: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
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上空闲对象的允许的最大数目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 lockstruct lruvec
用于指向一套lru链表,这些链表用于记录页面的使用情况,它位于内存节点struct scan_control
1.表示内存回收扫描的参数 2.nr_to_reclain: 要回收页面的数量 order: 分配页面的数量,从分配器传递过来的参数 reclaim_idx: 表示最高允许页面回收的zone nr_reclaimed: 已回收的页面数量 nr_scanned:扫描不活跃页的数量 priority: 扫描LRU链表的所有页面的LRU页面数量>>priority个页面,值越小,扫描数越大以4K页为例,基于ARMV8架构的处理器虚拟地址页表结构如下:
关于虚拟地址各段的说明:
用户空间层
主要是一些libc库封装的API,他们通过系统调用访问内核空间,这些常用的API包括malloc, mmap, mlock, madvise, mremap等。内核空间层
内核空间层主要提供了系统调用给到用户空间,相关系统调用包括sys_brk, sys_mmap,sys_madvise。提供了如下的功能:VMA管理, 匿名页面,page cache, 页面回收,反向映射,slab分配器,页表管理等。硬件层
包括MMU,TLB和cache部件,以及板载内存转载地址:http://iiqd.baihongyu.com/