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/zszmhd/archive/2012/08/29/2661461.html
- [ ] https://blog.csdn.net/qq_38410730/article/details/81105132
- [ ] https://www.cnblogs.com/wuchanming/p/4756911.html
内存: - [ ] https://www.cnblogs.com/losing-1216/p/4884483.html
GDT和LDT - [ ] https://www.cnblogs.com/chenwb89/p/operating_system_003.html
- [ ] https://blog.csdn.net/hzrandd/article/details/50905823