Linux メモリ管理: メモリ割り当てとメモリ リサイクルの原則


記事 コンテンツ
Linux のメモリ管理: Bootmem が主導権を握る Bootmemプロセスメモリアロケータを開始します
Linux メモリ管理: Buddy システムは長い間待ち望まれていました Buddy Systemパートナー システム メモリ アロケータ
Linux メモリ管理: Slab がデビュー Slabメモリアロケータ
Linux メモリ管理: メモリ割り当てとメモリ リサイクルの原則 メモリ割り当てとメモリ回復の原則

ソースコード解析コラムの4回目の記事です。

主に、メモリ管理、デバイス管理、システム起動、その他の部分の 4 つの主要な分析モジュールに分かれています。

メモリ管理はBootmemBuddy Systemと のSlab3 つの部分に分かれています。もちろん、メモリの初期化に加えて、メモリの割り当てとメモリのリサイクルも行う必要があります。

一部はtodo後で追加されます


記憶を取り戻す

基本的な考え方

システム メモリの負荷が高い場合、linuxシステム内の負荷の高い各zoneメモリがリサイクルされます。

メモリのリサイクルは主に匿名ページとファイル ページで実行されます。

  • 匿名ページの場合、使用頻度の低い匿名ページの一部がメモリのリサイクル プロセス中に除外され、swapパーティションに書き込まれ、空きページ フレームとしてパートナー システムに解放されます。
  • ファイル ページの場合、このファイル ページに保存されているコンテンツがクリーン ページの場合、書き込み可能である必要はなく、フリー ページはパートナー システムに直接解放されますが、逆にダーティ ページは書き込み可能になります。最初にディスクに書き戻されてから、パートナー システムにリリースされます。

ただし、このとき大きな負荷がかかるという欠点があるため、I/Oシステムではzone通常、1ページごとにラインを設定し、空きページフレーム数がこのラインに満たない場合には、メモリが不足します。リサイクル操作が実行されます。それ以外の場合、メモリのリサイクルは実行されません。

メモリのリサイクルはユニットzoneに基づいており、通常はzone次の 3 つの行があります。

  • watermark[WMARK_MIN]: このしきい値は、高速割り当てが失敗した後の低速割り当てでの割り当てに使用されます。この値が低速割り当てでも割り当てられない場合は、ダイレクト メモリ リサイクルと高速メモリ リサイクルが実行されます。
  • watermark[WMARK_LOW]: 低しきい値、高速割り当てのデフォルトのしきい値です。割り当てプロセス中に、zone空きページの数がこのしきい値よりも低い場合、システムはzone高速メモリ リサイクルを実行します。
  • watermark[WMARK_HIGH]: 高いしきい値は、zone空きページ数として十分な値です。一般に、zoneメモリ再利用を実行するときの目標は、zone空きページ数をこの値まで増やすことです。
liuzixuan@liuzixuan-ubuntu ~ # cat /proc/zoneinfo
Node 0, zone   Normal
  pages free     5179
        min      4189
        low      5236
        high     6283

メモリ リサイクルの主な対象となるのは、 リンク リスト内のページ、およびリンク リスト内のページの 3 つです。主にプロセス空間で使用されるメモリ ページの管理に使用されます。主に 3 種類のページを管理します。匿名ページ、匿名ページzoneファイル ページとページ。slablrubuffer_headlrushmem

メモリページがリサイクル可能かどうかを判断する前提は、page->_count = 0

セルフアロケータ

メモリ割り当てalloc_pagealloc_page一般的な呼び出し__alloc_pages_nodemask->__alloc_pages_internal

static inline struct page *
__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order,
		struct zonelist *zonelist, nodemask_t *nodemask)
{
    
    
	return __alloc_pages_internal(gfp_mask, order, zonelist, nodemask);
}

__alloc_pages_internallow一般に、しきい値を使用した高速メモリ割り当てget_page_from_freelistと、min使用量しきい値を使用した低速メモリ割り当てが実行されます。

struct page *
__alloc_pages_internal(gfp_t gfp_mask, unsigned int order,
			struct zonelist *zonelist, nodemask_t *nodemask)
{
    
    
    // ...
	page = get_page_from_freelist(gfp_mask|__GFP_HARDWALL, nodemask, order,
			zonelist, high_zoneidx, ALLOC_WMARK_LOW|ALLOC_CPUSET);
	if (page)
		goto got_pg;

高速なメモリ割り当て

高速メモリ割り当て機能は、しきい値get_page_from_freelistを介して適切な割り当てを取得します。しきい値に達しない場合は、高速メモリ リサイクルが実行されます。高速メモリ リサイクル後、割り当てが再度試行されます。lowzonelistzonezonelow

  • gfp_mask: メモリの申請に使用されますgfp mask
  • order: 物理メモリレベルを適用します
  • zonelist:zoneノードのzonelist配列
  • alloc_flags: 変換後にメモリを申請しますflags
  • high_zoneidx: 適用できる最大メモリ量zone

alloc flagsこれは、内部でメモリを適用するためにバディによって使用されflag、いくつかのメモリ割り当て動作を決定します。

/* The ALLOC_WMARK bits are used as an index to zone->watermark */
#define ALLOC_WMARK_MIN		WMARK_MIN
#define ALLOC_WMARK_LOW		WMARK_LOW
#define ALLOC_WMARK_HIGH	WMARK_HIGH
#define ALLOC_NO_WATERMARKS	0x04 /* don't check watermarks at all */
 
/* Mask to get the watermark bits */
#define ALLOC_WMARK_MASK	(ALLOC_NO_WATERMARKS-1)
 
/*
 * Only MMU archs have async oom victim reclaim - aka oom_reaper so we
 * cannot assume a reduced access to memory reserves is sufficient for
 * !MMU
 */
#ifdef CONFIG_MMU
#define ALLOC_OOM		0x08
#else
#define ALLOC_OOM		ALLOC_NO_WATERMARKS
#endif
 
#define ALLOC_HARDER		 0x10 /* try to alloc harder */
#define ALLOC_HIGH		 0x20 /* __GFP_HIGH set */
#define ALLOC_CPUSET		 0x40 /* check for correct cpuset */
#define ALLOC_CMA		 0x80 /* allow allocations from CMA areas */
#ifdef CONFIG_ZONE_DMA32
#define ALLOC_NOFRAGMENT	0x100 /* avoid mixing pageblock types */
#else
#define ALLOC_NOFRAGMENT	  0x0
#endif
#define ALLOC_KSWAPD		0x800 /* allow waking of kswapd, __GFP_KSWAPD_RECLAIM set */

ロゴの意味は以下の通りです

