基于msm-5.4
一、伙伴系统概述
1. 简介
伙伴系统是物理内存的三大管理机制之一,另外两个是slab缓存和per-cpu页帧缓存。##### 管理物理内存实际上就是管理 page 结构,将page添加到不同链表上进行管理。当用户申请内存的时候,从链表上拿一个page返还给用户,然后用户根据page可以找到对应的物理内存,进行读写操作。
伙伴系统中的4M的最大内存块,也被称为页块,pageblock。
在最初刚初始化的时候,伙伴系统里面什么也没有,伙伴系统会将所有的物理内存划分成 pageblock 的大小挂入到伙伴系统中。
伙伴系统是以分区为单位进行实现的。伙伴系统对一个分区上绝大多数物理内存进行管理,也即将绝大多数物理页被放到伙伴系统的 zone::free_area[] 的链表中进行管理。
zone中除了 free_area[] 外,还有一个 lowmem_reserve[], 它里面会留一些备份,并不会将当前分区中的所有物理页都放到伙伴系统中。######
伙伴系统对应的头文件是 linux/gfp.h.
2. 分配释放逻辑
当用户申请时,根据申请的大小到对应order对应的链表上去查找,若没有可用空闲内存块,则会去 order+1 对应的链表上去查找,若还没有,则依次增大order重复这个过程,直到分配到了内存。
当用户释放时,将内存块插入到其大小对应order对应的链表上,在插入到链表上时,可能做一些合并操作。合并条件是,插入后相邻两个物理内存块大小相等,且满足地址整除条件。
3. 版本迭代
新版本 free_area[] 中又有好几个链表,见 enum migratetype,分为不可移动的、可移动的、可回收的、CMA类型的、PCP类型的、ISOLATE类型的。
物理内存会用作不同的用途,比如运行一个程序的时候,需要把程序加载到内存中,此时物理页主要是用来存放代码的,我们知道代码是有地址的,在编译链接的时候这个地址是固定的,加载到内存中后就不要移动了,否则程序运行可能出问题。
二、相关数据结构
1. struct page
struct page { unsigned long private; //page_order atomic_t _mapcount; atomic_t _refcount; }
private: 表示当前内存块的大小。当从伙伴系统申请内存快的时候,返回的是内存块第一个物理页的page指针,用户根据此成员表示的order值获知申请到的内存块的大小。
_mapcount: 初始化为-1,若是没有从伙伴系统中分走,就还是-1. 若这个值发生变化了,就说明做了映射了,这个页面给用户使用了。
_refcount: 物理页的引用计数。
2. 数据结构间的关系
三、接口函数
1. 释放物理页到伙伴系统
//include/linux/gfp.h #define __free_page(page) __free_pages((page), 0) #define free_page(addr) free_pages((addr), 0) void __free_pages(struct page *page, unsigned int order); void free_pages(unsigned long addr, unsigned int order);
2. 从伙伴系统中分配物理页
//include/linux/gfp.h #define alloc_page(gfp_mask) alloc_pages(gfp_mask, 0); #define alloc_pages(gfp_mask, order) alloc_pages_node(numa_node_id(), gfp_mask, order)
返回值是struct page 指针,可以根据 page 找到对应的物理内存,通过转换可以找到对应的页帧号,甚至对应的虚拟地址。
gfp_mask 可以决定去哪个zone中去分配,如 GFP_DMA,优先去DMA分区去查找。每个zone分区上都有一个伙伴系统,这里的mask主要是决定优先从哪个zone分配而已,若指定的分区分配不到,还会去其它分区去分配。
四、实现逻辑
1. alloc_pages实现
分配内存有快速路径和慢速路径。
/* * alloc_pages(gfp_mask, order) --> alloc_pages_node(0, gfp_mask, order) --> __alloc_pages_node(0, gfp_mask, order) * -->__alloc_pages(gfp_mask, order, 0) --> __alloc_pages_nodemask(gfp_mask, order, 0, NULL) 恒: (gfp_mask, order, 0, NULL) */ struct page * __alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order, int preferred_nid, nodemask_t *nodemask) //page_alloc.c { /* fastpath: 从伙伴系统空闲链表中获取page。若是申请的是单个页,优先从cpu本地缓存去拿 */ page = get_page_from_freelist(alloc_mask, order, alloc_flags, &ac); /* slowpath: 对内存进行整理、迁移、压缩、交换,腾出大块连续物理内存 */ page = __alloc_pages_slowpath(alloc_mask, order, &ac); }
五、相关调试接口
1. /proc/buddyinfo
# cat /proc/buddyinfo Node 0, zone Normal 6424 2088 5924 2829 1361 595 152 52 15 4 212
需要使能 CONFIG_PROC_FS 才会打印,实现函数:
/* frag_show() 中遍历 pgdat->node_zones[],对于每一个zone都调用这个函数 */ static void frag_show_print(struct seq_file *m, pg_data_t *pgdat, struct zone *zone) //mm/vmstat.c { int order; seq_printf(m, "Node %d, zone %8s ", pgdat->node_id, zone->name); for (order = 0; order < MAX_ORDER; ++order) seq_printf(m, "%6lu ", zone->free_area[order].nr_free); }
嵌入式设备上只有一个node,所以是node 0,这里只有一个 Normal zone,后面11个数字,依次表示 order=0-10的链表上的内存块的个数。
2. /proc/pagetypeinfo
要想做更细致的查看每个order对应链表的迁移类型,可以使用这个接口。
# cat /proc/pagetypeinfo Page block order: 10 Pages per block: 1024 Free pages count per migrate type at order 0 1 2 3 4 5 6 7 8 9 10 //order 0-10 Node 0, zone Normal, type Unmovable 0 1 165 3 7 2 0 1 1 2 0 Node 0, zone Normal, type Movable 6297 2088 5741 2825 1354 592 152 51 15 2 212 Node 0, zone Normal, type Reclaimable 0 0 1 1 0 1 0 0 0 0 0 Node 0, zone Normal, type CMA 0 0 0 0 0 0 0 0 0 0 0 Node 0, zone Normal, type HighAtomic 0 0 0 0 0 0 0 0 0 0 0 Node 0, zone Normal, type Isolate 0 0 0 0 0 0 0 0 0 0 0 Number of blocks type Unmovable Movable Reclaimable CMA HighAtomic Isolate Node 0, zone Normal 1280 2058 100 36 0 0 Number of mixed blocks Unmovable Movable Reclaimable CMA HighAtomic Isolate Node 0, zone Normal 0 0 1 0 0 0
注:buddyinfo 和 pagetypeinfo 这两个文件是同时cat的。
打印内存分四部分,依次为:
static int pagetypeinfo_show(struct seq_file *m, void *arg) //mm/vmstat.c { pg_data_t *pgdat = (pg_data_t *)arg; /* 第一部分 */ seq_printf(m, "Page block order: %d\n", pageblock_order); //(MAX_ORDER-1)=10 seq_printf(m, "Pages per block: %lu\n", pageblock_nr_pages); //(1UL << pageblock_order)=1<<10 /* 第二部分:打印所有zone每个order每种迁移类型空闲链表上###对应的内存块个数 */ pagetypeinfo_showfree(m, pgdat); /* 第三部分:打印每种迁移类型对应pageblock的个数(应该包含已分配出去的) ##### */ pagetypeinfo_showblockcount(m, pgdat); /* 第四部分: 打印包含其它类型页面的pageblock数量 */ pagetypeinfo_showmixedcount(m, pgdat); } /* 第三部分数值 */ static void pagetypeinfo_showblockcount_print(struct seq_file *m, pg_data_t *pgdat, struct zone *zone) //vmstat.c { unsigned long start_pfn = zone->zone_start_pfn, end_pfn = zone_end_pfn(zone); unsigned long count[MIGRATE_TYPES] = { 0, }; /* 依次遍历所有的pageblock */ for (pfn = start_pfn; pfn < end_pfn; pfn += pageblock_nr_pages) { //1k page = pfn_to_online_page(pfn); mtype = get_pageblock_migratetype(page); count[mtype]++; } /* Print counts */ seq_printf(m, "Node %d, zone %8s ", pgdat->node_id, zone->name); for (mtype = 0; mtype < MIGRATE_TYPES; mtype++) seq_printf(m, "%12lu ", count[mtype]); } /* 第四部分数值 */ void pagetypeinfo_showmixedcount_print(struct seq_file *m, pg_data_t *pgdat, struct zone *zone) { for (; pfn < end_pfn; ) { page = pfn_to_online_page(pfn); pageblock_mt = get_pageblock_migratetype(page); for (; pfn < block_end_pfn; pfn++) { page_owner = get_page_owner(page_ext); page_mt = gfpflags_to_migratetype(page_owner->gfp_mask); if (pageblock_mt != page_mt) { if (is_migrate_cma(pageblock_mt)) count[MIGRATE_MOVABLE]++; else count[pageblock_mt]++; } } }
注:由于 struct free_area 中只有一个 nr_free 记录此order大小的空闲内存块的个数,并没有记录此大小内存块每种迁移类型对应的内存块的个数,在cat时需要遍历链表实现,最大显示个数限制为10万个,超过后显示">100000",因为遍历耗时可能导致spin lock临界区过长。
TODO: 看第三第四部分具体代码
六、实验
1. 分配物理页地址转换实验
/* ---- mt1 ---- */ #include <linux/mm.h> #include <linux/gfp.h> #include <asm/pgtable.h> struct mm_test_1 { unsigned long virt_addr; struct page *page; }; struct mm_test_1 mt1; static void mem_test_1_init(void) { mt1.page = alloc_pages(GFP_KERNEL, g_debug_arg); if (!mt1.page) { pr_info("no memory!\n"); } pr_info("pfn=0x%lx\n", page_to_pfn(mt1.page)); pr_info("paddr=0x%llx\n", page_to_phys(mt1.page)); pr_info("vaddr=0x%p\n", page_address(mt1.page)); mt1.virt_addr = (unsigned long )page_to_virt(mt1.page); pr_info("vaddr=0x%lx\n", mt1.virt_addr); pr_info("vmemmap=0x%p, mt1.page=0x%p\n", vmemmap, mt1.page); pr_info("mt1.page-vmemmap=%lu\n", (unsigned long)(mt1.page - vmemmap)); pr_info("mt1.page->private=%d\n", mt1.page->private); } static void mem_test_1_exit(void) { /* 分别以page指针和虚拟地址为参数 */ free_pages(mt1.virt_addr, g_debug_arg); //__free_pages(mt1.page, g_debug_arg); }
地址转换的三个宏 page_to_pfn() page_to_phys() page_to_virt() 打印出来的值,手动校验,都对应的上。多次编译,vmemmap的值是固定的。
标签:11,buddy,zone,pages,order,内存,初探,pfn,page From: https://www.cnblogs.com/hellokitty2/p/18279826