Dirty throttling의 반환 조건을 따라가면 writer가 잠드는 이유가 보입니다

writeback이 느리면 writer가 잠든다는 설명만으로는 같은 device에 쓰는 두 task의 동작 차이를 설명하기 어렵습니다. 어떤 호출은 통계만 보고 돌아가고, 어떤 호출은 한 번 자며, 어떤 호출은 다시 통계를 읽는 loop에 남습니다. 그 차이는 이미 만든 page 수뿐 아니라 domain 상태, task의 virtual 시각, wb의 잔량과 caller가 대기할 수 있는지에 달려 있습니다.

여기서는 upstream v6.18의 balance_dirty_pages()를 중심으로 그 반환 경로를 연결합니다. pages_dirtied는 task가 최근 accounting 이후 만든 base page 수이며, 이 함수 안에서 같은 byte를 다시 disk로 복사하는 양이 아닙니다. 다음은 구현코드가 아닌 호출관계입니다.

balance_dirty_pages_ratelimited_flags()
  balance_dirty_pages()
    balance_domain_limits()
    balance_wb_limits()
    __wb_update_bandwidth()
    wb_max_pause(), wb_min_pause()
    io_schedule_timeout()

먼저 domain의 dirty와 available을 맞춥니다

계산 상태는 global domain용 gdtc와 유효할 때만 쓰는 memcg domain용 mdtc로 나뉩니다. v6.18은 loop 시작에서 balance_domain_limits()로 available량, dirty량, threshold, freerun 상태를 준비합니다. 원래 wb가 memcg 소속이어도 전역 조건을 없애는 것이 아닙니다. 최초 freerun 반환은 global과 유효한 memcg domain이 모두 freerun일 때 가능합니다. v6.18 domain preparation

balance_domain_limits_v6_18.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * mm/page-writeback.c
 *
 * Copyright (C) 2002, Linus Torvalds.
 * Copyright (C) 2007 Red Hat, Inc., Peter Zijlstra
 *
 * Contains functions related to writing back dirty pages at the
 * address_space level.
 *
 * 10Apr2002	Andrew Morton
 *		Initial version
 */

/*
 * Complete scope from Linux v6.18 mm/page-writeback.c.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/page-writeback.c
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

static void balance_domain_limits(struct dirty_throttle_control *dtc,
				  bool strictlimit)
{
	domain_dirty_avail(dtc, true);
	domain_dirty_limits(dtc);
	domain_dirty_freerun(dtc, strictlimit);
}

domain_dirty_avail()의 두 번째 인자는 진행 중 writeback을 dirty량에 포함할지 정합니다. task throttling은 true를 사용하여 global NR_FILE_DIRTY + NR_WRITEBACK을 셉니다. memcg는 자체 file page, headroom, dirty·writeback 통계를 사용합니다. 이미 writeback인 page를 빼는 background 제출 판단과 같은 값을 재사용하는 것은 아닙니다. v6.18 domain accounting

domain_dirty_avail_v6_18.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * mm/page-writeback.c
 *
 * Copyright (C) 2002, Linus Torvalds.
 * Copyright (C) 2007 Red Hat, Inc., Peter Zijlstra
 *
 * Contains functions related to writing back dirty pages at the
 * address_space level.
 *
 * 10Apr2002	Andrew Morton
 *		Initial version
 */

/*
 * Complete scope from Linux v6.18 mm/page-writeback.c.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/page-writeback.c
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

static void domain_dirty_avail(struct dirty_throttle_control *dtc,
			       bool include_writeback)
{
	if (dtc_is_global(dtc)) {
		dtc->avail = global_dirtyable_memory();
		dtc->dirty = global_node_page_state(NR_FILE_DIRTY);
		if (include_writeback)
			dtc->dirty += global_node_page_state(NR_WRITEBACK);
	} else {
		unsigned long filepages = 0, headroom = 0, writeback = 0;

		mem_cgroup_wb_stats(dtc->wb, &filepages, &headroom, &dtc->dirty,
				    &writeback);
		if (include_writeback)
			dtc->dirty += writeback;
		mdtc_calc_avail(dtc, filepages, headroom);
	}
}

mdtc_calc_avail_v6_18.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * mm/page-writeback.c
 *
 * Copyright (C) 2002, Linus Torvalds.
 * Copyright (C) 2007 Red Hat, Inc., Peter Zijlstra
 *
 * Contains functions related to writing back dirty pages at the
 * address_space level.
 *
 * 10Apr2002	Andrew Morton
 *		Initial version
 */