  • ALLO_WMARK_XXX:メモリを申請するときにwatermark関連します
  • ALLOC_NO_WATERMARKS:メモリ申請時にチェックしないwater mark
  • ALLOC_OOM:メモリ不足時のトリガを許可する:OOMALLOC_HARDERページ移行時にMIGRATE_HIGHATOMIC予約メモリの使用を許可するかどうか
  • ALLOC_HIGH:と__GFP_HIGH同じ機能
  • ALLOC_CPUSET:CPUSETメモリアプリケーションを制御する機能を使用するかどうか
  • ALLOC_CMA: メモリCMA
  • ALLOC_NOFRAGMENT: 設定されている場合、no_fallbackメモリが不足している場合に使用されるポリシーが決定され、リモート ノードからのメモリの適用、つまり外部メモリ フラグメントの生成が許可されません。
  • ALLOC_KSWAPD:メモリ不足時に有効可能kswapd

get_page_from_freelistこのアルゴリズムをメモリに適用するのは初めての試みであり、中心となるアイデアはbuddy、メモリが十分なzoneときに対応するorder物理ページfreelistから物理ページを取得することです。

get_page_from_freelist

/*
 * get_page_from_freelist goes through the zonelist trying to allocate
 * a page.
 */
static struct page *
get_page_from_freelist(gfp_t gfp_mask, nodemask_t *nodemask, unsigned int order,
		struct zonelist *zonelist, int high_zoneidx, int alloc_flags)
{
    
    
	struct zoneref *z;
	struct page *page = NULL;
	int classzone_idx;
	struct zone *zone, *preferred_zone;
	nodemask_t *allowednodes = NULL;/* zonelist_cache approximation */
	int zlc_active = 0;		/* set if using zonelist_cache */
	int did_zlc_setup = 0;		/* just call zlc_setup() one time */

取得zone_id

