内存管理(读奔跑吧linux内核的总结)

一:物理内存初始化:

1.1:物理内存的大小

32位(4GB虚拟地址空间(用户空间内核空间))在arch/arm/boot/dts/vexpress-v2p-ca9.dts文件中配置

定义了内存的起始地址0x60000000 大小 x040000000(1GB的内存大小)

解析dts的过程:start_kernel-->setup_arch-->setup_machine_fdt-->early_init_dt_scan_nodes-->of_scan_flat_dt(遍历Nodes)-->early_init_dt_scan_memory(初始化单个内存Node)。

然后根据解析出的base/size,调用early_init_dt_add_memory_arch-->memblock_add-->memblock_add_range将解析出的物理内存加入到memblock子系统。

linux内核使用伙伴管理系统管理内存之前,采用的是memblock子系统进行简单的内存guanl,jilu内存的使用情况。

内存中的代码段和数据段,ramdisk和fdt是永久分配给内核的,GPU,Camera等预留大量的连续内存,提前预留好

memblock子系统把物理内存划分为若干内存区,按照使用类型放在动态内存集合或者静态内存集合。

具体解析看:https://blog.csdn.net/modianwutong/article/details/53162142(详细介绍memblock子系统

1.2:物理内存映射

内核使用内存之前,需要初始化内核页表,linux一般采用两层映射。PGD->PUD->PMD->PTE中间的PUD/PMD被省略,真正创建页表是在map_lowmem函数中实现,

映射区域一:0x60000000~0x60800000物理地址(0xc0000000~0xc0800000虚拟地址)一般存放内核代码数据段,(读写执行权限)包括swapper_pg_dir。

区域二:0x60800000~0x8f800000物理地址(0xc0800000~0xef800000虚拟地址)具有读写,不具有执行,是Normal memory部分。

pmd_clear()清空了三段地址页表项,0~MODULES_VADDR、MODULES_VADDR~PAGE_OFFSET、0xef800000~VMALLOC_START。

1.3:zone初始化

内存将一个node分成若干个zone进行管理,结构体include/linux/mmzone.h (struct zone)zone分类:NORMAL和HIGHMEM两种(ENUM zone_type中)。

zone的初始化在bootmem_init中进行。通过find_limits找出物理内存开始帧号min_low_pfn、结束帧号max_pfn、NORMAL区域的结束帧号max_low_pfn。

zone_sizes_init中计算出每个zone大小以及zone之间的hole,然后调用free_area_init_node创建内存节点的zone

zone_sizes_init->zone_sizes_init_node->zone_sizes_init_core用来初始化zone

ZONE_NORMAL对应的物理地址是0x60000000 - 0x8f800000,ZONE_HIGHMEM对应的物理地址是0x8f800000 - 0xa0000000。

每个zone在系统初始化的时候会计算水位值:WMARK_MIN、WMARK_LOW、WMARK_HIGH。这些参数在kswapd回收页面内存的时候会用到。计算水位的一个重要参数min_free_kbytes是在init_per_zone_wmark_min中进行的:

1.4:物理内存初始化

在内核知道物理内存ddr的大小以及计算出高端内存的起始地址和内核的空间布局后,物理页面如何加入到伙伴系统中去

伙伴系统:操作系统中常见的一种动态存储管理方法。用户提出申请时,分配一块大小合适的内存块给用户,释放时回收内存块。页的大小,和2的order次冥的页。

内核中分成几个zone来管理,zone结构体中有个free_area,每个free_area有几个相应的链表

MIGRATE_TYPES

enum {  MIGRATE_UNMOVABLE,--------------------页框内容不可移动,在内存中位置必须固定,无法移动到其它地方,核心内核分配的大部分页面都属于这一类。
    MIGRATE_RECLAIMABLE,------------------页框内容可回收,不能直接移动。因为还可以从某些源重建页面,比如映射文件的数据属于这种类别,kswapd会按照一定的规则,周期性回收这类页面。
    MIGRATE_MOVABLE,----------------------页框内容可移动,属于用户空间应用程序的页属于此类页面,他们是通过页表映射的,因此只需要更新页表项,并把数据复制到新位置就可以了。当然要注意,一个页面可能被多个进程共享,对应着多个页表项}

大部分的物理初始化页面存放在MIGRATE_MOVABLE链表中,内存页面初始化存放在2的10次幂的链表中

pageblock

通常是2的(MAX_ORDER-1)次幂的页面。

物理页面是通过2的n次幂加入到伙伴系统中去。,通过for_each_free_mem_range()函数来遍历所有的memblock内存块。找出内存块的起始地址和结束地址。

二:页表映射过程

2.1:arm32页表映射

arm32和linux内核维护的页表有所不同,有两套PTE.

PGD存放在swapper_pd_dir中,一个PGD目录包含了两份ARM32 PGD,再分配PTE时,共分配了1024个PTE,512个给linux维护使用,512个给ARM32的MMU使用,对应两个PGD的页表数目。

32bit的三级映射:PGD->PMD->PTE

2.1.1:页面映射过程

如果采用页表映射的方式,一级映射表(PGD),提供的不是物理地址,而是二级页表(PTE)的基地址。

