内存管理
Posted 源辰布道师 【刘经欢】
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内存管理相关的知识,希望对你有一定的参考价值。
13. 内存管理
#define NODES_SHIFT CONFIG_NODES_SHIFT
#define MAX_NUMNODES (1 << NODES_SHIFT)
这里主要介绍UMA机制。contig_page_data被定义如下:
struct pglist_data __refdata contig_page_data = { .bdata = &bootmem_node_data[0] };
EXPORT_SYMBOL(contig_page_data);
struct pglist_data即是pg_data_t的原型。了解pg_data_t中的结构成员对于了解内存管理是必经之路:
struct zone node_zones[MAX_NR_ZONES];
struct zonelist node_zonelists[MAX_ZONELISTS];
#ifdef CONFIG_FLAT_NODE_MEM_MAP /* means !SPARSEMEM */
#ifdef CONFIG_CGROUP_MEM_RES_CTLR
struct page_cgroup *node_page_cgroup;
...... /* for CONFIG_MEMORY_HOTPLUG */
unsigned long node_present_pages; /* total number of physical pages */
unsigned long node_spanned_pages; /* total size of physical page
wait_queue_head_t kswapd_wait;
- node_zones:当前节点中包含的最大管理区数。MAX_NR_ZONES在include/linux/bounds.h定义,该文件是在编译过程中根据管理区类型定义中的__MAX_NR_ZONES变量自动生成的。
- node_zonelists: 内存分配器所使用的管理区链表数组,MAX_ZONELISTS的值在配置CONFIG_NUMA时为2,否则为1。索引为0的链表表示后援 (Fallback)链表,也即当该链表中的第一个不满足分配内存时,依次尝试链表的其他管理区。索引为1,的链表则用来针对GFP_THISNODE的 内存申请,此时只能申请指定的该链表中的管理区。
- nr_zones:指定当前节点中的管理区数,也即node_zones中实际用到的管理区数。它的取值范围为[1, MAX_NR_ZONES]。对于UMA来说,它的值为1。
- node_mem_map:节点中页描述符数组首地址。
- node_page_cgroup:
- bdata:系统引导时用的Bootmem分配器。
- node_start_pfn:节点中第一个页框的下标。
- node_present_pages:节点中的页面数,不包含孔洞。
- node_spanned_pages:节点中的页面总数,包含孔洞。
- node_id:节点标识符,在节点数组中唯一存在。
- kswapd_wait:kswapd页换出守护进程使用的等待队列。
- kswapd: 指针指向kswaps内核线程的进程描述符。
- kswapd_max_order:kswapd将要创建的空闲块大小取对数的值。
/* Maximum number of zones on a zonelist */
#define MAX_ZONES_PER_ZONELIST (MAX_NUMNODES * MAX_NR_ZONES)
struct zonelist_cache *zlcache_ptr; // NULL or &zlcache
struct zoneref _zonerefs[MAX_ZONES_PER_ZONELIST + 1];
struct zonelist_cache zlcache; // optional ...
节点中的管理区都在free_area_init_core函数中初始化。调用关系如下所示:
最后一种限制不仅存在于80x86,而存在于所有的体系结构中。为了应对这两种限制,Linux把每个内存节点的物理内存划分为多个(通常为3个)管理区(zone)。在80x86 UMA体系结构中的管理区为:
/* Fields commonly accessed by the page allocator */
unsigned long pages_min, pages_low, pages_high;
unsigned long lowmem_reserve[MAX_NR_ZONES];
struct per_cpu_pageset pageset[NR_CPUS];
struct free_area free_area[MAX_ORDER];
/* Fields commonly accessed by the page reclaim scanner */
unsigned long recent_rotated[2];
unsigned long recent_scanned[2];
unsigned long pages_scanned; /* since last reclaim */
unsigned long flags; /* zone flags, see below */
atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
- pages_min,记录管理区中空闲页的数目。
- pages_low,回收页框使用的下届,同时也被管理区分配器作为阈值使用。
- pages_high,回收页框使用的上届,同时也被管理区分配器作为阈值使用。
- lowmem_reserve,指明在处理内存不足的临界情况下每个管理区必须保留的页框数目。
- pageset,单一页框的特殊告诉缓存。
内核为了尽可能保证一个原子内存分配请求成功,它为原子内存分配请求保留了一个页框池,只有在内存不足时才使用。保留内存的数量存放在min_free_kbytes变量中,单位为KB。
/* min_free_kbytes = sqrt(lowmem_kbytes * 16); */
lowmem_kbytes = nr_free_buffer_pages() * (PAGE_SIZE >> 10);
min_free_kbytes = int_sqrt(lowmem_kbytes * 16);
名称 | 大小 |
pages_min | min_free_kbytes >> (PAGE_SHIFT - 10) |
pages_low | pages_min * 5 / 4 |
pages_high | pages_min * 3 / 2 |
free_area_init_core中对管理区初始化的代码部分如下,后续章节将对该函数进一步分析。
zone->present_pages = realsize;
spin_lock_init(&zone->lru_lock);
zone->prev_priority = DEF_PRIORITY;
INIT_LIST_HEAD(&zone->lru[l].list);
unsigned long flags; /* Atomic flags, some possibly
atomic_t _count; /* Usage count, see below. */
atomic_t _mapcount; /* Count of ptes mapped in mms,
* & limit reverse map searches.
每一个物理页框都需要一个对应的page结构来进行管理:记录分配状态,分配和回收,互斥以及同步操作。对该结构成员的解释如下:
- flag域存放当前页框的页标志,它存储了体系结构无关的状态,专门供Linux内核自身使用。该标志可能的值定义在include/linux/page-flags.h中。
- 原子计数成员_count则指明了当前页框的引用计数,当该值为0时,就说明它没有被使用,此时在新分配内存时它就可以被使用。内核代码应该通过page_count来访问它,而非直接访问。
- 原子计数成员_mapcount表示在页表中有多少页指向该页框。在SLUB中它被inuse和objects代替。
PG_locked, /* Page is locked. Don\'t touch. */
以上是页标志位的可能取值,通常不应该直接使用这些标志位,而应该内核预定义好的宏,它们在相同的头文件中被定义,但是它们是被间接定义的,也即通过##连字符来统一对它们进行定义。
#define TESTPAGEFLAG(uname, lname) \\
static inline int Page##uname(struct page *page) \\
{ return test_bit(PG_##lname, &page->flags); }
PAGEFLAG(Referenced, referenced) TESTCLEARFLAG(Referenced, referenced)
宏 | 扩展函数/宏 | 用途 |
TESTPAGEFLAG(uname, lname) | Page##uname | 测试PG_##lname位 |
SETPAGEFLAG(uname, lname) | SetPage##uname | 设置PG_##lname位 |
CLEARPAGEFLAG(uname, lname)[a] | ClearPage##uname | 清除PG_##lname位 |
TESTSETFLAG(uname, lname) | TestSetPage##uname | 测试并设置PG_##lname |
TESTCLEARFLAG(uname, lname) | TestClearPage##uname | 测试并清除PG_##lname |
PAGEFLAG(uname, lname)[b] | TESTPAGEFLAG | 当于同时扩展了三个宏,也即三个函数 |
PAGEFLAG_FALSE(uname) | Page##uname | 永远返回0 |
TESTSCFLAG(uname, lname) | TESTSETFLAG | 当于同时扩展了两个宏,也即两个函数 |
SETPAGEFLAG_NOOP(uname) | SetPage##uname | 空操作 |
CLEARPAGEFLAG_NOOP(uname) | ClearPage##unam | 空操作 |
__CLEARPAGEFLAG_NOOP(uname) | __ClearPage##uname | 空操作 |
TESTCLEARFLAG_FALSE(uname) | TestClearPage##uname | 永远返回0 |
[a] 以上三个宏分别对应test_bit,set_bit和clear_bit,是原子操作,与它们对应的是有三个开头 [b] 与此对应也有__PAGEFLAG的宏存在。 | 以上是关于内存管理的主要内容,如果未能解决你的问题,请参考以下文章 |