Linux虚拟内存组织结构浅析(二)
Linux虚拟内存组织结构浅析(二)在前一篇文章中我们介绍了Linux虚拟内存在逻辑上的组织结构,现在就让我们从源代码入手,从程序级仔细看看各个数据结构体的内部组成如何,源代码来自于最新的kernel2.6.26.5,分析过程中主要参考了《Understanding the linux virtual memory》这本书,有兴趣的朋友可以去阅读一下。 一、节点的数据表示在内核中,
Linux虚拟内存组织结构浅析(二)
在前一篇文章中我们介绍了Linux虚拟内存在逻辑上的组织结构,现在就让我们从源代码入手,从程序级仔细看看各个数据结构体的内部组成如何,源代码来自于最新的kernel2.6.26.5,分析过程中主要参考了《Understanding the linux virtual memory》这本书,有兴趣的朋友可以去阅读一下。
一、节点的数据表示
在内核中,节点由结构体pg_data_t来表示,它是由结构体pglist_data通过typedef来定义的,位于文件<linux/mmzone.h>,其内容如下:
typedef struct pglist_data {
struct zone node_zones[MAX_NR_ZONES];
struct zonelist node_zonelists[MAX_ZONELISTS];
int nr_zones;
#ifdef CONFIG_FLAT_NODE_MEM_MAP
struct page *node_mem_map;
#endif
struct bootmem_data *bdata;
#ifdef CONFIG_MEMORY_HOTPLUG
spinlock_t node_size_lock;
#endif
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;
wait_queue_head_t kswapd_wait;
struct task_struct *kswapd;
int kswapd_max_order;
} pg_data_t;
各字段含义如下:
node_zones:包含本节点内区的描述结构体,一般而言MAX_NR_ZONES为3。
node_zonelists:这个数组指明了在分配内存时区的选择顺序。
nr_zones:指明本节点内区的个数,这一般都是1到3中的一个数字。并不是所有的节点都有3个区,比如有些节点就没有ZONE_DMA区。
node_mem_map:指向全局内存描述数组中的某一项,该元素描述了本节点的第一页物理内存。
bdata:指向bootmem_data结构体,该结构体用于系统启动时的内存分配器分配、管理该节点的内存。该内存分配器在系统启动完毕后不再使用。
node_size_lock:该自旋锁用于保护node_start_pfn、node_present_pages以及node_spanned_pages,当你要取得这些变量的值时,必须首先获取这个自旋锁。当你调用pfn_valid()前也应当获得这个自旋锁。并且注意必须在zone->lock和zone->size_seqlock前获取这个自旋锁。
node_start_pfn:这个是该节点内第一页物理内存在全局数组mem_map中的序号。
node_present_pages:当前该节点内可获得的物理页总数。
node_spanned_pages:节点内可访问的所有物理页数,包含可能存在的空洞页。
node_id:当前节点的编号,从0开始。
kswapd_wait:kswapd线程的等待队列。kswapd是一个内核线程,当系统中内存页较少时它负责回收物理内存页,这个线程常常处于睡眠状态。
kswapd:指向kswapd线程的进程描述符。
kswapd_max_order:kswapd线程要释放内存块大小取对数后的值。
二、区的数据表示
每个区由结构体struct zone来描述,其中包含了页使用统计、空闲页数、琐等信息,这个结构体在<linux/mmzone.h>中定义:
struct zone {
unsigned long pages_min, pages_low, pages_high;
unsigned long lowmem_reserve[MAX_NR_ZONES];
#ifdef CONFIG_NUMA
int node;
unsigned long min_unmapped_pages;
unsigned long min_slab_pages;
struct per_cpu_pageset *pageset[NR_CPUS];
#else
struct per_cpu_pageset pageset[NR_CPUS];
#endif
spinlock_t lock;
#ifdef CONFIG_MEMORY_HOTPLUG
seqlock_t span_seqlock;
#endif
struct free_area free_area[MAX_ORDER];
#ifndef CONFIG_SPARSEMEM
unsigned long *pageblock_flags;
#endif /* CONFIG_SPARSEMEM */
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 pages_scanned; /* since last reclaim */
unsigned long flags;
atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
int prev_priority;
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;
unsigned long spanned_pages;
unsigned long present_pages;
const char *name;
} ____cacheline_internodealigned_in_smp;
各字段含义如下:
pages_min:管理区中保留的页数,当区中空闲的页框数达到这个值时kswapd线程会被唤醒并以同步的方式回收区中的页框。
pages_low:回收页框的下限,当区中空闲的页框数达到这个值时,伙伴分配器会唤起kswapd线程开始回收页框。
pages_high:回收页框的上限,当kswapd线程被唤醒回收页框时,它会一直回收页框直到区中空闲的页框数达到这个值为止。
lowmem_reserve:指明在靠近内存低端的区内需要保留的页框数,这个主要是为了防止这样一种情况:当内存低端的页框全部被分配后,如果这时候有新的页分配请求就可能会产生OOM,即使此刻在高端内存我们还有大量空闲的页框。
node:指明这个区所属的节点编号。
pageset:用于实现每cpu高速缓存的数组,每个cpu在该数组内占有一项。由于内核经常请求和释放单个页框,因此每cpu高速缓存包含一些预先分配的页框,它们用于满足本地cpu发出的单一内存请求。
lock:保护该描述符的自旋锁。
span_seqlock:保护zone_start_pfn、spanned_pages和present_pages的顺序锁,之所以使用顺序锁来保护这3个变量是因为我们经常需要在没有获得zone->lock的情况下读取它们的值,同时我们很少更改这些值。
free_area:标识出区中空闲页框快,这个结构是伙伴分配器的主要数据结构,伙伴分配器就是根据该结构体来对区中内存进行分配、回收等操作的。
pageblock_flags:一个内存块的标志,该内存块大小为pageblock_nr_pages。
lru_lock:活动及非活动页链表使用的自旋锁,主要在页框回收时使用。
active_list:区的活动页链表。
inactive_list:区的非活动页链表。
nr_scan_active:回收页框要扫描的活动页数目。
nr_scan_inactive:回收页框要扫描的非活动页数目。
pages_scanned:页框回收时使用的计数器。
flags:区的标志,目前有三个标志可以设置:
ZONE_ALL_UNRECLAIMABLE——区中所有页框被锁定不能回收
ZONE_RECLAIM_LOCKED——不能并发回收区中页框
ZONE_OOM_LOCKED——区处于OOM的区链表中
vm_stat:包含区的统计信息。
prev_priority:区扫描优先级,主要用于页框回收过程中。
wait_table:进程等待队列的hash表,表中的进程在等待区中的某一页内存。
wait_table_hash_nr_entries:等待队列hash表数组的大小。
wait_table_bits:等待队列hash表大小,1<<wait_table_bits。
zone_pgdat:指向区所属节点的指针。
zone_start_pfn:区中第一个页框在mem_map数组中的编号。
spanned_pages:区中的页框数,包含空洞。
present_pages:区中包含的页框数,不包含空洞。
name:指向区的名称,这个域很少使用。
三、页的数据表示
这个结构体是描述物理内存的最小单位,它描述了每个物理页框的属性,定义于文件<linux/mm_types.h>:
struct page {
unsigned long flags;
atomic_t _count;
union {
atomic_t _mapcount;
struct {
u16 inuse;
u16 objects;
};
};
union {
struct {
unsigned long private;
struct address_space *mapping;
};
#if NR_CPUS >= CONFIG_SPLIT_PTLOCK_CPUS
spinlock_t ptl;
#endif
struct kmem_cache *slab;
struct page *first_page;
};
union {
pgoff_t index; /* Our offset within mapping. */
void *freelist; /* SLUB: freelist req. slab lock */
};
struct list_head lru;
#if defined(WANT_PAGE_VIRTUAL)
void *virtual;
#endif
#ifdef CONFIG_CGROUP_MEM_RES_CTLR
unsigned long page_cgroup;
#endif
};
各字段含义如下:
flags:一组原子型标志,有些标志可以异步更新,这个域功能繁多,有兴趣的可以进一步看看源代码。
_count:页框的引用计数器。如果该字段为-1,则相应页框空闲,并可被分配给任一进程或内核本身;如果该字段大于或等于0,则说明该页框被分配给了一个或多个进程,或用于存放一些内核数据结构。
_mapcount:本页框对应的页表项数目,本页框若未被映射则为-1。
inuse、objects:这2个标志在SLUB分配器中使用。
private:可用于正在使用该页的内核成分使用,如果页是空闲的则该字段由伙伴系统使用。
mapping:当页被插入页高速缓存中时使用。
slab:指向slab的指针。
first_page:
index、freelist:作为不同的含义被几种内核成分使用。
lru:将页链接到最近最少使用双向链表中。
virtual:指向本页框的内核虚拟地址。
page_cgroup:这个域只有在内存资源控制器起作用的情况下使用,用于统计处于内存资源控制器下的页框。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)