一句话内存是进程的“家”,目前现代操作系统内存管理基本上使用的是虚拟内存管理。一个地方出现问题,系统不会崩掉,会有相对应的处理,起到保护的作用。虚拟内存还有能让程序使用连续的、比实际内存更大的空间,而这些内存可能在物理上是离散的、甚至交换到硬盘上。
一、地址空间
1、我们知道程序在操作是访问的地址是逻辑地址,CPU通过总线操作的内存地址称为线性地址,内存单元对应的实际地址是物理地址。具体区别百度一下;
2、对于32位linux系统,CPU能访问的虚拟地址空间0x0~0xFFFFFFFF,低3GB的地址(0x0~0xC0000000)给应用层的地址空间,高地址1GB(0xC0000000~0xFFFFFFFF)是留给内核的;
3、内核里所有的线程共享1GB的地址空间,而每个应用进程都是独立私有的3GB地址空间。
二、分页
1、为了有效的管理,物理内存分为固定大小的块,称为帧或页(Page)。物理页的大小是有硬件决定的,通常是2的幂,X86平台为4KB。虚拟地址包含页号和偏移地址,页号作为页表的索引通过MMU映射到物理内存的基地址上,再加上页内偏移地址得到完整的物理地址。
2、分页管理将物理页与虚拟页一一映射,这样就可以将连续的内存映射到非连续的物理页上。此外利于碎片管理,磁盘交换都有较好的优越性。
3、内核对物理页都是用一个数据结构struct page来表示。所有的页描述符存放在mem_map数组中。
1、习惯上认为计算机内存是一种均匀、共享的资源。在忽略硬件高速缓存作用情况下,我们感觉无论那个内存或那个CPU对内存单元的访问需要相同的时间,但实际上并不成立。
2、linux2.6支持非一致内存访问模型,系统的物理内存划分为几个节点(node)。在一个独立的节点内,任意给定的CPU访问页面所需的时间都是相同的,对于不同的CPU,这个时间可能不同。
四、内存结构
1、对于NUMA系统有一个数组node_data来存所有的node的pg_data_t,UMA利用一个全局变量contig_page_data存储唯一node的内存。
2、每个内存节点下物理内存分成几个Zone(区域)
linux将X86的物理内存分为三个Zone:
① ZONE_DMA(0~16MB):DMA内存的分配区域;
② ZONE_NORMAL(16MB~896MB):正常映射的内存区,这两个内核直接把物理地址和虚拟地址进行永久的线性映射;
③ ZONE_HIGHMEM(896MB~):高端内存区域,物理页不能永久映射到虚拟地址上,只在分配了物理页后动态映射。
注意:Zone的分布和使用适合体系结构有关的,某些体系结构没有DMA访问的限制,那么它的DMA Zone就是空,某些体系结构寻址能力不超过1GB,那么HIGHMEN就不需要了。
五、node(内存节点)
对于每个Node的内存,内核定义了一个数据结构pg_data_t来描述。
注:对每个Zone的物理内存页,Linux用BUDDY算法来管理空闲页的分配与回收。基于BUDDY层,再用SLAB进行小块内存的管理与分配。(后面章节详细介绍)
一、地址空间
1、我们知道程序在操作是访问的地址是逻辑地址,CPU通过总线操作的内存地址称为线性地址,内存单元对应的实际地址是物理地址。具体区别百度一下;
2、对于32位linux系统,CPU能访问的虚拟地址空间0x0~0xFFFFFFFF,低3GB的地址(0x0~0xC0000000)给应用层的地址空间,高地址1GB(0xC0000000~0xFFFFFFFF)是留给内核的;
3、内核里所有的线程共享1GB的地址空间,而每个应用进程都是独立私有的3GB地址空间。
二、分页
1、为了有效的管理,物理内存分为固定大小的块,称为帧或页(Page)。物理页的大小是有硬件决定的,通常是2的幂,X86平台为4KB。虚拟地址包含页号和偏移地址,页号作为页表的索引通过MMU映射到物理内存的基地址上,再加上页内偏移地址得到完整的物理地址。
2、分页管理将物理页与虚拟页一一映射,这样就可以将连续的内存映射到非连续的物理页上。此外利于碎片管理,磁盘交换都有较好的优越性。
3、内核对物理页都是用一个数据结构struct page来表示。所有的页描述符存放在mem_map数组中。
struct page {
unsigned long flags; //表示页面的状态
/*页的应用计数器。如果该字段为-1,表示相应也空闲,
可以分配给人以进程或内核本身;如果该字段的值大于
或等于0,则说明该页被分配给了一个或多个进程,
或用于存放一些内核数据结构*/
atomic_t _count;
union {
/*页中页表项的个数。
个人理解:页中可能会存储表项(保存索引值以及下一级页表或页的基地址),
逻辑地址转换成物理地址会用到。*/
atomic_t _mapcount;
struct { /* SLUB uses */
short unsigned int inuse;
short unsigned int offset;
};
};
union {
struct {
unsigned long private;//当页表插入也告诉缓冲中时使用
struct address_space *mapping;
};
struct { /* SLUB uses */
void **lockless_freelist;
struct kmem_cache *slab; /* Pointer to slab */
};
struct {
struct page *first_page; /* Compound pages */
};
};
union {
pgoff_t index;
void *freelist; /* SLUB: freelist req. slab lock */
};
struct list_head lru;
};
三、NUMA(非一致内存访问)
1、习惯上认为计算机内存是一种均匀、共享的资源。在忽略硬件高速缓存作用情况下,我们感觉无论那个内存或那个CPU对内存单元的访问需要相同的时间,但实际上并不成立。
2、linux2.6支持非一致内存访问模型,系统的物理内存划分为几个节点(node)。在一个独立的节点内,任意给定的CPU访问页面所需的时间都是相同的,对于不同的CPU,这个时间可能不同。
四、内存结构
1、对于NUMA系统有一个数组node_data来存所有的node的pg_data_t,UMA利用一个全局变量contig_page_data存储唯一node的内存。
2、每个内存节点下物理内存分成几个Zone(区域)
linux将X86的物理内存分为三个Zone:
① ZONE_DMA(0~16MB):DMA内存的分配区域;
② ZONE_NORMAL(16MB~896MB):正常映射的内存区,这两个内核直接把物理地址和虚拟地址进行永久的线性映射;
③ ZONE_HIGHMEM(896MB~):高端内存区域,物理页不能永久映射到虚拟地址上,只在分配了物理页后动态映射。
注意:Zone的分布和使用适合体系结构有关的,某些体系结构没有DMA访问的限制,那么它的DMA Zone就是空,某些体系结构寻址能力不超过1GB,那么HIGHMEN就不需要了。
五、node(内存节点)
对于每个Node的内存,内核定义了一个数据结构pg_data_t来描述。
typedef struct pglist_data {
/**
* 节点管理区描述符数组
*/
struct zone node_zones[MAX_NR_ZONES];
/**
* 页分配器使用的zonelist数据结构的数组。
*/
struct zonelist node_zonelists[GFP_ZONETYPES];
/**
* 节点中管理区的个数
*/
int nr_zones;
/**
* 节点中页描述符的数组
*/
struct page *node_mem_map;
/**
* 用在内核初始化阶段
*/
struct bootmem_data *bdata;
/**
* 节点中第一个页框的下标。相对于
*/
unsigned long node_start_pfn;
/**
* 内存结点的大小,不包含空洞(以页为单位)
*/
unsigned long node_present_pages; /* total number of physical pages */
/**
* 节点的大小,包括空洞
*/
unsigned long node_spanned_pages; /* total size of physical page
range, including holes */
/**
* 节点标识符
*/
int node_id;
/**
* 内存节点链表的下一项。
*/
struct pglist_data *pgdat_next;
/**
* Kswapd页换出守护进程使用的等待队列
*/
wait_queue_head_t kswapd_wait;
/**
* 指针指向kswapd内核线程的进程描述符。
*/
struct task_struct *kswapd;
/**
* Kswapd将要创建的空闲块大小取对数的值。
*/
int kswapd_max_order;
} pg_data_t;
六、利用数据结构struct zone来描述zone,记录了zone管理的重要信息,大部分信息是在系统启动的时候由函数free_area_init初始化的,并有一个lock保护并发访问。
注:对每个Zone的物理内存页,Linux用BUDDY算法来管理空闲页的分配与回收。基于BUDDY层,再用SLAB进行小块内存的管理与分配。(后面章节详细介绍)
* ZONE_DMA < 16 MB ISA DMA capable memory
* ZONE_NORMAL 16-896 MB direct mapped by the kernel
* ZONE_HIGHMEM > 896 MB only page cache and user processes
*/
/**
* 内存管理区描述符 Zone
*/
struct zone {
/**
* 管理区中空闲页的数目
*/
unsigned long free_pages;
/**
* Pages_min-管理区中保留页的数目
* Page_low-回收页框使用的下界。同时也被管理区分配器为作为阈值使用。
* pages_high-回收页框使用的上界,同时也被管理区分配器作为阈值使用。
*/
unsigned long pages_min, pages_low, pages_high;
// 为内存不足保留的页框
unsigned long lowmem_reserve[MAX_NR_ZONES];
/**
* 用于实现单一页框的特殊高速缓存。
* 每CPU、每内存管理区都有一个。包含热高速缓存和冷高速缓存。
*/
struct per_cpu_pageset pageset[NR_CPUS];
// 保护该描述符的自旋锁
spinlock_t lock;
/**
* BUDDY页面管理(伙伴算法)
* 标识出管理区中的空闲页框块。
* 包含11个元素,被伙伴系统使用。分别对应大小的1,2,4,8,16,32,128,256,512,1024连续空闲块的链表。
* 第k个元素标识所有大小为2^k的空闲块。free_list字段指向双向循环链表的头。
*/
struct free_area free_area[MAX_ORDER];
ZONE_PADDING(_pad1_)
/* Fields commonly accessed by the page reclaim scanner */
/**
* 活动以及非活动链表使用的自旋锁。
*/
spinlock_t lru_lock;
/**
* 管理区中的活动页链表
*/
struct list_head active_list;
/**
* 管理区中的非活动页链表。
*/
struct list_head inactive_list;
/**
* 回收内存时需要扫描的活动页数。
*/
unsigned long nr_scan_active;
/**
* 回收内存时需要扫描的非活动页数目
*/
unsigned long nr_scan_inactive;
/**
* 管理区的活动链表上的页数目。
*/
unsigned long nr_active;
/**
* 管理区的非活动链表上的页数目。
*/
unsigned long nr_inactive;
/**
* 管理区内回收页框时使用的计数器。
*/
unsigned long pages_scanned; /* since last reclaim */
/**
* 在管理区中填满不可回收页时此标志被置位
*/
int all_unreclaimable; /* All pages pinned */
/**
* 临时管理区的优先级。
*/
int temp_priority;
/**
* 管理区优先级,范围在12和0之间。
*/
int prev_priority;
ZONE_PADDING(_pad2_)
/**
* 进程等待队列的散列表。这些进程正在等待管理区中的某页。
*/
wait_queue_head_t * wait_table;
/**
* 等待队列散列表的大小。
*/
unsigned long wait_table_size;
/**
* 等待队列散列表数组的大小。值为2^order
*/
unsigned long wait_table_bits;
/**
* 内存节点。
*/
struct pglist_data *zone_pgdat;
/**
* 指向管理区的第一个页描述符的指针。这个指针是数组mem_map的一个元素。
*/
struct page *zone_mem_map;
/* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */
/**
* 管理区的第一个页框的下标。指的是在mem_map数组中的下标
*/
unsigned long zone_start_pfn;
/**
* 以页为单位的管理区的总大小,包含空洞。
*/
unsigned long spanned_pages; /* total size, including holes */
/**
* 以页为单位的管理区的总大小,不包含空洞。
*/
unsigned long present_pages; /* amount of memory (excluding holes) */
/**
* 指针指向管理区的传统名称:DMA、NORMAL、HighMem
*/
char *name;
} ____cacheline_maxaligned_in_smp;