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