/*
 * Complete scope from Linux v6.18 mm/page-writeback.c.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/page-writeback.c
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

static void mdtc_calc_avail(struct dirty_throttle_control *mdtc,
			    unsigned long filepages, unsigned long headroom)
{
	struct dirty_throttle_control *gdtc = mdtc_gdtc(mdtc);
	unsigned long clean = filepages - min(filepages, mdtc->dirty);
	unsigned long global_clean = gdtc->avail - min(gdtc->avail, gdtc->dirty);
	unsigned long other_clean = global_clean - min(global_clean, clean);

	mdtc->avail = filepages + min(headroom, other_clean);
}

memcg available 계산은 file page 수에 headroom을 무조건 전부 더하지 않습니다. global clean량에서 다른 memcg가 차지한 clean량을 고려한 여유와 headroom 중 작은 값을 더합니다. 차감마다 min()을 두어 unsigned underflow를 피합니다. 이 준비가 있어야 domain별 byte·ratio 변환이 같은 단위를 유지합니다.

freerun에도 strictlimit와 task 예외가 있습니다

일반 domain freerun은 domain dirty량과 background·dirty threshold의 중간을 비교합니다. strictlimit BDI이면 같은 자리에 wb dirty량과 wb threshold를 놓습니다. 따라서 global memory가 여유롭다는 사실만으로 작은 strictlimit wb의 task를 항상 통과시키지는 않습니다. 비교는 이 단계에서는 <=입니다. v6.18 domain_dirty_freerun

domain_dirty_freerun_v6_18.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * mm/page-writeback.c
 *
 * Copyright (C) 2002, Linus Torvalds.
 * Copyright (C) 2007 Red Hat, Inc., Peter Zijlstra
 *
 * Contains functions related to writing back dirty pages at the
 * address_space level.
 *
 * 10Apr2002	Andrew Morton
 *		Initial version
 */

/*
 * Complete scope from Linux v6.18 mm/page-writeback.c.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/page-writeback.c
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

static void domain_dirty_freerun(struct dirty_throttle_control *dtc,
				 bool strictlimit)
{
	unsigned long dirty, thresh, bg_thresh;

	if (unlikely(strictlimit)) {
		wb_dirty_limits(dtc);
		dirty = dtc->wb_dirty;
		thresh = dtc->wb_thresh;
		bg_thresh = dtc->wb_bg_thresh;
	} else {
		dirty = dtc->dirty;
		thresh = dtc->thresh;
		bg_thresh = dtc->bg_thresh;
	}
	dtc->freerun = dirty <= dirty_freerun_ceiling(thresh, bg_thresh);
}

freerun으로 반환하면 dirty_paused_when=now, nr_dirtied=0으로 옮기고 다음 점검 page 수를 정합니다. global과 memcg poll interval 중 작은 값을 사용합니다. strictlimit이면 poll interval의 입력도 wb 수치입니다. 시각을 갱신했다는 사실을 실제로 sleep했다는 증거로 읽어서는 안 됩니다.

domain_poll_intv_v6_18.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * mm/page-writeback.c
 *
 * Copyright (C) 2002, Linus Torvalds.
 * Copyright (C) 2007 Red Hat, Inc., Peter Zijlstra
 *
 * Contains functions related to writing back dirty pages at the
 * address_space level.
 *
 * 10Apr2002	Andrew Morton
 *		Initial version
 */