32位虚拟地址的高12位(bit[31:20])作为访问一级页表的索引值,找到相应的表项,每个表项指向一个二级页表。

以虚拟地址的次8位(bit[19:12])作为访问二级页表的索引值,得到相应的页表项,从这个页表项中找到20位的物理页面地址。

这个过程在ARM32架构中由MMU硬件完成,软件不需要接入

2.2:页表的相关数据结构

在map_lowmem()使用create_mapping()创建页表映射,这个函数的参数结构是struct map_desc。

arch\arm\include\asm\mach\map.h: 
struct map_desc {
    unsigned long virtual;------虚拟地址起始地址
    unsigned long pfn;----------物理地址开始页帧号
    unsigned long length;-------内存空间大小
    unsigned int type;----------mem_types中的序号
}; map_desc中的type指向类型为struct mem_type的mem_types数组:
arch\arm\mm\mm.h:
struct mem_type {
    pteval_t prot_pte;------------PTE属性
    pteval_t prot_pte_s2;---------定义CONFIG_ARM_LPAE才有效
    pmdval_t prot_l1;-------------PMD属性
    pmdval_t prot_sect;-----------Section类型映射
    unsigned int domain;----------定义ARM中不同的域(eg:系统空间,用户空间等)
};
arch\arm\mm\mmu.c:
static struct mem_type mem_types[] = {
...
    [MT_MEMORY_RWX] = {
        .prot_pte  = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY,----------------------注意这里都是L_PTE_*类型,需要在写入MMU对应PTE时进行转换。
        .prot_l1   = PMD_TYPE_TABLE,
        .prot_sect = PMD_TYPE_SECT | PMD_SECT_AP_WRITE,
        .domain    = DOMAIN_KERNEL,
    },
    [MT_MEMORY_RW] = {
        .prot_pte  = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY |
                 L_PTE_XN,
        .prot_l1   = PMD_TYPE_TABLE,
        .prot_sect = PMD_TYPE_SECT | PMD_SECT_AP_WRITE,
        .domain    = DOMAIN_KERNEL,
    },
...
}

create_mapping的参数是struct map_desc类型,用于描述一个虚拟地址区域线性映射到物理区域。基于这块区域创建PGD/PTE。

 Linux的PGD页表目录和ARM32不同,总数和ARM32是一样的。在arm_mm_memblock_reserve中,通过swapper_pg_dir(内核页表存地方)可以知道其大小为16KB。pgd_offset_k可以通过init_mm结构体所指定的页面目录中来获取地址addr所指的页面目录指针pgd。然后通过addr右移PGDIR_SHIFT(21)得到pgd的索引值。最后再一级页表中找到pgd的指针。内核页表的基地址定义在0xc0004000。

一个pgd页表对应2MB的大小空间,即[addr,addr+2mb],(一个pgd对应的虚拟内存空间的大小)。

pgd的数目为2的11次方(2048)个,整个pgd页表所占空间2048*4=8kb。(pgd所占的空间大小,2048个pgd,一个pgd4字节的大小)。

这和ARM硬件的4096 PGD不一致。这里涉及到Linux实现技巧,在创建PTE中进行分析。

由于ARM-Linux采用两级页表映射,跳过PUD/PMD,直接到alloc_init_pte创建PTE。

就来看看SWAPPER_PG_DIR_SIZE,一共2048个PGD,但是每个PGD包含了两个相邻的PGD页面目录项。

所以存放PGD需要的空间通过memblock进行申请,PTE_HWTABLE_OFF和PTE_HWTABLE_SIZE都为512,所以一个1024个PTE。

early_pte_alloc分配的空间:前面512个表项是给Linux OS使用的,后512个表项是给ARM硬件MMU用的。

三:内存内核布局

https://www.cnblogs.com/arnoldlu/p/8068286.html(其中有一个内存布局框图)。

0x00000000~0xc000000(用户空间) ~0xc0004000()~0xc0008000(swapper_pg_dir)~0xc060b000(.text)~0xc075c000(.init)~0xc075e000(init_thread_union8kb)~0xc07833c0(.data)~0xc07acbf0(.bss)~0xef800000(NOrmal Memory) ~0xf0000000(8Mvmalloc_offset)~0xff000000(vmalloc)~0xfffffff

内核启动时会打印内存空间布局图:打印是在mem_init()函数中实现。

内核编译玩之后,会生成一个system.map的文件,可以找到这些数据的具体数值。

1gb的内核空间,其中一部分用于直接映射物理内存,这个区域称为线性映射区域(0~760M)这一部分内存区域被线性映射到(3gb~3gb+760mb)的虚拟地址上。,线性映射区域的虚拟地址和物理地址相差PAGE_OFFSET(3gb),可以通过-pa()虚拟转物理。高端地址的内存起始地址(760M)如何确定(arch/arm/mm/mmu.c)中的vmalloc_min中配置。

剩下的264mb空间保留给vmalloc,fixmap和高端向量表使用,

低于760mb的称为线性映射内存,高于760的称为高端内存,,我们可以从保存了240mb的虚拟地址空间中划出一部分用于动态映射高端内存,这样内核就可以访问到全部的4gb内存了。

猜你喜欢

转载自blog.csdn.net/zuodenghuakai/article/details/89088656
今日推荐