Swappiness는 RAM 사용 비율이 아니라 scan 압력의 입력입니다

swappiness를 60으로 두면 RAM의 60%를 swap한다는 설명을 종종 접하지만, reclaim의 실제 계산과는 다릅니다. 이 값은 anonymous와 file-backed list에 가할 scan 압력을 정하는 입력 중 하나입니다. list 크기, priority, swap 가능 여부, refault 비용, 특수 reclaim mode가 함께 작동합니다.

먼저 어느 list를 볼지 결정합니다

classic reclaim의 get_scan_count()는 네 list의 목표 scan 수를 만듭니다. inactive anon, active anon, inactive file, active file 각각의 base page 수입니다. SCAN_EQUAL은 네 list에서 같은 장수를 읽는다는 뜻이 아닙니다. 각 list의 priority-adjusted 크기에 추가적인 anon/file 가중을 적용하지 않는다는 뜻입니다.

swap이 허용되지 않거나 anon을 회수할 backing 조건이 없으면 file 쪽을 택합니다. 가장 적극적인 priority에 이르고 swappiness가 0이 아니라면 회수 실패를 피하기 위해 양쪽을 폭넓게 볼 수 있습니다. 작은 file cache만 계속 공격하는 상황에서는 anon만, 충분한 비활성 file cache를 저렴하게 비울 수 있는 cache-trim 상황에서는 file만 선택할 수 있습니다. 이러한 branch가 fraction 계산보다 먼저 적용됩니다.

따라서 swappiness 0은 모든 조건에서 anonymous memory를 절대로 건드리지 않는 보편적인 금지 flag로 사용할 수 없습니다. memcg 정책과 전역 reclaim의 의미도 동일하지 않습니다. 값의 범위는 0~200이고, 100은 양쪽 비용이 같을 때의 중립점입니다. 값이 100보다 크면 RAM 기반 swap 등 file 재읽기보다 swap이 저렴한 구성을 표현할 수 있습니다.

branch의 순서도 정책입니다. v6.12에서는 swap 불허·anon 회수 불가, memcg reclaim의 swappiness 0, priority 0이면서 swappiness nonzero, tiny-file, cache-trim 순서로 결정합니다. 그래서 tiny-file은 cache-trim보다 앞서지만 swap 불허를 무시하지는 않습니다. v6.18에는 proactive anon-only 요청을 나타내는 별도 SWAPPINESS_ANON_ONLY sentinel branch도 있습니다. 이 sentinel은 일반 vm.swappiness의 0~200 범위에 대한 예외값을 마음대로 쓰라는 뜻이 아닙니다. 두 version 모두 실제 값은 sc_swappiness()를 통해 context에 맞게 읽습니다. 일반 전역 기본값 60을 모든 cgroup·proactive 요청에 고정해서 대입하면 안 됩니다.

Thrashing 비용을 가중치로 바꿉니다

fraction mode에서는 lruvec에서 가져온 anon cost와 file cost를 사용합니다. 아래 식은 동작 방향을 이해하기 위한 대수적 모델입니다. 실제 kernel의 integer 연산, 0 나누기 방지용 offset, 반올림을 그대로 옮긴 실행 코드는 아닙니다.

a = anonymous reclaim cost
f = file reclaim cost
S = swappiness
A = 2*a + f
F = a + 2*f
anon_weight = S * F
file_weight = (200 - S) * A
anon_fraction = anon_weight / (anon_weight + file_weight)
file_fraction = file_weight / (anon_weight + file_weight)

anonymous refault 비용 a가 커지면 file_weight 쪽이 더 커집니다. 다시 사용할 anonymous 내용을 계속 쫓아내지 않도록 다른 쪽에 압력을 보내는 것입니다. file refault 비용이 커지면 반대로 anon scan을 늘립니다. cost를 자기 list의 scan 압력에 그대로 곱하는 식이 아닙니다.

