Linux内核空间布局

x86物理地址空间

Linux 内核空间分为三个区域ZONE: ZONE_DMA, ZONE_NORMAL, ZONE_HIGHMEM

 物理地址空间的顶部以下一段空间,被PCI设备的I/O内存映射占据,它们的大小和布局由PCI规范所决定。640K~1M这段地址空间被BIOS和VGA适配器所占据
由于这两段地址空间的存在,导致相应的RAM空间不能被CPU所寻址(当CPU访问该段地址时,北桥会自动将目的物理地址“路由”到相应的I/O设备上,不会发送给RAM),从而形成RAM空洞。

  • ZONE_DMA的范围是0~16M,该区域的物理页面专门供I/O设备的DMA使用。之所以需要单独管理DMA的物理页面,是因为DMA使用物理地址访问内存,不经过MMU,并且需要连续的缓冲区,所以为了能够提供物理上连续的缓冲区,必须从物理地址空间专门划分一段区域用于DMA。
  • ZONE_NORMAL的范围是16M~896M,该区域的物理页面是内核能够直接使用的。
  • ZONE_HIGHMEM的范围是896M~结束,该区域即为高端内存,内核不能直接使用。

ps: kmalloc为DMA分配内存 vmalloc 分配较大内核内存 malloc分配用户空间内存

DMA区

DMA ZONE产生的本质原因是:不一定所有的DMA都可以访问到所有的内存,这本质上是硬件的设计限制。k
主板上的ISA总线中的DMA控制器只能寻址24位物理地址,因此最多能够寻址到16MB的地方.
若ZONE_NORMAL区中的内存量不足的话,ZONE_DMA就会被当作ZONE_NORMAL.
一些较新的体系结构不支持古老的外围设备,而忽略了区域 ZONE_DMA。
IA64体系结构决定了ZONE_DMA的另一种实现方式,将其定义为覆盖4GB以下的所有内存。

直接映射区

针对早期计算机实际配置的物理内存很小, 一般地只有几MB, 所以为了提高内核通过虚拟地址访问物理地址内存的速度, 内核空间的虚拟地址与物理内存地址采用了
一种从低地址到高地址一一对应的固定映射方式.

也称为线性映射区.

但是由于硬件工艺的提升, 对大于1G的实际物理内存无法简单的对其进行直接映射,Linux规定内核映射区的上边界的值最大不能大于常数high_memory(896).
采取部分映射,剩余部分交给高端映射区.

物理内存管理

Linux内核是以物理页面(也称为page frame)为单位管理物理内存的,为了方便的记录每个物理页面的信息,Linux定义了page结构体:(位于include/linux/mm_types.h)

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_alignment;

Linux系统在初始化时,会根据实际的物理内存的大小,为每个物理页面创建一个page对象,所有的page对象构成一个mem_map数组。

内存管理区

内核源码中,内存管理区的结构体定义为:

struct zone {
...
       struct free_area  free_area[MAX_ORDER];
...
       spinlock_t            lru_lock;      
       struct zone_lru {
              struct list_head list;
       } lru[NR_LRU_LISTS];
       struct zone_reclaim_stat reclaim_stat;
       unsigned long             pages_scanned;     /* since last reclaim */
       unsigned long             flags;               /* zone flags, see below */
       atomic_long_t            vm_stat[NR_VM_ZONE_STAT_ITEMS];
       unsigned int inactive_ratio;
...
       wait_queue_head_t   * wait_table;
       unsigned long             wait_table_hash_nr_entries;
       unsigned long             wait_table_bits;
...
       struct pglist_data       *zone_pgdat;
       unsigned long             zone_start_pfn;
...
};
  • 其中zone_start_pfn表示该内存管理区在mem_map数组中的索引。
  • 内核在分配物理页面时,通常是一次性分配物理上连续的多个页面,为了便于快速的管理,内核将连续的空闲页面组成空闲区段,大小是2、4、8、16…等,
    然后将空闲区段按大小放在不同队列里,这样就构成了MAX_ORDER个队列,
    也就是zone里的free_area数组。这样在分配物理页面时,可以快速的定位刚好满足需求的空闲区段。这一机制称为buddy system。
  • 当释放不用的物理页面时,内核并不会立即将其放入空闲队列(free_area),
    而是将其插入非活动队列lru,便于再次时能够快速的得到。每个内存管理区都有1个inacitive_clean_list。
    另外,内核中还有3个全局的LRU队列,分别为active_list,inactive_dirty_list和swapper_space。其中:
    • active_list用于记录所有被映射了的物理页面
    • inactive_dirty_list用于记录所有断开了映射且未被同步到磁盘交换文件中的物理页面
    • swapper_space则用于记录换入/换出到磁盘交换文件中的物理页面。

物理页分配

分配物理内存的函数主要有

struct page * __alloc_pages(zonelist_t *zonelist, unsigned long order);
参数zonelist即从哪个内存管理区中分配物理页面,参数order即分配的内存大小。

__get_free_pages(unsigned int flags,unsigned int order);
参数flags可选GFP_KERNEL__GFP_DMA等,参数order同上。
该函数能够分配物理上连续的内存区域,得到的虚拟地址与物理地址是一一对应的。

void * kmalloc(size_t size,int flags);
该函数能够分配物理上连续的内存区域,得到的虚拟地址与物理地址是一一对应的。

物理页的回收

当空闲物理页面不足时,就需要从inactive_clean_list队列中选择某些物理页面插入空闲队列中,如果仍然不足,就需要把某些物理页面里的内容写回到磁盘交换文件里,腾出物理页面,为此内核源码中为磁盘交换文件定义了:

页表管理

为了保持兼容性,Linux最多支持4级页表,而在x86上,实际只用了其中的2级页表,即PGD(页全局目录表)和PT(页表),中间的PUD和PMD所占的位长都是0,因此对于x86的MMU是不可见的。

在内核源码中,分别为PGD,PUD,PMD,PT定义了相应的页表项,即

(定义在include/asm-generic/page.h中)

typedef struct {unsigned long pgd;} pgd_t;
typedef struct {unsigned long pud;} pud_t;
typedef struct {unsigned long pmd;} pmd_t;
typedef struct {unsigned long pte;} pte_t;

为了方便的操作页表项,还定义了以下宏:
(定义在arch/x86/include/asm/pgtable.h中)

mk_pte
pgd_page/pud_page/pmd_page/pte_page
pgd_alloc/pud_alloc/pmd_alloc/pte_alloc
pgd_free/pud_free/pmd_free/pte_free
set_pgd/ set_pud/ set_pmd/ set_pte

x86-64

在64位Linux操作系统上,分区如下:
最开始的16M内存是DMA ZONE 内存,用slab分配器的kmalloc分配获取。
DMA32 ZONE为16M~4G,高于4G的内存为Normal ZONE。

REFERENCES

原文地址:https://www.cnblogs.com/sonnet/p/15187549.html