/*
 * Complete scope from Linux v6.18 mm/page-writeback.c.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/page-writeback.c
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

static unsigned long domain_poll_intv(struct dirty_throttle_control *dtc,
				      bool strictlimit)
{
	unsigned long dirty, thresh;

	if (strictlimit) {
		dirty = dtc->wb_dirty;
		thresh = dtc->wb_thresh;
	} else {
		dirty = dtc->dirty;
		thresh = dtc->thresh;
	}

	return dirty_poll_interval(dirty, thresh);
}

최초 domain freerun에서 통과하지 못했더라도 PF_LOCAL_THROTTLE task에는 별도 경로가 있습니다. strictlimit가 아닐 때 wb dirty량이 wb freerun ceiling보다 작으면 wb_dirty_freerun()이 freerun으로 돌려보낼 수 있습니다. 이 비교는 <입니다. 이 예외를 일반 application writer 전체에 적용해서는 안 됩니다. v6.18 local-throttle exception

wb_dirty_freerun_v6_18.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * mm/page-writeback.c
 *
 * Copyright (C) 2002, Linus Torvalds.
 * Copyright (C) 2007 Red Hat, Inc., Peter Zijlstra
 *
 * Contains functions related to writing back dirty pages at the
 * address_space level.
 *
 * 10Apr2002	Andrew Morton
 *		Initial version
 */

/*
 * Complete scope from Linux v6.18 mm/page-writeback.c.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/page-writeback.c
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

static void wb_dirty_freerun(struct dirty_throttle_control *dtc,
			     bool strictlimit)
{
	dtc->freerun = false;

	/* was already handled in domain_dirty_freerun */
	if (strictlimit)
		return;

	wb_dirty_limits(dtc);
	/*
	 * LOCAL_THROTTLE tasks must not be throttled when below the per-wb
	 * freerun ceiling.
	 */
	if (!(current->flags & PF_LOCAL_THROTTLE))
		return;

	dtc->freerun = dtc->wb_dirty <
		       dirty_freerun_ceiling(dtc->wb_thresh, dtc->wb_bg_thresh);
}

balance_wb_limits_v6_18.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * mm/page-writeback.c
 *
 * Copyright (C) 2002, Linus Torvalds.
 * Copyright (C) 2007 Red Hat, Inc., Peter Zijlstra
 *
 * Contains functions related to writing back dirty pages at the
 * address_space level.
 *
 * 10Apr2002	Andrew Morton
 *		Initial version
 */

/*
 * Complete scope from Linux v6.18 mm/page-writeback.c.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/page-writeback.c
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

static void balance_wb_limits(struct dirty_throttle_control *dtc,
			      bool strictlimit)
{
	wb_dirty_freerun(dtc, strictlimit);
	if (dtc->freerun)
		return;

	wb_dirty_exceeded(dtc, strictlimit);
	wb_position_ratio(dtc);
}

pause 전에 worker와 rate를 준비합니다

일반 mode에서는 global의 아직 제출하지 않은 dirty량이 background threshold를 넘고 해당 wb의 writeback이 진행 중이 아닐 때 worker를 깨웁니다. laptop mode는 이 조기 시작을 미룹니다. 그러나 freerun을 벗어난 경로에서는 laptop mode여도 필요하면 background writeback을 시작합니다. laptop mode가 모든 writeback을 금지하는 설정은 아닙니다.

wb 한도를 계산한 뒤 dirty_exceeded는 wb dirty가 자기 threshold보다 크고, 동시에 domain dirty가 domain threshold보다 크거나 strictlimit일 때 켜집니다. v6.18은 global·memcg 결과를 OR하여 wb에 저장합니다. 이 값은 다음 rate-limited 진입의 검사 간격에도 영향을 줍니다.

wb_dirty_exceeded_v6_18.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * mm/page-writeback.c
 *
 * Copyright (C) 2002, Linus Torvalds.
 * Copyright (C) 2007 Red Hat, Inc., Peter Zijlstra
 *
 * Contains functions related to writing back dirty pages at the
 * address_space level.
 *
 * 10Apr2002	Andrew Morton
 *		Initial version
 */