양쪽 비용이 같고 특수 mode가 아니라면 S=60은 anon/file 가중의 대략 30:70에 해당합니다. 그러나 서로 다른 크기의 list에 이 비율을 적용하므로 전체 scanned page의 정확한 30:70이나 resident memory의 30:70을 보장하지는 않습니다. 실제로 free되는 비율은 회수 성공률에 따라 다시 달라집니다.

v6.12의 실제 integer 순서는 t=a+f, A=t+a, F=t+f, T=A+F를 만든 뒤 ap=S*(T+1)/(A+1), fp=(200-S)*(T+1)/(F+1)를 계산하는 것입니다. 분모는 ap+fp이고 두 가중값은 u64 fraction[ANON_AND_FILE]로 전달됩니다. 위 단순화는 offset과 각 나눗셈의 truncation을 제거한 뒤 공통 factor를 약분한 모델입니다. a=f=0이면 실제 code는 ap=S, fp=200-S를 얻어 0 나누기를 피합니다. cost가 모두 0인데 단순화 식의 0/0을 그대로 계산하면 안 됩니다.

v6.18은 이 계산을 별도의 calculate_pressure_balance()로 옮겼습니다. 아래는 그 helper 전체입니다. pressure fraction을 만든다는 목적과 integer 연산 순서를 함께 확인하실 수 있습니다.

v6.18 계산 helper · 전체 code · GPL-2.0

v6_18_calculate_pressure_balance.c

// SPDX-License-Identifier: GPL-2.0
/*
 *  Copyright (C) 1991, 1992, 1993, 1994  Linus Torvalds
 *
 *  Swap reorganised 29.12.95, Stephen Tweedie.
 *  kswapd added: 7.1.96  sct
 *  Removed kswapd_ctl limits, and swap out as many pages as needed
 *  to bring the system back to freepages.high: 2.4.97, Rik van Riel.
 *  Zone aware kswapd started 02/00, Kanoj Sarcar (kanoj@sgi.com).
 *  Multiqueue VM started 5.8.00, Rik van Riel.
 */

/* Extracted unchanged from Linux v6.18, mm/vmscan.c:2449-2484.
 * Full function definition; surrounding kernel context is required.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/vmscan.c#L2449
 * License text: GPL-2.0.txt (in this directory).
 */
static inline void calculate_pressure_balance(struct scan_control *sc,
			int swappiness, u64 *fraction, u64 *denominator)
{
	unsigned long anon_cost, file_cost, total_cost;
	unsigned long ap, fp;

	/*
	 * Calculate the pressure balance between anon and file pages.
	 *
	 * The amount of pressure we put on each LRU is inversely
	 * proportional to the cost of reclaiming each list, as
	 * determined by the share of pages that are refaulting, times
	 * the relative IO cost of bringing back a swapped out
	 * anonymous page vs reloading a filesystem page (swappiness).
	 *
	 * Although we limit that influence to ensure no list gets
	 * left behind completely: at least a third of the pressure is
	 * applied, before swappiness.
	 *
	 * With swappiness at 100, anon and file have equal IO cost.
	 */
	total_cost = sc->anon_cost + sc->file_cost;
	anon_cost = total_cost + sc->anon_cost;
	file_cost = total_cost + sc->file_cost;
	total_cost = anon_cost + file_cost;

	ap = swappiness * (total_cost + 1);
	ap /= anon_cost + 1;

	fp = (MAX_SWAPPINESS - swappiness) * (total_cost + 1);
	fp /= file_cost + 1;

	fraction[WORKINGSET_ANON] = ap;
	fraction[WORKINGSET_FILE] = fp;
	*denominator = ap + fp;
}

2/3 상한은 조건 없이 적용되지 않습니다

cost를 교차 반영하는 계산은 한쪽에 압력을 완전히 몰지 않도록 완화합니다. S=100에서 file cost가 anon cost보다 압도적으로 크면 anon fraction은 2/3에 접근합니다. 반대쪽도 대칭적입니다. 이 2/3를 모든 swappiness 값에 적용하는 것은 잘못입니다.

