Linux内核内存管理之内存结构概述(一)

一句话内存是进程的“家”,目前现代操作系统内存管理基本上使用的是虚拟内存管理。一个地方出现问题,系统不会崩掉,会有相对应的处理,起到保护的作用。虚拟内存还有能让程序使用连续的、比实际内存更大的空间,而这些内存可能在物理上是离散的、甚至交换到硬盘上。
一、地址空间
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;




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值