/*
 * Complete scope from Linux v6.18 mm/page-writeback.c.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/page-writeback.c
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

static inline void wb_dirty_exceeded(struct dirty_throttle_control *dtc,
				     bool strictlimit)
{
	dtc->dirty_exceeded = (dtc->wb_dirty > dtc->wb_thresh) &&
		((dtc->dirty > dtc->thresh) || strictlimit);
}

일반적인 제어 경로에서는 global과 memcg 중 더 작은 pos_ratio를 선택합니다. 다만 앞서 설명한 local-throttle freerun 예외는 그 선택보다 먼저 반환할 수 있습니다. 그리고 bandwidth 갱신 조건은 bw_time_stamp + BANDWIDTH_INTERVAL이 현재보다 이전인지입니다. 아직 다음 갱신 시각에 도달하지 않았을 때 갱신한다는 뜻이 아닙니다. 만료된 간격에서 __wb_update_bandwidth(..., true)를 호출하여 rate 계산도 수행합니다.

경과 시간은 virtual accounting에 반영합니다

선택한 ratio를 base rate에 곱한 task rate가 0이 아니면 HZ * pages_dirtied / task_ratelimit가 period입니다. 여기에 이전 accounting 이후 경과 시간을 빼서 pause를 구합니다. 0이면 나눗셈을 하지 않고 max_pause로 내려갑니다. 0인 이유를 해당 task 하나가 지나치게 빠르다는 사실로 단정할 수는 없습니다. 다른 wb의 dirty가 global 한도를 압박하거나 정수 연산 결과가 0이 되는 조건도 관련됩니다.

pause가 min_pause보다 작으면 세 가지 보정을 거쳐 sleep 없이 반환합니다. 1초보다 더 큰 음수 여유이면 accounting을 now로 재설정합니다. 그렇지 않고 period가 남으면 virtual 시각에 period만 더합니다. period마저 0이면 필요할 때 다음 점검량을 늘립니다. 반대로 pause가 max_pause보다 크면 한 번의 sleep 요청을 max_pause로 자르고, now도 초과분 중 max_pause 이하만큼 보정합니다.

실제로 대기할 때 v6.18은 TASK_KILLABLE을 설정하고 bdi->last_bdp_sleep에 실제 현재 jiffies를 기록한 뒤 io_schedule_timeout()을 호출합니다. 오래된 설명에서 쓰던 wb->dirty_sleep 필드와 섞지 않아야 합니다. 잠든 뒤 task의 virtual 시각은 계산한 now와 pause를 합친 값이며, 관측한 실제 wakeup 시각 그 자체가 아닙니다.

반복과 반환을 전체 함수에서 확인합니다

BDP_ASYNC이면 pause 지점에서 sleep하지 않고 -EAGAIN으로 반환합니다. 일반 경로는 sleep 뒤 task accounting을 갱신하고 task rate가 0이 아니면 loop를 끝냅니다. 0이면 wb 잔량이 wb_stat_error() 이하인지, fatal signal이 있는지를 보고 빠져나오며 나머지는 재평가합니다. 정상 wb까지 전역 pressure 때문에 무한정 막는 상황을 줄이는 조건입니다. signal 확인으로 빠져나왔다고 이 함수가 -EINTR을 만들어 반환하는 것은 아닙니다. 여기서 설정하는 오류는 async pause의 -EAGAIN이며 나머지 정상 반환은 0입니다.

아래는 trace와 cgroup·flags 분기를 생략하지 않은 v6.18의 전체 함수입니다. kernel 내부 helper와 task 상태가 필요한 정의로, standalone 프로그램은 아닙니다. 함수의 GPL-2.0-only 고지와 원래 attribution을 보존했습니다. 책의 정확한 source tag를 이 버전이라고 단정하지 않습니다. v6.18 complete balance_dirty_pages

balance_dirty_pages_v6_18.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * mm/page-writeback.c
 *
 * Copyright (C) 2002, Linus Torvalds.
 * Copyright (C) 2007 Red Hat, Inc., Peter Zijlstra
 *
 * Contains functions related to writing back dirty pages at the
 * address_space level.
 *
 * 10Apr2002	Andrew Morton
 *		Initial version
 */