같은 단순화 모델에서 file cost가 지배적일 때 anon fraction의 극한은 2*S / (S + 200)입니다. anon cost가 지배적일 때 file fraction은 (400 - 2*S) / (400 - S)에 접근합니다. S 자체가 바뀌면 상한도 바뀝니다. 비용을 모르는 상태나 양쪽이 0인 상황을 실제 integer 코드가 어떻게 다루는지도 따로 보아야 합니다.

예를 들어 연속 모델에서 a=4, f=12, S=60이면 A=20, F=28이고 anon weight는 1680, file weight는 2800이므로 fraction은 37.5:62.5입니다. S만 보고 30:70이라고 정할 수 없는 예입니다. S=50의 25:75, S=150의 75:25 역시 cost가 같을 때의 중립적인 비교이며 모든 cost 조합의 결과는 아닙니다. 또한 cost를 고정하고 S만 바꾸어도 정규화된 최종 fraction이 항상 S의 단순 선형함수인 것은 아닙니다.

한쪽 cost가 지배적인 극한을 S=60에 적용하면 anon 쪽 상한은 약 46.15%, file 쪽 상한은 약 82.35%입니다. S=100에서는 둘 다 66.67%가 됩니다. 이 수치는 offset을 버린 pressure 모델이며 kernel의 실제 scan counter나 runtime 측정치가 아닙니다. list 크기와 성공률을 적용하기 전이라는 전제도 유지해야 합니다.

Fraction은 각 list의 크기에 적용됩니다

각 LRU의 크기는 lruvec_lru_size() 등으로 구하고 priority로 줄입니다. 그 결과에 anon/file fraction을 적용해 nr[]를 채웁니다. file family 판정은 LRU index로 수행하므로 active와 inactive 모두 같은 family 가중을 받되 list 크기는 각각 다를 수 있습니다.

SCAN_ANON·SCAN_FILE은 반대 family의 목표를 0으로 만들고, SCAN_EQUAL은 priority-adjusted 목표를 그대로 둡니다. fraction mode는 이 목표를 비율로 줄이며, 큰 값의 곱셈·나눗셈에는 overflow와 32-bit 환경을 고려한 helper를 사용합니다. 실수 수식이 간단하다고 실제 구현의 offset과 rounding을 없애면 작은 list에서 결과가 달라질 수 있습니다.

scan balance를 해석할 때에는 세 숫자를 분리하는 것이 가장 유용합니다. 정책이 만든 목표량, 실제 검사량, 최종 회수량입니다. swappiness는 첫 번째에 관여하며 나머지 두 개를 단독으로 결정하지 않습니다.

online memcg에서는 div64_u64()로 나눠 내림하고, offline memcg에서는 남은 마지막 page를 놓치지 않도록 올림하는 branch가 있습니다. priority shift 결과가 0이고 memcg가 offline이면 min(lruvec_size, SWAP_CLUSTER_MAX)를 scan budget으로 살리는 처리도 있습니다. cgroup이 비활성인 구성은 online 기본 경로로 읽습니다. 64-bit division helper를 쓴다는 사실만으로 그보다 앞선 모든 곱셈에 무조건적인 overflow 안전성이 생기는 것은 아닙니다.

아래는 v6.12의 get_scan_count() 전체입니다. memory.min·memory.low가 있으면 보호된 사용량만큼 scan 압력을 비례 축소하고, soft protection을 넘겨 재시도했는지도 고려합니다. 보호값과 usage의 동시 변화에는 max(cgroup_size, protection)으로 대응하며, 이 결과를 priority shift와 family fraction보다 먼저 적용합니다. 모든 cgroup의 list 크기에 단순히 같은 right shift만 하는 구현은 아닙니다.

v6.12 전체 함수 · 전체 code

v6_12_get_scan_count.c

