物理内存是如何组织管理的
Posted Loopers
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了物理内存是如何组织管理的相关的知识,希望对你有一定的参考价值。
内存管理,相比大家都听过。但是内存管理到底是做什么呢?这就得从计算机刚出来的时候说起。计算机刚出来的时候内存资源很紧张,只有几十K,后来慢慢的到几百K,到周后来的512M,再到现在的几个G。真是因为内存资源的不足,在计算机的整个过程中衍生出各种各样的内存管理方法。
而内存管理的终极目标就是合理的不浪费的使用物理内存。Linux针对如何合理的使用物理内存,软件上设计了多种的内存管理方法。今天我们就来讨论下Linux是如何组织物理内存的,通俗的说就是如何管理电脑的内存条的。
Linux使用节点(node),区域(zone),页(page)三级结构来描述整个物理内存。
node
目前计算机系统有两种体系结构:
- 非一致性内存访问 NUMA(Non-Uniform Memory Access)意思是内存被划分为各个node,访问一个node花费的时间取决于CPU离这个node的距离。每一个cpu内部有一个本地的node,访问本地node时间比访问其他node的速度快
- 一致性内存访问 UMA(Uniform Memory Access)也可以称为SMP(Symmetric Multi-Process)对称多处理器。意思是所有的处理器访问内存花费的时间是一样的。也可以理解整个内存只有一个node。
- NUMA通常用在服务器领域,可以通过CONFIG_NUMA来配置是否开启
zone
ZONE的意思是把整个物理内存划分为几个区域,每个区域有特殊的含义。
先来看下内核中对zone的定义
enum zone_type
#ifdef CONFIG_ZONE_DMA
/*
* ZONE_DMA is used when there are devices that are not able
* to do DMA to all of addressable memory (ZONE_NORMAL). Then we
* carve out the portion of memory that is needed for these devices.
* The range is arch specific.
*
* Some examples
*
* Architecture Limit
* ---------------------------
* parisc, ia64, sparc <4G
* s390 <2G
* arm Various
* alpha Unlimited or 0-16MB.
*
* i386, x86_64 and multiple other arches
* <16M.
*/
ZONE_DMA,
#endif
#ifdef CONFIG_ZONE_DMA32
/*
* x86_64 needs two ZONE_DMAs because it supports devices that are
* only able to do DMA to the lower 16M but also 32 bit devices that
* can only do DMA areas below 4G.
*/
ZONE_DMA32,
#endif
/*
* Normal addressable memory is in ZONE_NORMAL. DMA operations can be
* performed on pages in ZONE_NORMAL if the DMA devices support
* transfers to all addressable memory.
*/
ZONE_NORMAL,
#ifdef CONFIG_HIGHMEM
/*
* A memory area that is only addressable by the kernel through
* mapping portions into its own address space. This is for example
* used by i386 to allow the kernel to address the memory beyond
* 900MB. The kernel will set up special mappings (page
* table entries on i386) for each page that the kernel needs to
* access.
*/
ZONE_HIGHMEM,
#endif
ZONE_MOVABLE,
#ifdef CONFIG_ZONE_DEVICE
ZONE_DEVICE,
#endif
__MAX_NR_ZONES
;
为了更好的解释各个ZONE的含义,比如上图。
32位系统:
在32位系统中,假设我们物理内存是4G的。
- DMA_ZONE是因为在X86架构下,有些DMA设备只能访问16M以下的地址,所以设计出了DMA_ZONE,当DMA设备访问内存时,从DMA_ZONE去获取内存
- HIGHMEM_ZONE: HIGHMEM_ZONE是32位时代的产物。出现的原因是:32位系统中4G的虚拟地址空间划分为0-3G是用户空间,3-4G是内核空间。而内核为了方便操作,需要将物理地址和内核虚拟空间建立线性映射的,而就因为内核只有1G空间,而物理内存有4G,是完全不能够线性映射的。这时候就将内核3G-3G+896M的地址线性映射到物理内存0-896M的区域。而896-4G的不能映射的区域就叫highmem_zone了。此处896是经典的x86架构的值,arm架构的值没研究。
- NORAML_ZONE: 16M-896M的区域就称为NORAML_ZONE了。
- 通常将HIGHMEM_ZONE的内存区域称为高端内存,896M以下的内存称为低端内存,低端内存是线性映射的
可以看看我的32位ubuntu机器,存在Noraml zone,DMA zone,HighMem zone。
64位系统
- 在64位系统上因为虚拟地址空间已经足够大了。比如当地址宽度的位数是39位的时候。用户空间和内核空间大小是一样大,大小是512G。
- 假设此时物理内存是4G,则整个4G都可以全部映射到内核虚拟地址区间的。所以说64位机器上已经不存在HIGHMEM_ZONE了。
- 而在x86的64位机器上还可能存在DMA,DMA_32的区域,用于DMA传输使用。
-
比如我的ubuntu机器,可以通过/proc/buddinfo看具体zone的信息
root@root-OptiPlex-7060:~$ cat /proc/buddyinfo
Node 0, zone DMA 3 3 1 1 3 2 0 0 1 1 3
Node 0, zone DMA32 4053 729 155 166 105 43 151 0 0 0 0
Node 0, zone Normal 33893 8921 6356 1472 1221 101 48 10 4 0 0
再比如看下我的一台ARm64的手机。
root:/ # cat /proc/buddyinfo
Node 0, zone Normal 12 7 148 52 114 39 16 8 5 5 117
Node 0, zone Movable 470 1135 880 340 35 8 4 2 3 0 653
可以看到在64位机器上已经不存在HIGHMEM_ZONE了。只剩下一个NORAML_ZONE
ZONE_MOVABLE:用于内存碎片技术,意思就是当内存出现碎片的时候,为了调整出一个大得连续内存的时候,就需要将Moveablezone的内容做交换,换出一个大得连续的内存区域。
page
就是代表一个物理页,一个物理页用一个struct page在内核中表示。
struct page
unsigned long flags; /* Atomic flags, some possibly
* updated asynchronously */
/*
* Five words (20/40 bytes) are available in this union.
* WARNING: bit 0 of the first word is used for PageTail(). That
* means the other users of this union MUST NOT use the bit to
* avoid collision and false-positive PageTail().
*/
union
struct /* Page cache and anonymous pages */
/**
* @lru: Pageout list, eg. active_list protected by
* zone_lru_lock. Sometimes used as a generic list
* by the page owner.
*/
struct list_head lru;
/* See page-flags.h for PAGE_MAPPING_FLAGS */
struct address_space *mapping;
pgoff_t index; /* Our offset within mapping. */
/**
* @private: Mapping-private opaque data.
* Usually used for buffer_heads if PagePrivate.
* Used for swp_entry_t if PageSwapCache.
* Indicates order in the buddy system if PageBuddy.
*/
unsigned long private;
;
struct /* slab, slob and slub */
union
struct list_head slab_list; /* uses lru */
struct /* Partial pages */
struct page *next;
#ifdef CONFIG_64BIT
int pages; /* Nr of pages left */
int pobjects; /* Approximate count */
#else
short int pages;
short int pobjects;
#endif
;
;
struct kmem_cache *slab_cache; /* not slob */
/* Double-word boundary */
void *freelist; /* first free object */
union
void *s_mem; /* slab: first object */
unsigned long counters; /* SLUB */
struct /* SLUB */
unsigned inuse:16;
unsigned objects:15;
unsigned frozen:1;
;
;
;
struct /* Tail pages of compound page */
unsigned long compound_head; /* Bit zero is set */
/* First tail page only */
unsigned char compound_dtor;
unsigned char compound_order;
atomic_t compound_mapcount;
;
struct /* Second tail page of compound page */
unsigned long _compound_pad_1; /* compound_head */
unsigned long _compound_pad_2;
struct list_head deferred_list;
;
struct /* Page table pages */
unsigned long _pt_pad_1; /* compound_head */
pgtable_t pmd_huge_pte; /* protected by page->ptl */
unsigned long _pt_pad_2; /* mapping */
union
struct mm_struct *pt_mm; /* x86 pgds only */
atomic_t pt_frag_refcount; /* powerpc */
;
#if ALLOC_SPLIT_PTLOCKS
spinlock_t *ptl;
#else
spinlock_t ptl;
#endif
;
struct /* ZONE_DEVICE pages */
/** @pgmap: Points to the hosting device page map. */
struct dev_pagemap *pgmap;
unsigned long hmm_data;
unsigned long _zd_pad_1; /* uses mapping */
;
/** @rcu_head: You can use this to free a page by RCU. */
struct rcu_head rcu_head;
;
union /* This union is 4 bytes in size. */
/*
* If the page can be mapped to userspace, encodes the number
* of times this page is referenced by a page table.
*/
atomic_t _mapcount;
/*
* If the page is neither PageSlab nor mappable to userspace,
* the value stored here may help determine what this page
* is used for. See page-flags.h for a list of page types
* which are currently stored here.
*/
unsigned int page_type;
unsigned int active; /* SLAB */
int units; /* SLOB */
;
/* Usage count. *DO NOT USE DIRECTLY*. See page_ref.h */
atomic_t _refcount;
#ifdef CONFIG_MEMCG
struct mem_cgroup *mem_cgroup;
#endif
/*
* On machines where all RAM is mapped into kernel address space,
* we can simply calculate the virtual address. On machines with
* highmem some memory is mapped into kernel virtual memory
* dynamically, so we need a place to store that address.
* Note that this field could be 16 bits on x86 ... ;)
*
* Architectures with slow multiplication can define
* WANT_PAGE_VIRTUAL in asm/page.h
*/
#if defined(WANT_PAGE_VIRTUAL)
void *virtual; /* Kernel virtual address (NULL if
not kmapped, ie. highmem) */
#endif /* WANT_PAGE_VIRTUAL */
#ifdef LAST_CPUPID_NOT_IN_PAGE_FLAGS
int _last_cpupid;
#endif
可以看到struct page结构体里面基本都是联合体,就是为了节省空间。因为物理页很多 ,则为了表示物理页就需要很多的page,而page是需要占用内存的。所以page结构体采用了联合体这种结构来组织。但是可读性很差。
Page Frame
为了描述一个物理page,内核使用struct page结构来表示一个物理页。假设一个page的大小是4K的,内核会将整个物理内存分割成一个一个4K大小的物理页,而4K大小物理页的区域我们称为page frame
Page Frame Num(PFN)
将物理地址分成一块一块的大小,就比如大小是4K的话,将一个物理页的区域我们称为page frame, 而对每个page frame的编号就称为PFN.
物理地址和pfn的关系是:物理地址>>PAGE_SHIFT = pfn
pfn和page的关系:
内核中支持了好几个内存模型:CONFIG_FLATMEM(平坦内存模型)CONFIG_DISCONTIGMEM(不连续内存模型)CONFIG_SPARSEMEM_VMEMMAP(稀疏的内存模型)目前ARM64使用的稀疏的类型模式
/* memmap is virtually contiguous. */
#define __pfn_to_page(pfn) (vmemmap + (pfn))
#define __page_to_pfn(page) (unsigned long)((page) - vmemmap)
刚开机的时候,内核会将整个struct page映射到内核虚拟地址空间vmemmap的区域,所以我们可以简单的认为struct page的基地址是vmemmap,则:
vmemmap+pfn的地址就是此struct page对应的地址.
总结:
一个物理内存分为好几个node,每个node存在好几个zone,每个zone中细分为page大小。
以上是关于物理内存是如何组织管理的的主要内容,如果未能解决你的问题,请参考以下文章
Linux 内核 内存管理物理内存组织结构 ⑥ ( 物理页 page 简介 | 物理页 page 与 MMU 内存管理单元 | 内存节点 pglist_data 与 物理页 page 联系 )
Linux 内核 内存管理物理内存组织结构 ② ( 内存模型 | 平坦内存 | 稀疏内存 | 非连续内存 | 内存管理系统三级结构 | 节点 Node | 区域 Zone | 页 Page )
Linux 内核 内存管理物理内存组织结构 ③ ( 内存管理系统三级结构 | 内存节点描述 | 内存节点 pglist_data 结构体 | pglist_data 结构体源码 )
Linux 内核 内存管理物理内存组织结构 ④ ( 内存区域 zone 简介 | zone 结构体源码分析 | zone 结构体源码 )