/*
 * Complete scope from Linux v6.18 mm/page-writeback.c.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/page-writeback.c
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

static int balance_dirty_pages(struct bdi_writeback *wb,
			       unsigned long pages_dirtied, unsigned int flags)
{
	struct dirty_throttle_control gdtc_stor = { GDTC_INIT(wb) };
	struct dirty_throttle_control mdtc_stor = { MDTC_INIT(wb, &gdtc_stor) };
	struct dirty_throttle_control * const gdtc = &gdtc_stor;
	struct dirty_throttle_control * const mdtc = mdtc_valid(&mdtc_stor) ?
						     &mdtc_stor : NULL;
	struct dirty_throttle_control *sdtc;
	unsigned long nr_dirty;
	long period;
	long pause;
	long max_pause;
	long min_pause;
	int nr_dirtied_pause;
	unsigned long task_ratelimit;
	unsigned long dirty_ratelimit;
	struct backing_dev_info *bdi = wb->bdi;
	bool strictlimit = bdi->capabilities & BDI_CAP_STRICTLIMIT;
	unsigned long start_time = jiffies;
	int ret = 0;

	for (;;) {
		unsigned long now = jiffies;

		nr_dirty = global_node_page_state(NR_FILE_DIRTY);

		balance_domain_limits(gdtc, strictlimit);
		if (mdtc) {
			/*
			 * If @wb belongs to !root memcg, repeat the same
			 * basic calculations for the memcg domain.
			 */
			balance_domain_limits(mdtc, strictlimit);
		}

		/*
		 * In laptop mode, we wait until hitting the higher threshold
		 * before starting background writeout, and then write out all
		 * the way down to the lower threshold.  So slow writers cause
		 * minimal disk activity.
		 *
		 * In normal mode, we start background writeout at the lower
		 * background_thresh, to keep the amount of dirty memory low.
		 */
		if (!laptop_mode && nr_dirty > gdtc->bg_thresh &&
		    !writeback_in_progress(wb))
			wb_start_background_writeback(wb);

		/*
		 * If memcg domain is in effect, @dirty should be under
		 * both global and memcg freerun ceilings.
		 */
		if (gdtc->freerun && (!mdtc || mdtc->freerun)) {
			unsigned long intv;
			unsigned long m_intv;

free_running:
			intv = domain_poll_intv(gdtc, strictlimit);
			m_intv = ULONG_MAX;

			current->dirty_paused_when = now;
			current->nr_dirtied = 0;
			if (mdtc)
				m_intv = domain_poll_intv(mdtc, strictlimit);
			current->nr_dirtied_pause = min(intv, m_intv);
			break;
		}

		/* Start writeback even when in laptop mode */
		if (unlikely(!writeback_in_progress(wb)))
			wb_start_background_writeback(wb);

		mem_cgroup_flush_foreign(wb);

		/*
		 * Calculate global domain's pos_ratio and select the
		 * global dtc by default.
		 */
		balance_wb_limits(gdtc, strictlimit);
		if (gdtc->freerun)
			goto free_running;
		sdtc = gdtc;

		if (mdtc) {
			/*
			 * If memcg domain is in effect, calculate its
			 * pos_ratio.  @wb should satisfy constraints from
			 * both global and memcg domains.  Choose the one
			 * w/ lower pos_ratio.
			 */
			balance_wb_limits(mdtc, strictlimit);
			if (mdtc->freerun)
				goto free_running;
			if (mdtc->pos_ratio < gdtc->pos_ratio)
				sdtc = mdtc;
		}

		wb->dirty_exceeded = gdtc->dirty_exceeded ||
				     (mdtc && mdtc->dirty_exceeded);
		if (time_is_before_jiffies(READ_ONCE(wb->bw_time_stamp) +
					   BANDWIDTH_INTERVAL))
			__wb_update_bandwidth(gdtc, mdtc, true);

		/* throttle according to the chosen dtc */
		dirty_ratelimit = READ_ONCE(wb->dirty_ratelimit);
		task_ratelimit = ((u64)dirty_ratelimit * sdtc->pos_ratio) >>
							RATELIMIT_CALC_SHIFT;
		max_pause = wb_max_pause(wb, sdtc->wb_dirty);
		min_pause = wb_min_pause(wb, max_pause,
					 task_ratelimit, dirty_ratelimit,
					 &nr_dirtied_pause);

		if (unlikely(task_ratelimit == 0)) {
			period = max_pause;
			pause = max_pause;
			goto pause;
		}
		period = HZ * pages_dirtied / task_ratelimit;
		pause = period;
		if (current->dirty_paused_when)
			pause -= now - current->dirty_paused_when;
		/*
		 * For less than 1s think time (ext3/4 may block the dirtier
		 * for up to 800ms from time to time on 1-HDD; so does xfs,
		 * however at much less frequency), try to compensate it in
		 * future periods by updating the virtual time; otherwise just
		 * do a reset, as it may be a light dirtier.
		 */
		if (pause < min_pause) {
			trace_balance_dirty_pages(wb,
						  sdtc,
						  dirty_ratelimit,
						  task_ratelimit,
						  pages_dirtied,
						  period,
						  min(pause, 0L),
						  start_time);
			if (pause < -HZ) {
				current->dirty_paused_when = now;
				current->nr_dirtied = 0;
			} else if (period) {
				current->dirty_paused_when += period;
				current->nr_dirtied = 0;
			} else if (current->nr_dirtied_pause <= pages_dirtied)
				current->nr_dirtied_pause += pages_dirtied;
			break;
		}
		if (unlikely(pause > max_pause)) {
			/* for occasional dropped task_ratelimit */
			now += min(pause - max_pause, max_pause);
			pause = max_pause;
		}