// SPDX-License-Identifier: GPL-2.0
/*
 *  Copyright (C) 1991, 1992, 1993, 1994  Linus Torvalds
 *
 *  Swap reorganised 29.12.95, Stephen Tweedie.
 *  kswapd added: 7.1.96  sct
 *  Removed kswapd_ctl limits, and swap out as many pages as needed
 *  to bring the system back to freepages.high: 2.4.97, Rik van Riel.
 *  Zone aware kswapd started 02/00, Kanoj Sarcar (kanoj@sgi.com).
 *  Multiqueue VM started 5.8.00, Rik van Riel.
 */

/* Extracted unchanged from Linux v6.12, mm/vmscan.c:2369-2564.
 * Full function definition; surrounding kernel context is required.
 * Source: https://github.com/torvalds/linux/blob/v6.12/mm/vmscan.c#L2369
 * License text: GPL-2.0.txt (in this directory).
 */
static void get_scan_count(struct lruvec *lruvec, struct scan_control *sc,
			   unsigned long *nr)
{
	struct pglist_data *pgdat = lruvec_pgdat(lruvec);
	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
	unsigned long anon_cost, file_cost, total_cost;
	int swappiness = sc_swappiness(sc, memcg);
	u64 fraction[ANON_AND_FILE];
	u64 denominator = 0;	/* gcc */
	enum scan_balance scan_balance;
	unsigned long ap, fp;
	enum lru_list lru;

	/* If we have no swap space, do not bother scanning anon folios. */
	if (!sc->may_swap || !can_reclaim_anon_pages(memcg, pgdat->node_id, sc)) {
		scan_balance = SCAN_FILE;
		goto out;
	}

	/*
	 * Global reclaim will swap to prevent OOM even with no
	 * swappiness, but memcg users want to use this knob to
	 * disable swapping for individual groups completely when
	 * using the memory controller's swap limit feature would be
	 * too expensive.
	 */
	if (cgroup_reclaim(sc) && !swappiness) {
		scan_balance = SCAN_FILE;
		goto out;
	}

	/*
	 * Do not apply any pressure balancing cleverness when the
	 * system is close to OOM, scan both anon and file equally
	 * (unless the swappiness setting disagrees with swapping).
	 */
	if (!sc->priority && swappiness) {
		scan_balance = SCAN_EQUAL;
		goto out;
	}

	/*
	 * If the system is almost out of file pages, force-scan anon.
	 */
	if (sc->file_is_tiny) {
		scan_balance = SCAN_ANON;
		goto out;
	}

	/*
	 * If there is enough inactive page cache, we do not reclaim
	 * anything from the anonymous working right now.
	 */
	if (sc->cache_trim_mode) {
		scan_balance = SCAN_FILE;
		goto out;
	}

	scan_balance = SCAN_FRACT;
	/*
	 * Calculate the pressure balance between anon and file pages.
	 *
	 * The amount of pressure we put on each LRU is inversely
	 * proportional to the cost of reclaiming each list, as
	 * determined by the share of pages that are refaulting, times
	 * the relative IO cost of bringing back a swapped out
	 * anonymous page vs reloading a filesystem page (swappiness).
	 *
	 * Although we limit that influence to ensure no list gets
	 * left behind completely: at least a third of the pressure is
	 * applied, before swappiness.
	 *
	 * With swappiness at 100, anon and file have equal IO cost.
	 */
	total_cost = sc->anon_cost + sc->file_cost;
	anon_cost = total_cost + sc->anon_cost;
	file_cost = total_cost + sc->file_cost;
	total_cost = anon_cost + file_cost;

	ap = swappiness * (total_cost + 1);
	ap /= anon_cost + 1;

	fp = (MAX_SWAPPINESS - swappiness) * (total_cost + 1);
	fp /= file_cost + 1;

	fraction[0] = ap;
	fraction[1] = fp;
	denominator = ap + fp;
out:
	for_each_evictable_lru(lru) {
		bool file = is_file_lru(lru);
		unsigned long lruvec_size;
		unsigned long low, min;
		unsigned long scan;

		lruvec_size = lruvec_lru_size(lruvec, lru, sc->reclaim_idx);
		mem_cgroup_protection(sc->target_mem_cgroup, memcg,
				      &min, &low);

		if (min || low) {
			/*
			 * Scale a cgroup's reclaim pressure by proportioning
			 * its current usage to its memory.low or memory.min
			 * setting.
			 *
			 * This is important, as otherwise scanning aggression
			 * becomes extremely binary -- from nothing as we
			 * approach the memory protection threshold, to totally
			 * nominal as we exceed it.  This results in requiring
			 * setting extremely liberal protection thresholds. It
			 * also means we simply get no protection at all if we
			 * set it too low, which is not ideal.
			 *
			 * If there is any protection in place, we reduce scan
			 * pressure by how much of the total memory used is
			 * within protection thresholds.
			 *
			 * There is one special case: in the first reclaim pass,
			 * we skip over all groups that are within their low
			 * protection. If that fails to reclaim enough pages to
			 * satisfy the reclaim goal, we come back and override
			 * the best-effort low protection. However, we still
			 * ideally want to honor how well-behaved groups are in
			 * that case instead of simply punishing them all
			 * equally. As such, we reclaim them based on how much
			 * memory they are using, reducing the scan pressure
			 * again by how much of the total memory used is under
			 * hard protection.
			 */
			unsigned long cgroup_size = mem_cgroup_size(memcg);
			unsigned long protection;

			/* memory.low scaling, make sure we retry before OOM */
			if (!sc->memcg_low_reclaim && low > min) {
				protection = low;
				sc->memcg_low_skipped = 1;
			} else {
				protection = min;
			}

			/* Avoid TOCTOU with earlier protection check */
			cgroup_size = max(cgroup_size, protection);

			scan = lruvec_size - lruvec_size * protection /
				(cgroup_size + 1);

			/*
			 * Minimally target SWAP_CLUSTER_MAX pages to keep
			 * reclaim moving forwards, avoiding decrementing
			 * sc->priority further than desirable.
			 */
			scan = max(scan, SWAP_CLUSTER_MAX);
		} else {
			scan = lruvec_size;
		}

		scan >>= sc->priority;

		/*
		 * If the cgroup's already been deleted, make sure to
		 * scrape out the remaining cache.
		 */
		if (!scan && !mem_cgroup_online(memcg))
			scan = min(lruvec_size, SWAP_CLUSTER_MAX);

		switch (scan_balance) {
		case SCAN_EQUAL:
			/* Scan lists relative to size */
			break;
		case SCAN_FRACT:
			/*
			 * Scan types proportional to swappiness and
			 * their relative recent reclaim efficiency.
			 * Make sure we don't miss the last page on
			 * the offlined memory cgroups because of a
			 * round-off error.
			 */
			scan = mem_cgroup_online(memcg) ?
			       div64_u64(scan * fraction[file], denominator) :
			       DIV64_U64_ROUND_UP(scan * fraction[file],
						  denominator);
			break;
		case SCAN_FILE:
		case SCAN_ANON:
			/* Scan one type exclusively */
			if ((scan_balance == SCAN_FILE) != file)
				scan = 0;
			break;
		default:
			/* Look ma, no brain */
			BUG();
		}

		nr[lru] = scan;
	}
}