	classzone_idx = zone_idx(preferred_zone);

ラベルを見てみましょうzonelist_scan。ラベルが渡された場合はzonelist、十分な空きページがあるラベルを探します。zone

zonelist_scan:
	/*
	 * Scan zonelist, looking for a zone with enough free.
	 * See also cpuset_zone_allowed() comment in kernel/cpuset.c.
	 */
	for_each_zone_zonelist_nodemask(zone, z, zonelist,
						high_zoneidx, nodemask) {
    
    
		if (NUMA_BUILD && zlc_active &&
			!zlc_zone_worth_trying(zonelist, z, allowednodes))
				continue;
		if ((alloc_flags & ALLOC_CPUSET) &&
			!cpuset_zone_allowed_softwall(zone, gfp_mask))
				goto try_next_zone;

		if (!(alloc_flags & ALLOC_NO_WATERMARKS)) {
    
    
			unsigned long mark;
			int ret;
			if (alloc_flags & ALLOC_WMARK_MIN)
				mark = zone->pages_min;
			else if (alloc_flags & ALLOC_WMARK_LOW)
				mark = zone->pages_low;
			else
				mark = zone->pages_high;

			if (zone_watermark_ok(zone, order, mark,
				    classzone_idx, alloc_flags))
				goto try_this_zone;

			if (zone_reclaim_mode == 0)
				goto this_zone_full;

			ret = zone_reclaim(zone, gfp_mask, order);
			switch (ret) {
    
    
			case ZONE_RECLAIM_NOSCAN:
				/* did not scan */
				goto try_next_zone;
			case ZONE_RECLAIM_FULL:
				/* scanned but unreclaimable */
				goto this_zone_full;
			default:
				/* did we reclaim enough */
				if (!zone_watermark_ok(zone, order, mark,
						classzone_idx, alloc_flags))
					goto this_zone_full;
			}
		}

へのマクロzonelist_scan展開について

for (z = first_zones_zonelist(zonelist, high_zoneidx, nodemask, &zone)
{
    
    
	zone;
    z = next_zones_zonelist(++z, high_zoneidx, nodemask, &zone)// 获取zonelist中的下一个zone
}
static inline struct zoneref *first_zones_zonelist(struct zonelist *zonelist,
					enum zone_type highest_zoneidx,
					nodemask_t *nodes,
					struct zone **zone)
{
    
    
	return next_zones_zonelist(zonelist->_zonerefs, highest_zoneidx, nodes,
								zone);
}

struct zoneref *next_zones_zonelist(struct zoneref *z,
					enum zone_type highest_zoneidx,
					nodemask_t *nodes,
					struct zone **zone)
{
    
    
	/*
	 * Find the next suitable zone to use for the allocation.
	 * Only filter based on nodemask if it's set
	 */
	if (likely(nodes == NULL))
		while (zonelist_zone_idx(z) > highest_zoneidx)
			z++;
	else
		while (zonelist_zone_idx(z) > highest_zoneidx ||
				(z->zone && !zref_in_nodemask(z, nodes)))
			z++;

	*zone = zonelist_zone(z); // 获得zonelist中的zone
	return z;
}
		if (NUMA_BUILD && zlc_active &&
            // z->zone所在的节点不允许分配或者该zone已经饱满了
			!zlc_zone_worth_trying(zonelist, z, allowednodes)) 
				continue;
		if ((alloc_flags & ALLOC_CPUSET) &&
            // 开启了检查内存节点是否在指定CPU集合,并且该zone不被允许在该CPU上分配内存
			!cpuset_zone_allowed_softwall(zone, gfp_mask))
				goto try_next_zone;

zlc_zone_worth_tryingノードが割り当てを許可しているかどうか、またはノードzoneがいっぱいかどうかを確認します

static int zlc_zone_worth_trying(struct zonelist *zonelist, struct zoneref *z,
						nodemask_t *allowednodes)
{
    
    
	struct zonelist_cache *zlc;	/* cached zonelist speedup info */
	int i;				/* index of *z in zonelist zones */
	int n;				/* node that zone *z is on */

	zlc = zonelist->zlcache_ptr; // 得到zonelist_cache指针信息
	if (!zlc)
		return 1;

	i = z - zonelist->_zonerefs; // 获得_zonerefs数组位置
	n = zlc->z_to_n[i];

	/* This zone is worth trying if it is allowed but not full */
	return node_isset(n, *allowednodes) && !test_bit(i, zlc->fullzones);
}
struct zonelist_cache {
    
    
	unsigned short z_to_n[MAX_ZONES_PER_ZONELIST];		/* zone->nid */
	DECLARE_BITMAP(fullzones, MAX_ZONES_PER_ZONELIST);	/* zone full? */
	unsigned long last_full_zap;		/* when last zap'd (jiffies) */
};

主なものは、次の 2 つの関数をテストすることですnode_issettest_bit

#define node_isset(node, nodemask) test_bit((node), (nodemask).bits)

static inline int test_bit(int nr, const volatile unsigned long *addr)
{
    
    
	return 1UL & (addr[BIT_WORD(nr)] >> (nr & (BITS_PER_LONG-1)));
}

cpuset_zone_allowed_softwall関数についても同様であり、概要は示しません。

高速メモリ割り当ての水位を取得する

		if (!(alloc_flags & ALLOC_NO_WATERMARKS)) {
    
    
			unsigned long mark;
			int ret;
			if (alloc_flags & ALLOC_WMARK_MIN)
				mark = zone->pages_min; // 选择min阈值
			else if (alloc_flags & ALLOC_WMARK_LOW)
				mark = zone->pages_low; // 选择low阈值
			else
				mark = zone->pages_high; // 选择high阈值
ゾーン_ウォーターマーク_ok

zone割り当てるのに十分なページがあることを確認してください

			if (zone_watermark_ok(zone, order, mark,
				    classzone_idx, alloc_flags))
				goto try_this_zone;

水位を確認します。これは、 、のいずれmaskです。この関数から、割り当てられるページはいくつかの条件を満たしている必要があることがわかります。lowminhigh2^order

  • 割り当てられたページ フレームに加えて、メモリ管理領域には少なくともmin1 つの空きページ フレームもあります。
  • 割り当てられたページ フレームに加えて、order少なくともoブロック内にmin/2^o空きページ フレーム以上の空きページ フレームが存在します。
/*
 * Return 1 if free pages are above 'mark'. This takes into account the order
 * of the allocation.
 */
int zone_watermark_ok(struct zone *z, int order, unsigned long mark,
		      int classzone_idx, int alloc_flags)
{
    
    
	/* free_pages my go negative - that's OK */
	long min = mark;
    // 获得空闲页的数量vm_stat[NR_FREE_PAGES]
    // 减去要分配的页面(1 << order)
	long free_pages = zone_page_state(z, NR_FREE_PAGES) - (1 << order) + 1;
	int o;

	if (alloc_flags & ALLOC_HIGH)
		min -= min / 2;
	if (alloc_flags & ALLOC_HARDER)
		min -= min / 4;
	// lowmem_reserve表示要预留的页面个数
	if (free_pages <= min + z->lowmem_reserve[classzone_idx]) 
		return 0;
	// 除去要分配的页面个数,从order k 到 order 10的空闲页面总数,至少得是 min/(2^k)
	for (o = 0; o < order; o++) {
    
    
		/* At the next order, this order's pages become unavailable */
		free_pages -= z->free_area[o].nr_free << o;

		/* Require fewer higher order pages to be free */
		min >>= 1;

		if (free_pages <= min)
			return 0;
	}
	return 1;
}

zone_watermark_ok水位try_this_zone監視を通過した後、直接ページフレームの割り当て、つまりbuffered_rmqueue関数の割り当てに進みます。

バッファされた_rmqueue

この機能は、相手システムのzone指定領域にページを配置するためのコア機能です。

static struct page *buffered_rmqueue(struct zone *preferred_zone,
			struct zone *zone, int order, gfp_t gfp_flags)
{
    
    
	unsigned long flags;
	struct page *page;
	int cold = !!(gfp_flags & __GFP_COLD);
	int cpu;
	int migratetype = allocflags_to_migratetype(gfp_flags); //

again:
	cpu  = get_cpu();
	if (likely(order == 0)) {
    
      // 表示单页,从pcplist进行分配 冷热页
		struct per_cpu_pages *pcp;
		// 获取到本节点的cpu高速缓存页
		pcp = &zone_pcp(zone, cpu)->pcp;
		local_irq_save(flags);
        // 该链表为空,大概率上次获取的cpu高速缓存的迁移类型和这次不一致
		if (!pcp->count) {
    
    
            // 从伙伴系统中获得页,然后向高速缓存中添加内存页
			pcp->count = rmqueue_bulk(zone, 0,
					pcp->batch, &pcp->list, migratetype);
            // 如果链表仍然为空,那么说明伙伴系统中页面也没有了,分配失败
			if (unlikely(!pcp->count))
				goto failed;
		}
		
		/* Find a page of the appropriate migrate type */
        // 如果分配的页面不需要考虑硬件缓存(注意不是每CPU页面缓存),则取出链表的最后一个节点返回给上层
		if (cold) {
    
    
			list_for_each_entry_reverse(page, &pcp->list, lru)
				if (page_private(page) == migratetype)
					break;
		} else {
    
     // 如果要考虑硬件缓存,则取出链表的第一个页面,这个页面是最近刚释放到每CPU缓存的,缓存热度更高
			list_for_each_entry(page, &pcp->list, lru)
				if (page_private(page) == migratetype)
					break;
		}

		/* Allocate more to the pcp list if necessary */
		if (unlikely(&page->lru == &pcp->list)) {
    
    
			pcp->count += rmqueue_bulk(zone, 0,
					pcp->batch, &pcp->list, migratetype);
			page = list_entry(pcp->list.next, struct page, lru);
		}
		//将页面从每CPU缓存链表中取出,并将每CPU缓存计数减1
		list_del(&page->lru);
		pcp->count--;
    // 分配的是多个页面,不需要考虑每CPU页面缓存,直接从系统中分配
	} else {
    
     // 去指定的migratetype的链表中去分配
		spin_lock_irqsave(&zone->lock, flags); //关中断,并获得管理区的锁
        
		page = __rmqueue(zone, order, migratetype);
		spin_unlock(&zone->lock); //先回收(打开)锁,待后面统计计数设置完毕后再开中断
		if (!page)
			goto failed;
	}
	// 事件统计计数,debug(调试)用
	__count_zone_vm_events(PGALLOC, zone, 1 << order);
	zone_statistics(preferred_zone, zone);
	local_irq_restore(flags); //恢复中断
	put_cpu();

	VM_BUG_ON(bad_range(zone, page));
	if (prep_new_page(page, order, gfp_flags))
		goto again;
	return page;

failed:
	local_irq_restore(flags);
	put_cpu();
	return NULL;
}

この機能は__rmqueue2 つの状況に分けられます。

  • クイック割り当て__rmqueue_smallest: 指定された移行タイプのリンク リストから直接割り当てます。
  • 遅い割り当て__rmqueue_fallback: 指定されたリンク リスト内の移行タイプに十分なメモリがない場合、バックアップ リストが使用されます。
/*
 * Do the hard work of removing an element from the buddy allocator.
 * Call me with the zone->lock already held.
 */
static struct page *__rmqueue(struct zone *zone, unsigned int order,
						int migratetype)
{
    
    
	struct page *page;
	// 快速分配
	page = __rmqueue_smallest(zone, order, migratetype);

	if (unlikely(!page))
		page = __rmqueue_fallback(zone, order, migratetype);

	return page;
}

この時点で完了ですが、zone_watermark_ok水位検知に失敗した場合は引き続き下方向へ呼び出しを行ってください。


ここで実行すると、zoneページをリサイクルする必要があることを示します

つまり、水位検出に失敗した場合は、使用可能なページが存在しないことを意味するため、ここである程度のメモリリサイクルが実行されます。

			ret = zone_reclaim(zone, gfp_mask, order);

zone_reclaimページをリサイクルすること

ページフレーム数が再利用された2^order場合のみ true が返され、再利用されてページフレーム数に達していない場合でも false が返されます。

int zone_reclaim(struct zone *zone, gfp_t gfp_mask, unsigned int order)
{
    
    
	int node_id;
	int ret;
	// 都小于最小设定的值
	if (zone_pagecache_reclaimable(zone) <= zone->min_unmapped_pages &&
	    zone_page_state(zone, NR_SLAB_RECLAIMABLE) <= zone->min_slab_pages)
		return ZONE_RECLAIM_FULL;

	if (zone_is_all_unreclaimable(zone)) // 设定了标识不回收
		return ZONE_RECLAIM_FULL;
	// 如果没有设置__GFP_WAIT,即wait为0,则不继续进行内存分配
    // 如果PF_MEMALLOC被设置,也就是说调用内存分配函数的本身就是内存回收进程,则不继续进行内存分配
	if (!(gfp_mask & __GFP_WAIT) || (current->flags & PF_MEMALLOC))
		return ZONE_RECLAIM_NOSCAN;

	node_id = zone_to_nid(zone);// 获得本zone的nodeid
    // 不属于该cpu范围
	if (node_state(node_id, N_CPU) && node_id != numa_node_id())
		return ZONE_RECLAIM_NOSCAN;
	// 其他进程在回收
	if (zone_test_and_set_flag(zone, ZONE_RECLAIM_LOCKED))
		return ZONE_RECLAIM_NOSCAN;

	ret = __zone_reclaim(zone, gfp_mask, order);// 回收该zone的页
	zone_clear_flag(zone, ZONE_RECLAIM_LOCKED);// 释放回收锁

	if (!ret)
		count_vm_event(PGSCAN_ZONE_RECLAIM_FAILED);

	return ret;
}

ここPF_MEMALLOCと はプロセス フラグ ビットである__GFP_WAITことに注意してください。通常、メモリ管理以外のサブシステムはこのフラグを使用すべきではありません。PF_MEMALLOC

コールダウンを続けます__zone_reclaim

static int __zone_reclaim(struct zone *zone, gfp_t gfp_mask, unsigned int order)
{
    
    
	/* Minimum pages needed in order to stay on node */
	const unsigned long nr_pages = 1 << order;
	struct task_struct *p = current;
	struct reclaim_state reclaim_state;
	int priority;
	struct scan_control sc = {
    
     // 控制扫描结果
		.may_writepage = !!(zone_reclaim_mode & RECLAIM_WRITE),
		.may_unmap = !!(zone_reclaim_mode & RECLAIM_SWAP),
		.may_swap = 1,
		.swap_cluster_max = max_t(unsigned long, nr_pages,
					SWAP_CLUSTER_MAX),
		.gfp_mask = gfp_mask,
		.swappiness = vm_swappiness,
		.order = order,
		.isolate_pages = isolate_pages_global,
	};
	unsigned long slab_reclaimable;

	disable_swap_token();
	cond_resched();
	/*
	 * We need to be able to allocate from the reserves for RECLAIM_SWAP
	 * and we also need to be able to write out pages for RECLAIM_WRITE
	 * and RECLAIM_SWAP.
	 */
	p->flags |= PF_MEMALLOC | PF_SWAPWRITE;
	reclaim_state.reclaimed_slab = 0;
	p->reclaim_state = &reclaim_state;

	if (zone_pagecache_reclaimable(zone) > zone->min_unmapped_pages) {
    
    
		priority = ZONE_RECLAIM_PRIORITY;
		do {
    
    
			note_zone_scanning_priority(zone, priority);
			shrink_zone(priority, zone, &sc); // 回收内存
			priority--;
		} while (priority >= 0 && sc.nr_reclaimed < nr_pages);
	}

	slab_reclaimable = zone_page_state(zone, NR_SLAB_RECLAIMABLE);
	if (slab_reclaimable > zone->min_slab_pages) {
    
    
		while (shrink_slab(sc.nr_scanned, gfp_mask, order) &&
			zone_page_state(zone, NR_SLAB_RECLAIMABLE) >
				slab_reclaimable - nr_pages)
			;
		sc.nr_reclaimed += slab_reclaimable -
			zone_page_state(zone, NR_SLAB_RECLAIMABLE);
	}

	p->reclaim_state = NULL;
	current->flags &= ~(PF_MEMALLOC | PF_SWAPWRITE);
	return sc.nr_reclaimed >= nr_pages;
}

についてshrink_zoneshrink_slab後ほど説明します

			switch (ret) {
    
    
			case ZONE_RECLAIM_NOSCAN:
				/* did not scan */
				goto try_next_zone;
			case ZONE_RECLAIM_FULL:
				/* scanned but unreclaimable */
				goto this_zone_full;
			default:
				/* did we reclaim enough */
				if (!zone_watermark_ok(zone, order, mark, 
						classzone_idx, alloc_flags))
					goto this_zone_full;
			}

理想的には、メモリの割り当てを開始する

try_this_zone:
		page = buffered_rmqueue(preferred_zone, zone, order, gfp_mask);
		if (page)
			break;

zoneいっぱいになるはずです

this_zone_full:
		if (NUMA_BUILD)
			zlc_mark_zone_full(zonelist, z);

次へ進むzone

try_next_zone:
		if (NUMA_BUILD && !did_zlc_setup) {
    
    
			/* we do zlc_setup after the first zone is tried */
			allowednodes = zlc_setup(zonelist, alloc_flags);
			zlc_active = 1;
			did_zlc_setup = 1;
		}
	}

再びサイクルします

	if (unlikely(NUMA_BUILD && page == NULL && zlc_active)) {
    
    
		/* Disable zlc cache for second zonelist scan */
		zlc_active = 0;
		goto zonelist_scan;
	}

メモリ割り当てが遅い

低速メモリ割り当て: 高速メモリ割り当てがある場合、つまり、高速割り当てでメモリが取得されないzonelist場合、低速割り当てに対してしきい値が使用されます。zonemin

  • 非同期メモリ圧縮
  • 直接メモリ再利用
  • 軽量同期メモリ圧縮 (oom割り当てに応じて)

nodeカーネルkswapdスレッドを起動します

	for_each_zone_zonelist(zone, z, zonelist, high_zoneidx)
		wakeup_kswapd(zone, order);  // 唤醒每个node的kswapd内核线程
/*
 * A zone is low on free memory, so wake its kswapd task to service it.
 */
void wakeup_kswapd(struct zone *zone, int order)
{
    
    
	pg_data_t *pgdat;

	if (!populated_zone(zone))
		return;

	pgdat = zone->zone_pgdat;
	// 检查水位
	if (zone_watermark_ok(zone, order, zone->pages_low, 0, 0))
		return;
	if (pgdat->kswapd_max_order < order)
		pgdat->kswapd_max_order = order;
	if (!cpuset_zone_allowed_hardwall(zone, GFP_KERNEL))  // 允许位
		return;
	if (!waitqueue_active(&pgdat->kswapd_wait))  
		return;
	wake_up_interruptible(&pgdat->kswapd_wait);  
}

要件を減らし、min高速メモリ割り当ての基準としてしきい値を使用するようにしてください。

	alloc_flags = ALLOC_WMARK_MIN;
	if ((unlikely(rt_task(p)) && !in_interrupt()) || !wait)
		alloc_flags |= ALLOC_HARDER;
	if (gfp_mask & __GFP_HIGH)
		alloc_flags |= ALLOC_HIGH;
	if (wait)
		alloc_flags |= ALLOC_CPUSET;

いくつかのマクロ定義はここにあります

  • ALLOC_HARDER: メモリの割り当てにさらに努力していることを示します
  • ALLOC_HIGH:発信者の__GFP_HIGH優先度を高く設定することを示します
  • ALLOC_CPUSET:cpusetメモリページの割り当てが許可されているかどうかを確認することを示します
	page = get_page_from_freelist(gfp_mask, nodemask, order, zonelist,
						high_zoneidx, alloc_flags);
	if (page)
		goto got_pg;

ここには「5 つの剣」という言葉がありますが、1 番目の剣はカジュアルに使用され、規格に従って割り当てられているlow、つまり直接呼び出されます。2 番目の剣は、規格に従ってメモリを割り当てるため、およびメモリを割り当てるときにget_free_page_list()使用されます。記号とをmin追加する記号ALLOC_WMARK_MINALLOC_HARDERALLOC_HIGH

ここでは、水位をまったくチェックせずに呼び出しが行われ、alloc_flags値が次のように割り当てられます。ALLOC_NO_WATERMARKS

rebalance:
	if (((p->flags & PF_MEMALLOC) || unlikely(test_thread_flag(TIF_MEMDIE)))
			&& !in_interrupt()) {
    
    
		if (!(gfp_mask & __GFP_NOMEMALLOC)) {
    
    
nofail_alloc:
			/* go through the zonelist yet again, ignoring mins */
			page = get_page_from_freelist(gfp_mask, nodemask, order,
				zonelist, high_zoneidx, ALLOC_NO_WATERMARKS); // 不检查水位分配内存
			if (page)
				goto got_pg;
			if (gfp_mask & __GFP_NOFAIL) {
    
    
				congestion_wait(WRITE, HZ/50);
				goto nofail_alloc;
			}
		}
		goto nopage;
	}
try_to_free_pages

メモリを同期的に解放してメモリ ページを取得します。主な機能は次のとおりです。try_to_free_pages

	cpuset_update_task_memory_state();
	p->flags |= PF_MEMALLOC;

	lockdep_set_current_reclaim_state(gfp_mask);
	reclaim_state.reclaimed_slab = 0;
	p->reclaim_state = &reclaim_state;

	did_some_progress = try_to_free_pages(zonelist, order,
						gfp_mask, nodemask);

	p->reclaim_state = NULL;
	lockdep_clear_current_reclaim_state();
	p->flags &= ~PF_MEMALLOC;

関数本体は

unsigned long try_to_free_pages(struct zonelist *zonelist, int order,
				gfp_t gfp_mask, nodemask_t *nodemask)
{
    
    
	struct scan_control sc = {
    
     // 扫描控制结构
		.gfp_mask = gfp_mask,
		.may_writepage = !laptop_mode,
		.swap_cluster_max = SWAP_CLUSTER_MAX,
		.may_unmap = 1,
		.may_swap = 1,
		.swappiness = vm_swappiness,
		.order = order,
		.mem_cgroup = NULL,
		.isolate_pages = isolate_pages_global,
		.nodemask = nodemask,
	};

	return do_try_to_free_pages(zonelist, &sc);
}

明確にする必要がある概念的な問題があります。最初の 3 つの方法はメモリを割り当てることです。メモリが不足している場合は、他のノードからnodeページ フレームを取得するだけです(もちろんメモリのリサイクルもありますzone_reclaimが、本質はメモリを見つけることです)他のノードから)、ここからは他のノードから直接このノードを検索します

do_try_to_free_pagesこれは、shrink_zoneページshrink_slabをリサイクルする主な論理機能です。

static unsigned long do_try_to_free_pages(struct zonelist *zonelist,
					struct scan_control *sc)
{
    
    
	int priority;
	unsigned long ret = 0;
	unsigned long total_scanned = 0;
	struct reclaim_state *reclaim_state = current->reclaim_state;
	unsigned long lru_pages = 0;
	struct zoneref *z;
	struct zone *zone;
	enum zone_type high_zoneidx = gfp_zone(sc->gfp_mask);

	delayacct_freepages_start();

	if (scanning_global_lru(sc))
		count_vm_event(ALLOCSTALL);
	/*
	 * mem_cgroup will not do shrink_slab.
	 */
	if (scanning_global_lru(sc)) {
    
    
		for_each_zone_zonelist(zone, z, zonelist, high_zoneidx) {
    
    

			if (!cpuset_zone_allowed_hardwall(zone, GFP_KERNEL))
				continue;

			lru_pages += zone_lru_pages(zone);
		}
	}

	for (priority = DEF_PRIORITY; priority >= 0; priority--) {
    
    
		sc->nr_scanned = 0;
		if (!priority)
			disable_swap_token();
		shrink_zones(priority, zonelist, sc);
		/*
		 * Don't shrink slabs when reclaiming memory from
		 * over limit cgroups
		 */
		if (scanning_global_lru(sc)) {
    
    
			shrink_slab(sc->nr_scanned, sc->gfp_mask, lru_pages);
			if (reclaim_state) {
    
    
				sc->nr_reclaimed += reclaim_state->reclaimed_slab;
				reclaim_state->reclaimed_slab = 0;
			}
		}
		total_scanned += sc->nr_scanned;
		if (sc->nr_reclaimed >= sc->swap_cluster_max) {
    
    
			ret = sc->nr_reclaimed;
			goto out;
		}

		/*
		 * Try to write back as many pages as we just scanned.  This
		 * tends to cause slow streaming writers to write data to the
		 * disk smoothly, at the dirtying rate, which is nice.   But
		 * that's undesirable in laptop mode, where we *want* lumpy
		 * writeout.  So in laptop mode, write out the whole world.
		 */
		if (total_scanned > sc->swap_cluster_max +
					sc->swap_cluster_max / 2) {
    
    
			wakeup_pdflush(laptop_mode ? 0 : total_scanned);
			sc->may_writepage = 1;
		}

		/* Take a nap, wait for some writeback to complete */
		if (sc->nr_scanned && priority < DEF_PRIORITY - 2)
			congestion_wait(WRITE, HZ/10);
	}
	/* top priority shrink_zones still had more to do? don't OOM, then */
	if (!sc->all_unreclaimable && scanning_global_lru(sc))
		ret = sc->nr_reclaimed;
out:
	/*
	 * Now that we've scanned all the zones at this priority level, note
	 * that level within the zone so that the next thread which performs
	 * scanning of this zone will immediately start out at this priority
	 * level.  This affects only the decision whether or not to bring
	 * mapped pages onto the inactive list.
	 */
	if (priority < 0)
		priority = 0;

	if (scanning_global_lru(sc)) {
    
    
		for_each_zone_zonelist(zone, z, zonelist, high_zoneidx) {
    
    

			if (!cpuset_zone_allowed_hardwall(zone, GFP_KERNEL))
				continue;

			zone->prev_priority = priority;
		}
	} else
		mem_cgroup_record_reclaim_priority(sc->mem_cgroup, priority);

	delayacct_freepages_end();

	return ret;
}	
	if (likely(did_some_progress)) {
    
    
		page = get_page_from_freelist(gfp_mask, nodemask, order,
					zonelist, high_zoneidx, alloc_flags); // 回收后再去分配
		if (page)
			goto got_pg;

todo、複雑すぎるので、後で見てみましょう

最後にomm、本当にページが割り当てられていない場合、特定のプロセスが占有しているページを強制終了する (少し残酷な) メカニズムを使用することです。これが、いわゆるメカニズムですout of memory killer

	} else if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) {
    
    
		if (!try_set_zone_oom(zonelist, gfp_mask)) {
    
    
			schedule_timeout_uninterruptible(1);
			goto restart;
		}

		/*
		 * Go through the zonelist yet one more time, keep
		 * very high watermark here, this is only to catch
		 * a parallel oom killing, we must fail if we're still
		 * under heavy pressure.
		 */
		page = get_page_from_freelist(gfp_mask|__GFP_HARDWALL, nodemask,
			order, zonelist, high_zoneidx,
			ALLOC_WMARK_HIGH|ALLOC_CPUSET); // 虚晃一枪,使用ALLOC_WMARK_HIGH来要求,明显不可能完成
		if (page) {
    
    
			clear_zonelist_oom(zonelist, gfp_mask);
			goto got_pg;
		}

		/* The OOM killer will not help higher order allocs so fail */
		if (order > PAGE_ALLOC_COSTLY_ORDER) {
    
    
			clear_zonelist_oom(zonelist, gfp_mask);
			goto nopage;
		}

		out_of_memory(zonelist, gfp_mask, order); // 释放进程的内存
		clear_zonelist_oom(zonelist, gfp_mask);
		goto restart;

スキャン制御

スキャン制御構造。その主な機能は、メモリの再利用またはメモリ圧縮のための変数とパラメータを保存することであり、一部の処理結果もここに保存されます。

主にメモリのリサイクルとメモリ圧縮に使用されます。

struct scan_control {
    
     
	/* Incremented by the number of inactive pages that were scanned */
	unsigned long nr_scanned;  // 已经扫描的页框数量

	/* Number of pages freed so far during a call to shrink_zones() */
	unsigned long nr_reclaimed;   // 已经回收的页框数量

	/* This context's GFP mask */
	gfp_t gfp_mask;  // 申请内存时使用的分配标志

	int may_writepage;  // 能否执行回写操作

	/* Can mapped pages be reclaimed? */
	int may_unmap;  // 能否进行unmap操作,即将所有映射了此页的页表项清空

	/* Can pages be swapped as part of reclaim? */
	int may_swap;  // 能否进行swap交换

	/* This context's SWAP_CLUSTER_MAX. If freeing memory for
	 * suspend, we effectively ignore SWAP_CLUSTER_MAX.
	 * In this context, it doesn't matter that we scan the
	 * whole list at once. */
	int swap_cluster_max;

	int swappiness;

	int all_unreclaimable;

	int order;  // 申请内存时使用的order值,因为只有申请内存,然后内存不足时才会进行扫描

	/* Which cgroup do we reclaim from */
	struct mem_cgroup *mem_cgroup;  // 目标memcg,如果是针对整个zone的,则此为NULL

	/*
	 * Nodemask of nodes allowed by the caller. If NULL, all nodes
	 * are scanned.
	 */
	nodemask_t	*nodemask;  // 允许执行扫描的node节点掩码

	/* Pluggable isolate pages callback */
	unsigned long (*isolate_pages)(unsigned long nr, struct list_head *dst,
			unsigned long *scanned, int order, int mode,
			struct zone *z, struct mem_cgroup *mem_cont,
			int active, int file);
};

メモリ圧縮テクノロジ: 通常の状況では、メモリが不足している場合、システムの最適化を達成するためにメモリ データをI/Oディスクに頻繁に書き戻すとflash、寿命に影響を与えるだけでなく、システムのパフォーマンスにも重大な影響を与えるため、メモリ圧縮テクノロジの導入が必要になります。 、主流なものは次のとおりです。

  • zSwap:スワップスペース、通常は圧縮された匿名ページ
  • zRam: メモリを使用してブロックデバイスをシミュレートする方法で、一般に匿名ページは圧縮されます。
  • zCache: 通常、ファイルのページは圧縮されています。

swap匿名ページは、プロセスのヒープやスタックなど、ファイルに関連付けられていないページであり、ディスク ファイルと交換することはできませんが、ハード ディスク上に追加のスワップ パーティションを分割するか、スワップ ファイルを使用することで交換できます。

アクティブページと遅延ページについては、システム内のアプリケーションが頻繁にアクセスするかどうかでページがアクティブであるかどうかを判断するのが一般的で、ページが設定されていない場合は遅延ページであるため、移動する必要があります。ページが設定されており、最近アクセスされたことを示す場合は、アクティブなリンク リストに移動する必要があります。

時間が経つにつれて、最もアクティブでないページが遅延リンク リストの最後に配置されます。メモリが不足すると、カーネルはこれらのページをスワップアウトします。これらのページは、誕生からスワップアウトされるまでほとんど使用されないためです。の原則に従ってLRU、これらのページを交換してもシステムへのダメージは最小限に抑えられます。

メモリの縮小

メモリのリサイクルは通常、適切なzoneメモリのリサイクルを指しますが、最後にmemcgリサイクルされるメモリのリサイクルを指す場合もあります。

  • 2^(order+1)毎回ページ フレームをリサイクルし、このメモリ割り当てに満足して、できるだけ多くのページ フレームを再利用しようとします。非アクティブリンクリストlruの番号がこの基準を満たしていない場合、この状態の判定はキャンセルされます。
  • zoneメモリのリサイクルにはメモリの圧縮が伴うzoneため、zoneメモリのリサイクルを実行すると、メモリ圧縮が完了するまで空きページ フレームが返されます。

前述のリサイクル機能によると、主に 3 つの機能がありますshrink_zoneshrink_listshrink_slab

シュリンクゾーン

static void shrink_zone(int priority, struct zone *zone,
				struct scan_control *sc)
{
    
    
	unsigned long nr[NR_LRU_LISTS];
	unsigned long nr_to_scan;
	unsigned long percent[2];	/* anon @ 0; file @ 1 */
	enum lru_list l;
	unsigned long nr_reclaimed = sc->nr_reclaimed;
	unsigned long swap_cluster_max = sc->swap_cluster_max;

get_scan_ratio

	get_scan_ratio(zone, sc, percent);

一般に、物理メモリが十分ではない場合、次の 2 つのオプションがあります。

  • 一部の匿名ページをswapパーティションに置き換えます
  • page cache内部のデータをディスクにフラッシュするか、直接クリーンアップします。

どちらの方法でも、swap重みは次のように置き換えられます。

	/*
	 * With swappiness at 100, anonymous and file have the same priority.
	 * This scanning priority is essentially the inverse of IO cost.
	 */
	anon_prio = sc->swappiness;
	file_prio = 200 - sc->swappiness;

まず完全に閉まっているかどうかを確認しますswap

static void get_scan_ratio(struct zone *zone, struct scan_control *sc,
					unsigned long *percent)
{
    
    
	unsigned long anon, file, free;
	unsigned long anon_prio, file_prio;
	unsigned long ap, fp;
	struct zone_reclaim_stat *reclaim_stat = get_reclaim_stat(zone, sc);

	/* If we have no swap space, do not bother scanning anon pages. */
	if (!sc->may_swap || (nr_swap_pages <= 0)) {
    
    
		percent[0] = 0;
		percent[1] = 100;
		return;
	}

page cache匿名ページとページの数を数える

	anon  = zone_nr_pages(zone, sc, LRU_ACTIVE_ANON) +
		zone_nr_pages(zone, sc, LRU_INACTIVE_ANON);
	file  = zone_nr_pages(zone, sc, LRU_ACTIVE_FILE) +
		zone_nr_pages(zone, sc, LRU_INACTIVE_FILE);

空きページ数free+page cacheページ数がhighしきい値未満の場合、すべてがswap中央に配置されます。

	if (scanning_global_lru(sc)) {
    
    
		free  = zone_page_state(zone, NR_FREE_PAGES);
		/* If we have very few page cache pages,
		   force-scan anon pages. */
		if (unlikely(file + free <= zone->pages_high)) {
    
      
			percent[0] = 100;
			percent[1] = 0;
			return;
		}
	}

割合を計算する

	anon_prio = sc->swappiness;
	file_prio = 200 - sc->swappiness;

	/*
	 * The amount of pressure on anon vs file pages is inversely
	 * proportional to the fraction of recently scanned pages on
	 * each list that were recently referenced and in active use.
	 */
	ap = (anon_prio + 1) * (reclaim_stat->recent_scanned[0] + 1);
	ap /= reclaim_stat->recent_rotated[0] + 1;

	fp = (file_prio + 1) * (reclaim_stat->recent_scanned[1] + 1);
	fp /= reclaim_stat->recent_rotated[1] + 1;

	/* Normalize to percentages */
	percent[0] = 100 * ap / (ap + fp + 1);
	percent[1] = 100 - percent[0];

途中に切り替えがあります

	for_each_evictable_lru(l) {
    
    
		int file = is_file_lru(l);
		unsigned long scan;

		scan = zone_nr_pages(zone, sc, l);
		if (priority) {
    
    
			scan >>= priority;
			scan = (scan * percent[file]) / 100;
		}
		if (scanning_global_lru(sc)) {
    
    
			zone->lru[l].nr_scan += scan;
			nr[l] = zone->lru[l].nr_scan;
			if (nr[l] >= swap_cluster_max)
				zone->lru[l].nr_scan = 0;
			else
				nr[l] = 0;
		} else
			nr[l] = scan;
	}

マクロは次for_each_evictable_lruのように展開されます

for (l = 0; l <= LRU_ACTIVE_FILE; l++)

l表現される変数は次のとおりですstruct lru_list

enum lru_list {
    
    
	LRU_INACTIVE_ANON = LRU_BASE,
	LRU_ACTIVE_ANON = LRU_BASE + LRU_ACTIVE,
	LRU_INACTIVE_FILE = LRU_BASE + LRU_FILE,
	LRU_ACTIVE_FILE = LRU_BASE + LRU_FILE + LRU_ACTIVE,
#ifdef CONFIG_UNEVICTABLE_LRU
	LRU_UNEVICTABLE,
#else
	LRU_UNEVICTABLE = LRU_ACTIVE_FILE, /* avoid compiler errors in dead code */
#endif
	NR_LRU_LISTS
};

Visible LRU_ACTIVE_FILE = LRU_BASE + LRU_FILE + LRU_ACTIVE、リサイクル可能なリンク リストを示します

lruしたがって、このループはこれら 3 つのリンク リストをループし、リサイクル可能なリンク リストをすべて走査します。


シュリンクリスト

LRU_INACTIVE_ANON,LRU_ACTIVE_ANON,LRU_INACTIVE_FILE,LRU_ACTIVE_FILEこの順序lruリンクされたリストをたどります

	while (nr[LRU_INACTIVE_ANON] || nr[LRU_ACTIVE_FILE] ||
					nr[LRU_INACTIVE_FILE]) {
    
    
		for_each_evictable_lru(l) {
    
    
			if (nr[l]) {
    
    
				nr_to_scan = min(nr[l], swap_cluster_max);
				nr[l] -= nr_to_scan;
				// 对此lru类型的链表进行回收
				nr_reclaimed += shrink_list(l, nr_to_scan,
							    zone, sc, priority);
			}
		}
		/*
		 * On large memory systems, scan >> priority can become
		 * really large. This is fine for the starting priority;
		 * we want to put equal scanning pressure on each zone.
		 * However, if the VM has a harder time of freeing pages,
		 * with multiple processes reclaiming pages, the total
		 * freeing target can get unreasonably large.
		 */
		if (nr_reclaimed > swap_cluster_max &&
			priority < DEF_PRIORITY && !current_is_kswapd())
			break;
	}

vfs_cache_init各ディレクトリエントリがどのように保存されたかを思い出してくださいshrinker_listここでメモリのリサイクルが実行されます。主なカテゴリは次のとおりです。

  • LRU_ACTIVE_FILE: アクティブ ファイル、呼び出し、プロセスshrink_active_listアクティブリンク リストlru
  • LRU_ACTIVE_ANON: アクティビティ匿名、呼び出しshrink_active_listlruアクティビティ リストの処理、非匿名アクティビティ ページが少なすぎます
  • shrink_inactive_list、非アクティブなlruリンクされたリスト
static unsigned long shrink_list(enum lru_list lru, unsigned long nr_to_scan,
	struct zone *zone, struct scan_control *sc, int priority)
{
    
    
	int file = is_file_lru(lru);

	if (lru == LRU_ACTIVE_FILE) {
    
    
		shrink_active_list(nr_to_scan, zone, sc, priority, file);
		return 0;
	}

	if (lru == LRU_ACTIVE_ANON && inactive_anon_is_low(zone, sc)) {
    
    
		shrink_active_list(nr_to_scan, zone, sc, priority, file);
		return 0;
	}
	return shrink_inactive_list(nr_to_scan, zone, sc, priority, file);
}

再利用されたメモリを設定する

	sc->nr_reclaimed = nr_reclaimed;

非匿名のアクティブなページが少なすぎる場合は、shrink_active_list

	/*
	 * Even if we did not try to evict anon pages at all, we want to
	 * rebalance the anon lru active/inactive ratio.
	 */
	if (inactive_anon_is_low(zone, sc))
		shrink_active_list(SWAP_CLUSTER_MAX, zone, sc, priority, 0);

書き戻されたダーティ ページが多すぎる場合は、ここでしばらくスリープします。

	throttle_vm_writeout(sc->gfp_mask);

シュリンクアクティブリスト

表示する機能を選択してください

static void shrink_active_list(unsigned long nr_pages, struct zone *zone,
			struct scan_control *sc, int priority, int file)
{
    
    
	unsigned long pgmoved;
	int pgdeactivate = 0;
	unsigned long pgscanned;
	LIST_HEAD(l_hold);	/* The pages which were snipped off */
	LIST_HEAD(l_inactive);
	struct page *page;
	struct pagevec pvec;
	enum lru_list lru;
	struct zone_reclaim_stat *reclaim_stat = get_reclaim_stat(zone, sc);

	lru_add_drain();
	spin_lock_irq(&zone->lru_lock);
	pgmoved = sc->isolate_pages(nr_pages, &l_hold, &pgscanned, sc->order,
					ISOLATE_ACTIVE, zone,
					sc->mem_cgroup, 1, file);
	/*
	 * zone->pages_scanned is used for detect zone's oom
	 * mem_cgroup remembers nr_scan by itself.
	 */
	if (scanning_global_lru(sc)) {
    
    
		zone->pages_scanned += pgscanned;
	}
	reclaim_stat->recent_scanned[!!file] += pgmoved;

	if (file)
		__mod_zone_page_state(zone, NR_ACTIVE_FILE, -pgmoved);
	else
		__mod_zone_page_state(zone, NR_ACTIVE_ANON, -pgmoved);
	spin_unlock_irq(&zone->lru_lock);

	pgmoved = 0;
	while (!list_empty(&l_hold)) {
    
    
		cond_resched();
		page = lru_to_page(&l_hold);
		list_del(&page->lru);

		if (unlikely(!page_evictable(page, NULL))) {
    
    
			putback_lru_page(page);
			continue;
		}

		/* page_referenced clears PageReferenced */
		if (page_mapping_inuse(page) &&
		    page_referenced(page, 0, sc->mem_cgroup))
			pgmoved++;

		list_add(&page->lru, &l_inactive);
	}

	/*
	 * Move the pages to the [file or anon] inactive list.
	 */
	pagevec_init(&pvec, 1);
	lru = LRU_BASE + file * LRU_FILE;

	spin_lock_irq(&zone->lru_lock);
	/*
	 * Count referenced pages from currently used mappings as
	 * rotated, even though they are moved to the inactive list.
	 * This helps balance scan pressure between file and anonymous
	 * pages in get_scan_ratio.
	 */
	reclaim_stat->recent_rotated[!!file] += pgmoved;

	pgmoved = 0;
	while (!list_empty(&l_inactive)) {
    
    
		page = lru_to_page(&l_inactive);
		prefetchw_prev_lru_page(page, &l_inactive, flags);
		VM_BUG_ON(PageLRU(page));
		SetPageLRU(page);
		VM_BUG_ON(!PageActive(page));
		ClearPageActive(page);

		list_move(&page->lru, &zone->lru[lru].list);
		mem_cgroup_add_lru_list(page, lru);
		pgmoved++;
		if (!pagevec_add(&pvec, page)) {
    
    
			__mod_zone_page_state(zone, NR_LRU_BASE + lru, pgmoved);
			spin_unlock_irq(&zone->lru_lock);
			pgdeactivate += pgmoved;
			pgmoved = 0;
			if (buffer_heads_over_limit)
				pagevec_strip(&pvec);
			__pagevec_release(&pvec);
			spin_lock_irq(&zone->lru_lock);
		}
	}
	__mod_zone_page_state(zone, NR_LRU_BASE + lru, pgmoved);
	pgdeactivate += pgmoved;
	__count_zone_vm_events(PGREFILL, zone, pgscanned);
	__count_vm_events(PGDEACTIVATE, pgdeactivate);
	spin_unlock_irq(&zone->lru_lock);
	if (buffer_heads_over_limit)
		pagevec_strip(&pvec);
	pagevec_release(&pvec);
}

高速メモリ再利用

この機能でget_page_from_freelist()は、トラバーサルzonelist処理中に、zone割り当て前にそれぞれが判断され、zone割り当て後の空きメモリ量 < しきい値 + 予約されたページ フレーム数の場合、zone高速メモリ リサイクルが実行されます。

しきい値はmin/low/high次のいずれかになります。

直接メモリ再利用

直接メモリ再利用は低速割り当てで発生します。低速割り当てでは、nodeすべてのノードのkswapカーネル スレッドが最初に起動され、その後、呼び出しはしきい値get_page_from_freelistを使用してmin連続ページ フレームをzonelist後ろから削除しようとしzoneます。失敗すると、非同期圧縮が行われます。非同期zonelist圧縮しきい値の使用が試行され、失敗した場合は、ダイレクト メモリ リサイクルが実行されます。zoneget_page_from_freelistmin

kswapd メモリのリサイクル

kswapd->balance_pgdat()->kswapd_shrink_zone()->shrink_zone()

get_page_from_freelist()割り当てプロセス中、関数がしきい値から連続ページ フレームを取得できず、割り当てメモリ フラグがマークされていないlow限りカーネル スレッドが起動され、その中でメモリのリサイクルが実行されます。zonelistzonegfp_mask__GFP_NO_KSWAPDkswapdkswapd

おすすめ

転載: blog.csdn.net/qq_48322523/article/details/128307150