pause:
		trace_balance_dirty_pages(wb,
					  sdtc,
					  dirty_ratelimit,
					  task_ratelimit,
					  pages_dirtied,
					  period,
					  pause,
					  start_time);
		if (flags & BDP_ASYNC) {
			ret = -EAGAIN;
			break;
		}
		__set_current_state(TASK_KILLABLE);
		bdi->last_bdp_sleep = jiffies;
		io_schedule_timeout(pause);

		current->dirty_paused_when = now + pause;
		current->nr_dirtied = 0;
		current->nr_dirtied_pause = nr_dirtied_pause;

		/*
		 * This is typically equal to (dirty < thresh) and can also
		 * keep "1000+ dd on a slow USB stick" under control.
		 */
		if (task_ratelimit)
			break;

		/*
		 * In the case of an unresponsive NFS server and the NFS dirty
		 * pages exceeds dirty_thresh, give the other good wb's a pipe
		 * to go through, so that tasks on them still remain responsive.
		 *
		 * In theory 1 page is enough to keep the consumer-producer
		 * pipe going: the flusher cleans 1 page => the task dirties 1
		 * more page. However wb_dirty has accounting errors.  So use
		 * the larger and more IO friendly wb_stat_error.
		 */
		if (sdtc->wb_dirty <= wb_stat_error())
			break;

		if (fatal_signal_pending(current))
			break;
	}
	return ret;
}

이 경로는 느린 device에 대한 durability 보장이나 task별 I/O 예약이 아닙니다. dirty 생산자가 메모리 buffering을 무한정 늘리지 못하게 하되, worker가 계속 소비할 수 있도록 task의 다음 진행 시점을 조정하는 제어입니다. 특정 호출의 지연을 설명할 때에는 freerun 반환, 짧은 pause의 accounting 반환, 실제 sleep, 0-rate 재평가를 나누어 보는 것이 정확합니다.

참고 자료

  • Lorenzo Stoakes, The Linux Memory Manager, Early Access 2025, Chapter 10, Core Dirty Throttling 및 관련 dirty limit·pause 설명.
  • Linux v6.18 mm/page-writeback.c: 전체 balancing 함수와 domain·wb helper.
  • Linux v6.18 VM tunables: dirty threshold와 laptop mode interface.