v6.18에서 분리된 함수까지 읽습니다

v6.18에서는 protection 계산을 apply_proportional_protection()으로 분리했습니다. low가 min보다 크고 아직 low reclaim 재시도가 아니면 low를 적용하면서 memcg_low_skipped를 남깁니다. 그렇지 않으면 min을 적용합니다. 최소 scan granularity는 보호를 무시하고 특정 32 page를 free하라는 뜻이 아니라 이후 scan 계산이 진행하도록 하는 budget 조정입니다.

v6.18 protection helper · 전체 code

v6_18_apply_proportional_protection.c

// SPDX-License-Identifier: GPL-2.0
/*
 *  Copyright (C) 1991, 1992, 1993, 1994  Linus Torvalds
 *
 *  Swap reorganised 29.12.95, Stephen Tweedie.
 *  kswapd added: 7.1.96  sct
 *  Removed kswapd_ctl limits, and swap out as many pages as needed
 *  to bring the system back to freepages.high: 2.4.97, Rik van Riel.
 *  Zone aware kswapd started 02/00, Kanoj Sarcar (kanoj@sgi.com).
 *  Multiqueue VM started 5.8.00, Rik van Riel.
 */

/* Extracted unchanged from Linux v6.18, mm/vmscan.c:2486-2547.
 * Full function definition; surrounding kernel context is required.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/vmscan.c#L2486
 * License text: GPL-2.0.txt (in this directory).
 */
