内存管理-41-highatomic预留内存
基于msm-5.4
一、相关结构体
1. struct zone
struct zone { unsigned long nr_reserved_highatomic; ... };
成员介绍:
nr_reserved_highatomic: 记录为高优先级原子分配预留的内存页面数量。
二、赋值逻辑
赋值路径只有两个,一个是对高阶原子分配进行页面预留,另一个是减少高阶原子页面的预留。
1. 增加预留页面
调用路径:
__alloc_pages_cpuset_fallback //page_alloc.c __alloc_pages_may_oom //page_alloc.c __alloc_pages_direct_compact //page_alloc.c __alloc_pages_direct_reclaim //page_alloc.c __alloc_pages_slowpath //page_alloc.c __alloc_pages_nodemask //page_alloc.c get_page_from_freelist //page_alloc.c reserve_highatomic_pageblock //page_alloc.c zone->nr_reserved_highatomic += pageblock_nr_pages;
执行逻辑:
1.1 reserve_highatomic_pageblock()
static void reserve_highatomic_pageblock(struct page *page, struct zone *zone, unsigned int alloc_order) //page_block.c { int mt; unsigned long max_managed, flags; /* 将保留的数量限制为 1 个页面块或大约 1% 的区域。检查容易发生竞争,锁外检查无害 */ max_managed = (zone_managed_pages(zone) / 100) + pageblock_nr_pages; if (zone->nr_reserved_highatomic >= max_managed) return; spin_lock_irqsave(&zone->lock, flags); /* 持有zone->lock后再次检查,上面在锁外进行一次检查可以预防卡锁 */ if (zone->nr_reserved_highatomic >= max_managed) goto out_unlock; /* * 获取参数page所在页面块的迁移特性,如果不是 HIGHATOMIC、ISOLATE、CMA * 三种迁移类型,就将page所在页面块设置为高阶原子类型。 */ mt = get_pageblock_migratetype(page); if (!is_migrate_highatomic(mt) && !is_migrate_isolate(mt) && !is_migrate_cma(mt)) { /* 设置页面块的迁移类型为HIGHATOMIC */ zone->nr_reserved_highatomic += pageblock_nr_pages; set_pageblock_migratetype(page, MIGRATE_HIGHATOMIC); /* 将page所在的页面块迁移到 HIGHATOMIC 这个迁移类型对应的freelist链表上 */ move_freepages_block(zone, page, MIGRATE_HIGHATOMIC, NULL); } out_unlock: spin_unlock_irqrestore(&zone->lock, flags); }
1.2 move_freepages_block()
/* reserve_highatomic_pageblock: (zone, page, MIGRATE_HIGHATOMIC, NULL) */ int move_freepages_block(struct zone *zone, struct page *page, int migratetype, int *num_movable) //page_alloc.c { unsigned long start_pfn, end_pfn; struct page *start_page, *end_page; if (num_movable) *num_movable = 0; /* page所在页面块的start/end pfn和页面块首page结构指针 */ start_pfn = page_to_pfn(page); start_pfn = start_pfn & ~(pageblock_nr_pages-1); start_page = pfn_to_page(start_pfn); end_page = start_page + pageblock_nr_pages - 1; end_pfn = start_pfn + pageblock_nr_pages - 1; /* Do not cross zone boundaries */ /* 若start_pfn不在zone管理的页面之间,则将start_page指向page(而不是页面块的首个page了) */ if (!zone_spans_pfn(zone, start_pfn)) start_page = page; /* 若end_pfn不在zone管理的页面之间, 则直接返回 */ if (!zone_spans_pfn(zone, end_pfn)) return 0; /* 将页面块迁移到迁移类型对应的freelist链表上 */ return move_freepages(zone, start_page, end_page, migratetype, num_movable); }
1.3 move_freepages()
/* * 翻译: 将range内的空闲页面移动到请求类型的空闲列表中。请注意,start_page 和 end_pages 未在页块边界上对齐。 * 如果需要对齐,请使用 move_freepages_block() */ /* reserve_highatomic_pageblock: (zone, start_page, end_page, MIGRATE_HIGHATOMIC, NULL) */ static int move_freepages(struct zone *zone, struct page *start_page, struct page *end_page, int migratetype, int *num_movable) //page_alloc.c { struct page *page; unsigned int order; int pages_moved = 0; for (page = start_page; page <= end_page;) { /* 检查区间中的每一个页面是否有对应实际的物理内存,是否是valid的 */ if (!pfn_valid_within(page_to_pfn(page))) { page++; continue; } /* 伙伴系统中的空闲页面这个Buddy标志是设置的 */ if (!PageBuddy(page)) { /* * 翻译: 我们假设可以隔离并迁移的页面是可移动的。但我们实际上并没有尝试隔离,因为这样做成本太高。 * 通过参数 *num_movable 返回的是,不是buddy中的页面,但是moveable状态的页面。 */ if (num_movable && (PageLRU(page) || __PageMovable(page))) (*num_movable)++; page++; /* 若page不是伙伴系统中内存块的首个页面,这里就continue进行下一轮循环了 */ continue; } /* 下面就是page是伙伴系统中空闲页面块的首个页面的逻辑了 */ /* 确保我们不会无意中更改节点,断言page和zone必须属于同一个node,嵌入式单node恒成立 */ VM_BUG_ON_PAGE(page_to_nid(page) != zone_to_nid(zone), page); VM_BUG_ON_PAGE(page_zone(page) != zone, page); //断言page必须属于这个zone order = page_order(page); //page->private 应该只有buddy中的首个页面的private才表示order /* 直接调用的是list_move()从原来链表上del下来再add到新链表上去 */ move_to_free_area(page, &zone->free_area[order], migratetype); page += 1 << order; pages_moved += 1 << order; } /* 返回的是成功迁移的页面数量 */ return pages_moved; }
2. 减少预留
调用路径:
__alloc_pages_slowpath //page_alloc.c __alloc_pages_direct_reclaim //page_alloc.c 传参: (ac, false) __alloc_pages_slowpath //page_alloc.c should_reclaim_retry //page_alloc.c 传参: (ac, true) unreserve_highatomic_pageblock zone->nr_reserved_highatomic -= min(pageblock_nr_pages, zone->nr_reserved_highatomic);
执行逻辑:
2.1 unreserve_highatomic_pageblock()
/* * 翻译: 当内存压力导致分配即将失败时使用。这可能会损害高阶分配在内存压力巨大时的可靠性, * 但失败的原子分配应该比 OOM 更容易恢复。 * 如果 @force 为真,则尝试取消保留页面块,即使高原子页面块已耗尽。 * * should_reclaim_retry:(ac, true) 返回迁移到ac->migratetype类型的空闲链表的页面数 */ static bool unreserve_highatomic_pageblock(const struct alloc_context *ac, bool force) { struct zonelist *zonelist = ac->zonelist; unsigned long flags; struct zoneref *z; struct zone *zone; struct page *page; int order; bool ret; /* 遍历系统中所有的zone */ for_each_zone_zonelist_nodemask(zone, z, zonelist, ac->high_zoneidx, ac->nodemask) { /* 除非内存压力真的很高,否则至少保留一个页块,一个页面块也不给highatomic保留了。 */ if (!force && zone->nr_reserved_highatomic <= pageblock_nr_pages) continue; spin_lock_irqsave(&zone->lock, flags); //关键部分主要是这个锁保护 /* 遍历每一个order的HIGHATOMIC链表 */ for (order = 0; order < MAX_ORDER; order++) { struct free_area *area = &(zone->free_area[order]); page = get_page_from_free_area(area, MIGRATE_HIGHATOMIC); if (!page) continue; /* * 翻译: 在页面释放路径中,migratetype 更改很活跃,因此尽管我们将页面块的迁移类型 * 从 highatomic 更改为 ac->migratetype,但我们可以在此循环中计算页面块中的多个空 * 闲页面。因此我们应该调整一次计数。 * * 这里再次确认查询找到的page是 HIGHATOMIC 迁移类型的。 */ if (is_migrate_highatomic_page(page)) { zone->nr_reserved_highatomic -= min(pageblock_nr_pages, zone->nr_reserved_highatomic); } /* * 将分配出来的页面设置为ac指定的迁移类型,并移动到对应的freelist链表上,若成功移动 * 了页面(ret!=0), 则返回成功移动的页面数,然后退出遍历。 */ set_pageblock_migratetype(page, ac->migratetype); ret = move_freepages_block(zone, page, ac->migratetype, NULL); if (ret) { spin_unlock_irqrestore(&zone->lock, flags); return ret; } } spin_unlock_irqrestore(&zone->lock, flags); } return false; }
三、总结
1. 为 highatomic 类型分配的预留页面数量是有限制的,最大不能超过 zone->managed_pages/100+4k 个页面,并且在内存紧张时会从 MIGRATE_HIGHATOMIC 类型的freelist链表中释放出来,但会尽量给保留一个pageblock大小的内存页面给highatomic使用。若内存非常紧张时,则一个页面也不会给highatomic保留了,全部会释放出来。
2. 默认没有对 zone->nr_reserved_highatomic 进行任何调试打印。
3. 代码中涉及到的:
PageBuddy(page) 是Linux内核中用于检查一个页面是否处于空闲状态并在伙伴系统中的函数。
PageLRU(page) 宏在Linux内核中用于检查一个页面是否在LRU(Least Recently Used)链表上。
posted on 2024-11-09 16:21 Hello-World3 阅读(10) 评论(0) 编辑 收藏 举报