内存管理数据结构总结

作者 by adtxl / 2022-04-15 / 暂无评论 / 303 个足迹

整理自《奔跑吧,Linux内核,张天飞》

  1. 简述

在大部分Linux系统中,内存设备的初始化一般是在BIOS或bootloader中,然后把DDR的大小传递给Linux内核,因此从Linux内核角度来看DDR,其实就是一段物理内存空间。在Linux内核中,和内存硬件物理特性相关的一些数据结构主要集中在MMU(处理器中内存管理单元)中,例如页表、cache/TLB操作等。因此大部分的Linux内核中关于内存管理的相关数据结构都是软件的概念,例如mm、vma、zone、page、pg_data等。Linux内核中内存管理中的数据结构错综复杂,归纳总结如下

image.png

ps:图中的pg_date应是pg_data

2. 数据结构之间的转换

3.1 由mm_struct数据结构和虚拟地址vaddr找到对应的VMA

内核提供相当多的API来找到VMA。

struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr);
struct vm_area_struct *find_vma_prev(struct mm_struct *mm, unsigned long addr,
            struct vm_area_struct **pprev)
static inline struct vm_area_struct * find_vma_intersection(struct mm_struct * mm,
    unsigned long start_addr, unsigned long end_addr);

由VMA得出MM数据结构,struct vm_area_struct数据结构有一个指针指向struct mm_struct

struct vm_area_struct {
    .....
    struct mm_struct *vm_mm;
    .....
}

2.2 由page和VMA找到虚拟地址vaddr

// 只针对匿名页面

[mm/rmap.c]

/*
 * At what user virtual address is page expected in @vma?
 */
static inline unsigned long
__vma_address(struct page *page, struct vm_area_struct *vma)
{
    pgoff_t pgoff = page_to_pgoff(page);
    return vma->vm_start + ((pgoff - vma->vm_pgoff) << PAGE_SHIFT);
}

inline unsigned long
vma_address(struct page *page, struct vm_area_struct *vma)
{
    unsigned long address = __vma_address(page, vma);

    /* page should be within @vma mapping range */
    VM_BUG_ON_VMA(address < vma->vm_start || address >= vma->vm_end, vma);

    return address;
}

=>pgoff = page->index; 表示一个vma中page的index
=>vaddr = vma->vm_start + ((pgoff - vma->vm_pgoff) << PAGE_SHIFT);

2.3 由page找到所有映射的VMA

通过反向映射rmap系统来实现`rmap_walk()`
对于匿名页面来说:
=>由page->mapping找到anon_vma数据结构
=>遍历anon_vma->rb_root红黑树,取出avc数据结构
=>每个avc数据结构指向每个映射的VMA

由VMA和虚拟地址addr,找出相应的page数据结构

[include/linux/mm.h]

static inline struct page *follow_page(struct vm_area_struct *vma,
        unsigned long address, unsigned int foll_flags)

=> 由虚拟地址vaddr通过查询页表找出pte
=> 由pte找出页帧号pfn,然后在mem_map[]找到相应的struct page结构

2.4 page和pfn之间的互换

[include/asm-generic/memory_model.h]

//由page到pfn
#define __page_to_pfn(page) ((unsigned long)((page) - mem_map) + \
                 ARCH_PFN_OFFSET)
//由pfn到page
#define __pfn_to_page(pfn)  (mem_map + ((pfn) - ARCH_PFN_OFFSET))

2.5 pfn和paddr之间的互换

[arch/arm/include/asm/memory.h]

//由paddr到pfn
#define __phys_to_pfn(paddr)    ((unsigned long)((paddr) >> PAGE_SHIFT))
//由pfn到paddr
#define __pfn_to_phys(pfn)  ((phys_addr_t)(pfn) << PAGE_SHIFT)

2.6 page和pte之间的互换

[arch/arm/include/asm/pgtable.h]

由page到pte:
先由page到pfn
然后由pfn到pte
#define pte_page(pte)       pfn_to_page(pte_pfn(pte))

#define pte_pfn(pte)        ((pte_val(pte) & PHYS_MASK) >> PAGE_SHIFT)
#define pfn_to_page __pfn_to_page
#define __pfn_to_page(pfn)  (mem_map + ((pfn) - ARCH_PFN_OFFSET))

2.7 zone和page之间的互换

由zone到page:
zone数据结构有zone->start_pfn指向zone起始的页面,然后由pfn找到page数据结构。

有page到zone:
page_zone()函数返回page所属的zone,通过page->flags布局实现。

2.8 zone和pg_data之间的互换

由pg_data到zone:
pg_data_t->node_zones

由zone到pg_data:
zone->zone_pgdat

独特见解