static unsigned long apply_proportional_protection(struct mem_cgroup *memcg,
		struct scan_control *sc, unsigned long scan)
{
	unsigned long min, low;

	mem_cgroup_protection(sc->target_mem_cgroup, memcg, &min, &low);

	if (min || low) {
		/*
		 * Scale a cgroup's reclaim pressure by proportioning
		 * its current usage to its memory.low or memory.min
		 * setting.
		 *
		 * This is important, as otherwise scanning aggression
		 * becomes extremely binary -- from nothing as we
		 * approach the memory protection threshold, to totally
		 * nominal as we exceed it.  This results in requiring
		 * setting extremely liberal protection thresholds. It
		 * also means we simply get no protection at all if we
		 * set it too low, which is not ideal.
		 *
		 * If there is any protection in place, we reduce scan
		 * pressure by how much of the total memory used is
		 * within protection thresholds.
		 *
		 * There is one special case: in the first reclaim pass,
		 * we skip over all groups that are within their low
		 * protection. If that fails to reclaim enough pages to
		 * satisfy the reclaim goal, we come back and override
		 * the best-effort low protection. However, we still
		 * ideally want to honor how well-behaved groups are in
		 * that case instead of simply punishing them all
		 * equally. As such, we reclaim them based on how much
		 * memory they are using, reducing the scan pressure
		 * again by how much of the total memory used is under
		 * hard protection.
		 */
		unsigned long cgroup_size = mem_cgroup_size(memcg);
		unsigned long protection;

		/* memory.low scaling, make sure we retry before OOM */
		if (!sc->memcg_low_reclaim && low > min) {
			protection = low;
			sc->memcg_low_skipped = 1;
		} else {
			protection = min;
		}

		/* Avoid TOCTOU with earlier protection check */
		cgroup_size = max(cgroup_size, protection);

		scan -= scan * protection / (cgroup_size + 1);

		/*
		 * Minimally target SWAP_CLUSTER_MAX pages to keep
		 * reclaim moving forwards, avoiding decrementing
		 * sc->priority further than desirable.
		 */
		scan = max(scan, SWAP_CLUSTER_MAX);
	}
	return scan;
}

마지막으로 아래는 두 helper를 호출하는 v6.18의 get_scan_count() 전체입니다. v6.12 전체 함수와 나란히 보면 helper 분리와 proactive anon-only 예외를 실제 scope에서 구분할 수 있습니다. 어느 version도 swappiness를 resident RAM의 배분 비율로 사용하지 않습니다.

v6.18 전체 함수 · 전체 code

v6_18_get_scan_count.c

// SPDX-License-Identifier: GPL-2.0
/*
 *  Copyright (C) 1991, 1992, 1993, 1994  Linus Torvalds
 *
 *  Swap reorganised 29.12.95, Stephen Tweedie.
 *  kswapd added: 7.1.96  sct
 *  Removed kswapd_ctl limits, and swap out as many pages as needed
 *  to bring the system back to freepages.high: 2.4.97, Rik van Riel.
 *  Zone aware kswapd started 02/00, Kanoj Sarcar (kanoj@sgi.com).
 *  Multiqueue VM started 5.8.00, Rik van Riel.
 */

/* Extracted unchanged from Linux v6.18, mm/vmscan.c:2556-2670.
 * Full function definition; surrounding kernel context is required.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/vmscan.c#L2556
 * License text: GPL-2.0.txt (in this directory).
 */
static void get_scan_count(struct lruvec *lruvec, struct scan_control *sc,
			   unsigned long *nr)
{
	struct pglist_data *pgdat = lruvec_pgdat(lruvec);
	struct mem_cgroup *memcg = lruvec_memcg(lruvec);
	int swappiness = sc_swappiness(sc, memcg);
	u64 fraction[ANON_AND_FILE];
	u64 denominator = 0;	/* gcc */
	enum scan_balance scan_balance;
	enum lru_list lru;

	/* If we have no swap space, do not bother scanning anon folios. */
	if (!sc->may_swap || !can_reclaim_anon_pages(memcg, pgdat->node_id, sc)) {
		scan_balance = SCAN_FILE;
		goto out;
	}

	/*
	 * Global reclaim will swap to prevent OOM even with no
	 * swappiness, but memcg users want to use this knob to
	 * disable swapping for individual groups completely when
	 * using the memory controller's swap limit feature would be
	 * too expensive.
	 */
	if (cgroup_reclaim(sc) && !swappiness) {
		scan_balance = SCAN_FILE;
		goto out;
	}

	/* Proactive reclaim initiated by userspace for anonymous memory only */
	if (swappiness == SWAPPINESS_ANON_ONLY) {
		WARN_ON_ONCE(!sc->proactive);
		scan_balance = SCAN_ANON;
		goto out;
	}

	/*
	 * Do not apply any pressure balancing cleverness when the
	 * system is close to OOM, scan both anon and file equally
	 * (unless the swappiness setting disagrees with swapping).
	 */
	if (!sc->priority && swappiness) {
		scan_balance = SCAN_EQUAL;
		goto out;
	}

	/*
	 * If the system is almost out of file pages, force-scan anon.
	 */
	if (sc->file_is_tiny) {
		scan_balance = SCAN_ANON;
		goto out;
	}

	/*
	 * If there is enough inactive page cache, we do not reclaim
	 * anything from the anonymous working right now to make sure
         * a streaming file access pattern doesn't cause swapping.
	 */
	if (sc->cache_trim_mode) {
		scan_balance = SCAN_FILE;
		goto out;
	}

	scan_balance = SCAN_FRACT;
	calculate_pressure_balance(sc, swappiness, fraction, &denominator);

out:
	for_each_evictable_lru(lru) {
		bool file = is_file_lru(lru);
		unsigned long lruvec_size;
		unsigned long scan;

		lruvec_size = lruvec_lru_size(lruvec, lru, sc->reclaim_idx);
		scan = apply_proportional_protection(memcg, sc, lruvec_size);
		scan >>= sc->priority;

		/*
		 * If the cgroup's already been deleted, make sure to
		 * scrape out the remaining cache.
		 */
		if (!scan && !mem_cgroup_online(memcg))
			scan = min(lruvec_size, SWAP_CLUSTER_MAX);

		switch (scan_balance) {
		case SCAN_EQUAL:
			/* Scan lists relative to size */
			break;
		case SCAN_FRACT:
			/*
			 * Scan types proportional to swappiness and
			 * their relative recent reclaim efficiency.
			 * Make sure we don't miss the last page on
			 * the offlined memory cgroups because of a
			 * round-off error.
			 */
			scan = mem_cgroup_online(memcg) ?
			       div64_u64(scan * fraction[file], denominator) :
			       DIV64_U64_ROUND_UP(scan * fraction[file],
						  denominator);
			break;
		case SCAN_FILE:
		case SCAN_ANON:
			/* Scan one type exclusively */
			if ((scan_balance == SCAN_FILE) != file)
				scan = 0;
			break;
		default:
			/* Look ma, no brain */
			BUG();
		}

		nr[lru] = scan;
	}
}

참고 자료