Writeback을 기다렸는데도 error를 따로 확인해야 하는 이유

I/O가 더는 진행 중이 아니라는 사실과 I/O가 성공했다는 사실은 다릅니다. device가 error로 완료해도 writeback bit를 풀어 waiter를 깨워야 합니다. 따라서 file writeback의 helper들은 제출과 대기를 나누고, 마지막에 별도의 error 상태를 확인합니다.

filemap_flush()와 file_write_and_wait_range()를 이름만 비슷한 동기화 함수로 묶으면 이 구분이 사라집니다. upstream v6.18 구현에서 각각 어떤 계약을 제공하는지 살펴보겠습니다.

flush는 integrity writeback과 다릅니다

filemap_flush()는 mapping 전체에 WB_SYNC_NONE을 사용해 writeback을 시작하는 경로입니다. 이미 다른 I/O가 진행 중인 대상 등을 건너뛸 수 있으며 모든 dirty folio를 반드시 제출하고 기다린다는 계약이 아닙니다. 이름의 flush만 보고 crash 시 보존을 요구하는 API로 해석해서는 안 됩니다.

반대로 filemap_fdatawrite() 계열은 WB_SYNC_ALL로 data-integrity 제출을 요구합니다. 그렇지만 이름에 wait가 없는 이 helper가 새로 제출한 I/O의 완료까지 모두 기다려 준다고 단정할 수 없습니다. callback은 integrity 목적에 맞게 대상을 처리하고, 별도 wait helper가 완료와 error를 맡습니다. v6.18 mm/filemap.c

아래는 구현코드가 아닌 호출관계입니다.

filemap_flush(mapping)
  -> __filemap_fdatawrite(mapping, WB_SYNC_NONE)
  -> __filemap_fdatawrite_range(mapping, 0, LLONG_MAX, WB_SYNC_NONE)

filemap_write_and_wait_range(mapping, start, end)
  -> __filemap_fdatawrite_range(mapping, start, end, WB_SYNC_ALL)
  -> __filemap_fdatawait_range()
  -> filemap_check_errors()

file_write_and_wait_range(file, start, end)
  -> integrity data writeback and wait
  -> file_check_and_advance_wb_err()

byte 범위는 inclusive end를 사용합니다

__filemap_fdatawrite_range()는 writeback_control을 만들고 nr_to_write = LONG_MAX, 지정한 byte 범위와 sync mode를 전달합니다. 이 interface의 end는 마지막 byte를 포함합니다. [start, start + length) 형태의 사용자 범위를 변환할 때 길이가 0인 경우와 overflow를 별도로 고려해야 합니다.

filemap_fdatawrite_wbc()는 mapping이 writeback 가능한지와 DIRTY mark가 있는지를 검사하고, 필요하면 do_writepages()로 내려갑니다. writeback 가능 여부는 BDI의 능력과 관계된 조건이지 cache page 수를 세는 검사와 같은 뜻이 아닙니다. cache가 있어도 dirty가 없다면 새 제출이 필요하지 않을 수 있습니다.

mapping_needs_writeback()처럼 cache 존재를 빠르게 보는 helper도 있습니다. 이것은 이미 진행 중인 I/O를 기다릴 필요가 있을지 포함한 판단입니다. dirty가 없다는 이유만으로 wait를 생략하면 앞서 제출된 I/O를 놓칠 수 있습니다.

현재 mapping_needs_writeback()의 검사는 정확히 mapping->nrpages입니다. 이것을 mapping이 writeback할 능력이 있는지 확인하는 mapping_can_writeback()과 섞어서는 안 됩니다. 후자는 host inode의 BDI가 BDI_CAP_WRITEBACK을 갖는지 확인합니다. 두 전체 helper는 다음과 같습니다. v6.18 filemap.c · v6.18 backing-dev.h

mapping_needs_writeback_v6_18.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * Complete scope from Linux v6.18 mm/filemap.c.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

/*
 *	linux/mm/filemap.c
 *
 * Copyright (C) 1994-1999  Linus Torvalds
 */
/*
 * This file handles the generic file mmap semantics used by
 * most "normal" filesystems (but you don't /have/ to use this:
 * the NFS filesystem used to do this differently, for example)
 */

static bool mapping_needs_writeback(struct address_space *mapping)
{
	return mapping->nrpages;
}

mapping_can_writeback_v6_18.c

// SPDX-License-Identifier: GPL-2.0
/*
 * Complete scope from Linux v6.18 include/linux/backing-dev.h.
 * Source: https://github.com/torvalds/linux/blob/v6.18/include/linux/backing-dev.h
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

/*
 * include/linux/backing-dev.h
 *
 * low-level device information and state which is propagated up through
 * to high-level code.
 */

static inline bool mapping_can_writeback(struct address_space *mapping)
{
	return inode_to_bdi(mapping->host)->capabilities & BDI_CAP_WRITEBACK;
}

제출 쪽의 전체 함수도 구분해서 볼 수 있습니다. filemap_flush()와 filemap_fdatawrite()는 같은 내부 helper에 서로 다른 sync mode를 넘깁니다. __filemap_fdatawrite()는 전체 범위를, __filemap_fdatawrite_range()는 caller의 범위로 만든 control을 전달합니다. 모든 경우에 새 I/O의 완료 wait까지 여기서 추가하는 것은 아닙니다.

filemap_flush_v6_18.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * Complete scope from Linux v6.18 mm/filemap.c.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

/*
 *	linux/mm/filemap.c
 *
 * Copyright (C) 1994-1999  Linus Torvalds
 */
/*
 * This file handles the generic file mmap semantics used by
 * most "normal" filesystems (but you don't /have/ to use this:
 * the NFS filesystem used to do this differently, for example)
 */

int filemap_flush(struct address_space *mapping)
{
	return __filemap_fdatawrite(mapping, WB_SYNC_NONE);
}

filemap_fdatawrite_v6_18.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * Complete scope from Linux v6.18 mm/filemap.c.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

/*
 *	linux/mm/filemap.c
 *
 * Copyright (C) 1994-1999  Linus Torvalds
 */
/*
 * This file handles the generic file mmap semantics used by
 * most "normal" filesystems (but you don't /have/ to use this:
 * the NFS filesystem used to do this differently, for example)
 */

int filemap_fdatawrite(struct address_space *mapping)
{
	return __filemap_fdatawrite(mapping, WB_SYNC_ALL);
}

__filemap_fdatawrite_v6_18.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * Complete scope from Linux v6.18 mm/filemap.c.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

/*
 *	linux/mm/filemap.c
 *
 * Copyright (C) 1994-1999  Linus Torvalds
 */
/*
 * This file handles the generic file mmap semantics used by
 * most "normal" filesystems (but you don't /have/ to use this:
 * the NFS filesystem used to do this differently, for example)
 */

static inline int __filemap_fdatawrite(struct address_space *mapping,
	int sync_mode)
{
	return __filemap_fdatawrite_range(mapping, 0, LLONG_MAX, sync_mode);
}

__filemap_fdatawrite_range_v6_18.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * Complete scope from Linux v6.18 mm/filemap.c.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

/*
 *	linux/mm/filemap.c
 *
 * Copyright (C) 1994-1999  Linus Torvalds
 */
/*
 * This file handles the generic file mmap semantics used by
 * most "normal" filesystems (but you don't /have/ to use this:
 * the NFS filesystem used to do this differently, for example)
 */

int __filemap_fdatawrite_range(struct address_space *mapping, loff_t start,
				loff_t end, int sync_mode)
{
	struct writeback_control wbc = {
		.sync_mode = sync_mode,
		.nr_to_write = LONG_MAX,
		.range_start = start,
		.range_end = end,
	};

	return filemap_fdatawrite_wbc(mapping, &wbc);
}

filemap_fdatawrite_wbc()는 writeback 능력과 DIRTY mark가 없으면 0으로 일찍 반환합니다. 그 뒤 cgroup accounting용 연결을 준비하고 do_writepages()의 결과를 받은 후 연결을 해제합니다. page 수 검사로 early return한다고 설명하는 것은 이 함수의 조건과 다릅니다.

filemap_fdatawrite_wbc_v6_18.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * Complete scope from Linux v6.18 mm/filemap.c.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

/*
 *	linux/mm/filemap.c
 *
 * Copyright (C) 1994-1999  Linus Torvalds
 */
/*
 * This file handles the generic file mmap semantics used by
 * most "normal" filesystems (but you don't /have/ to use this:
 * the NFS filesystem used to do this differently, for example)
 */

int filemap_fdatawrite_wbc(struct address_space *mapping,
			   struct writeback_control *wbc)
{
	int ret;

	if (!mapping_can_writeback(mapping) ||
	    !mapping_tagged(mapping, PAGECACHE_TAG_DIRTY))
		return 0;

	wbc_attach_fdatawrite_inode(wbc, mapping->host);
	ret = do_writepages(mapping, wbc);
	wbc_detach_inode(wbc);
	return ret;
}

WRITEBACK mark를 찾아서 기다립니다

__filemap_fdatawait_range()는 byte 범위를 page index로 바꾼 뒤 PAGECACHE_TAG_WRITEBACK으로 folio batch를 찾습니다. 각 folio의 folio_wait_writeback()을 호출하고 batch 참조를 놓은 다음 필요하면 reschedule합니다.

모든 cached folio를 잠그는 함수는 아닙니다. waiting 대상은 진행 중인 writeback이며, 대기가 끝났다고 caller가 folio lock을 획득하지도 않습니다. batch를 release한다는 것도 각 folio를 반드시 물리적으로 해제한다는 뜻이 아니라 조회 참조를 반환하는 것입니다.

오래된 pagevec loop에는 각 page의 PG_error를 지우는 코드가 보일 수 있습니다. v6.18의 이 wait loop는 folio batch와 writeback 대기를 사용하며 같은 per-page error 정리 코드를 전제로 하지 않습니다. error의 최종 보고는 mapping 또는 file의 별도 경로를 확인해야 합니다.

아래는 v6.18의 내부 wait와 두 wrapper 전체입니다. filemap_fdatawait()는 전체 범위를 지정하며, filemap_fdatawait_range()가 대기 뒤 mapping error를 확인하고 반환합니다. 내부 helper는 오류를 직접 반환하는 함수가 아닙니다.

__filemap_fdatawait_range_v6_18.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * Complete scope from Linux v6.18 mm/filemap.c.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

/*
 *	linux/mm/filemap.c
 *
 * Copyright (C) 1994-1999  Linus Torvalds
 */
/*
 * This file handles the generic file mmap semantics used by
 * most "normal" filesystems (but you don't /have/ to use this:
 * the NFS filesystem used to do this differently, for example)
 */

static void __filemap_fdatawait_range(struct address_space *mapping,
				     loff_t start_byte, loff_t end_byte)
{
	pgoff_t index = start_byte >> PAGE_SHIFT;
	pgoff_t end = end_byte >> PAGE_SHIFT;
	struct folio_batch fbatch;
	unsigned nr_folios;

	folio_batch_init(&fbatch);

	while (index <= end) {
		unsigned i;

		nr_folios = filemap_get_folios_tag(mapping, &index, end,
				PAGECACHE_TAG_WRITEBACK, &fbatch);

		if (!nr_folios)
			break;

		for (i = 0; i < nr_folios; i++) {
			struct folio *folio = fbatch.folios[i];

			folio_wait_writeback(folio);
		}
		folio_batch_release(&fbatch);
		cond_resched();
	}
}

filemap_fdatawait_v6_18.c

// SPDX-License-Identifier: GPL-2.0
/*
 * Complete scope from Linux v6.18 include/linux/pagemap.h.
 * Source: https://github.com/torvalds/linux/blob/v6.18/include/linux/pagemap.h
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

/*
 * Copyright 1995 Linus Torvalds
 */

static inline int filemap_fdatawait(struct address_space *mapping)
{
	return filemap_fdatawait_range(mapping, 0, LLONG_MAX);
}

filemap_fdatawait_range_v6_18.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * Complete scope from Linux v6.18 mm/filemap.c.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

/*
 *	linux/mm/filemap.c
 *
 * Copyright (C) 1994-1999  Linus Torvalds
 */
/*
 * This file handles the generic file mmap semantics used by
 * most "normal" filesystems (but you don't /have/ to use this:
 * the NFS filesystem used to do this differently, for example)
 */

int filemap_fdatawait_range(struct address_space *mapping, loff_t start_byte,
			    loff_t end_byte)
{
	__filemap_fdatawait_range(mapping, start_byte, end_byte);
	return filemap_check_errors(mapping);
}

비교를 위해 v6.0의 내부 wait 전체도 제시합니다. 이 구현에는 end_byte < start_byte guard와 ClearPageError()가 있습니다. pagevec 참조의 release, reschedule, WRITEBACK tag 조회라는 역할은 이어지지만 current code와 token까지 동일한 함수가 아닙니다. 대기 종료를 곧바로 성공적인 disk write로 해석하지 않는 원칙은 두 구현 모두에 적용됩니다.

__filemap_fdatawait_range_v6_0.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * Complete scope from Linux v6.0 mm/filemap.c.
 * Source: https://github.com/torvalds/linux/blob/v6.0/mm/filemap.c
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

/*
 *	linux/mm/filemap.c
 *
 * Copyright (C) 1994-1999  Linus Torvalds
 */
/*
 * This file handles the generic file mmap semantics used by
 * most "normal" filesystems (but you don't /have/ to use this:
 * the NFS filesystem used to do this differently, for example)
 */

static void __filemap_fdatawait_range(struct address_space *mapping,
				     loff_t start_byte, loff_t end_byte)
{
	pgoff_t index = start_byte >> PAGE_SHIFT;
	pgoff_t end = end_byte >> PAGE_SHIFT;
	struct pagevec pvec;
	int nr_pages;

	if (end_byte < start_byte)
		return;

	pagevec_init(&pvec);
	while (index <= end) {
		unsigned i;

		nr_pages = pagevec_lookup_range_tag(&pvec, mapping, &index,
				end, PAGECACHE_TAG_WRITEBACK);
		if (!nr_pages)
			break;

		for (i = 0; i < nr_pages; i++) {
			struct page *page = pvec.pages[i];

			wait_on_page_writeback(page);
			ClearPageError(page);
		}
		pagevec_release(&pvec);
		cond_resched();
	}
}

일부 error가 나도 이미 제출한 I/O는 남습니다

filemap_write_and_wait_range()는 제출에서 -ENOSPC 같은 error가 나도 일부 I/O는 시작되었을 수 있다고 보고 대기를 수행합니다. 곧바로 반환하면 caller가 버려도 된다고 판단한 data를 device가 아직 사용하고 있을 수 있습니다.

-EIO는 이 경로에서 별도로 취급하여 후속 대기를 피합니다. 심각한 제출 실패를 나타낼 수 있다는 구현상의 선택입니다. 이를 모든 함수에 적용되는 일반적인 EIO 규칙으로 확장해서는 안 됩니다.

마지막에는 mapping error를 확인하며, 직접 제출 과정의 error가 이미 있으면 그 값을 우선 보존합니다. 직접 호출의 성공과 비동기 완료에서 기록된 error를 합치는 단계가 따로 있는 것입니다.

다음은 v6.18 filemap_write_and_wait()와 range 함수 전체입니다. 현재 range 함수는 lend < lstart이면 먼저 0을 반환하며, 이 early return에서는 mapping error를 읽고 지우지도 않습니다. 유효한 inclusive 범위에 대한 후속 처리와 invalid range의 동작을 같게 설명해서는 안 됩니다.

filemap_write_and_wait_v6_18.c

// SPDX-License-Identifier: GPL-2.0
/*
 * Complete scope from Linux v6.18 include/linux/pagemap.h.
 * Source: https://github.com/torvalds/linux/blob/v6.18/include/linux/pagemap.h
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

/*
 * Copyright 1995 Linus Torvalds
 */

static inline int filemap_write_and_wait(struct address_space *mapping)
{
	return filemap_write_and_wait_range(mapping, 0, LLONG_MAX);
}

filemap_write_and_wait_range_v6_18.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * Complete scope from Linux v6.18 mm/filemap.c.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

/*
 *	linux/mm/filemap.c
 *
 * Copyright (C) 1994-1999  Linus Torvalds
 */
/*
 * This file handles the generic file mmap semantics used by
 * most "normal" filesystems (but you don't /have/ to use this:
 * the NFS filesystem used to do this differently, for example)
 */

int filemap_write_and_wait_range(struct address_space *mapping,
				 loff_t lstart, loff_t lend)
{
	int err = 0, err2;

	if (lend < lstart)
		return 0;

	if (mapping_needs_writeback(mapping)) {
		err = __filemap_fdatawrite_range(mapping, lstart, lend,
						 WB_SYNC_ALL);
		/*
		 * Even if the above returned error, the pages may be
		 * written partially (e.g. -ENOSPC), so we wait for it.
		 * But the -EIO is special case, it may indicate the worst
		 * thing (e.g. bug) happened, so we avoid waiting for it.
		 */
		if (err != -EIO)
			__filemap_fdatawait_range(mapping, lstart, lend);
	}
	err2 = filemap_check_errors(mapping);
	if (!err)
		err = err2;
	return err;
}

mapping 단위의 filemap_check_errors()는 AS_ENOSPC와 AS_EIO를 test-and-clear합니다. 둘 다 관찰되면 EIO가 우선합니다. 여러 caller가 이 공유 flag를 소비할 수 있으므로 이것은 file마다 독립된 error 이력을 보장하는 경로가 아닙니다.

filemap_check_errors_v6_18.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * Complete scope from Linux v6.18 mm/filemap.c.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

/*
 *	linux/mm/filemap.c
 *
 * Copyright (C) 1994-1999  Linus Torvalds
 */
/*
 * This file handles the generic file mmap semantics used by
 * most "normal" filesystems (but you don't /have/ to use this:
 * the NFS filesystem used to do this differently, for example)
 */

int filemap_check_errors(struct address_space *mapping)
{
	int ret = 0;
	/* Check for outstanding write errors */
	if (test_bit(AS_ENOSPC, &mapping->flags) &&
	    test_and_clear_bit(AS_ENOSPC, &mapping->flags))
		ret = -ENOSPC;
	if (test_bit(AS_EIO, &mapping->flags) &&
	    test_and_clear_bit(AS_EIO, &mapping->flags))
		ret = -EIO;
	return ret;
}

file별 cursor는 error의 관찰 위치를 기억합니다

여러 열린 file이 같은 inode와 mapping을 공유할 수 있습니다. 한 caller가 mapping error를 읽고 지웠다는 이유로 다른 writer가 관련 error를 영원히 놓쳐서는 안 됩니다. file별 interface는 file->f_wb_err cursor와 mapping의 error sequence를 사용하여 이전 관찰 이후의 writeback error를 확인하고 관찰 위치를 진전시킵니다.

이것은 failed I/O 각각을 무한히 보존한 event log는 아닙니다. error가 어떤 범위의 마지막 write와 정확히 일대일 대응한다는 보장도 아닙니다. background writeback의 오류가 나중에 fsync()에서 보고될 수 있으므로 application은 sync 반환값을 반드시 다루어야 합니다.

아래는 v6.18의 file별 제출·대기와 error cursor 처리 전체입니다. file_write_and_wait_range()도 invalid range를 먼저 반환하며, 정상 범위에서는 새로 제출할 cache page가 없더라도 error cursor를 확인할 수 있습니다. 제출의 errno가 먼저 있으면 그 값을 유지하되 cursor 관찰 자체는 수행합니다.

file_write_and_wait_range_v6_18.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * Complete scope from Linux v6.18 mm/filemap.c.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

/*
 *	linux/mm/filemap.c
 *
 * Copyright (C) 1994-1999  Linus Torvalds
 */
/*
 * This file handles the generic file mmap semantics used by
 * most "normal" filesystems (but you don't /have/ to use this:
 * the NFS filesystem used to do this differently, for example)
 */

int file_write_and_wait_range(struct file *file, loff_t lstart, loff_t lend)
{
	int err = 0, err2;
	struct address_space *mapping = file->f_mapping;

	if (lend < lstart)
		return 0;

	if (mapping_needs_writeback(mapping)) {
		err = __filemap_fdatawrite_range(mapping, lstart, lend,
						 WB_SYNC_ALL);
		/* See comment of filemap_write_and_wait() */
		if (err != -EIO)
			__filemap_fdatawait_range(mapping, lstart, lend);
	}
	err2 = file_check_and_advance_wb_err(file);
	if (!err)
		err = err2;
	return err;
}

file_check_and_advance_wb_err_v6_18.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * Complete scope from Linux v6.18 mm/filemap.c.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

/*
 *	linux/mm/filemap.c
 *
 * Copyright (C) 1994-1999  Linus Torvalds
 */
/*
 * This file handles the generic file mmap semantics used by
 * most "normal" filesystems (but you don't /have/ to use this:
 * the NFS filesystem used to do this differently, for example)
 */

int file_check_and_advance_wb_err(struct file *file)
{
	int err = 0;
	errseq_t old = READ_ONCE(file->f_wb_err);
	struct address_space *mapping = file->f_mapping;

	/* Locklessly handle the common case where nothing has changed */
	if (errseq_check(&mapping->wb_err, old)) {
		/* Something changed, must use slow path */
		spin_lock(&file->f_lock);
		old = file->f_wb_err;
		err = errseq_check_and_advance(&mapping->wb_err,
						&file->f_wb_err);
		trace_file_check_and_advance_wb_err(file, old);
		spin_unlock(&file->f_lock);
	}

	/*
	 * We're mostly using this function as a drop in replacement for
	 * filemap_check_errors. Clear AS_EIO/AS_ENOSPC to emulate the effect
	 * that the legacy code would have had on these flags.
	 */
	clear_bit(AS_EIO, &mapping->flags);
	clear_bit(AS_ENOSPC, &mapping->flags);
	return err;
}

빠른 경로는 READ_ONCE(file->f_wb_err)와 mapping의 sequence를 비교합니다. 바뀐 오류가 있으면 file->f_lock 아래에서 현재 cursor를 다시 읽고 진전시킵니다. 같은 open file description을 공유하는 dup()된 fd는 같은 struct file과 cursor를 공유하므로 fd 숫자마다 독립적이라고 생각하면 안 됩니다. 마지막에는 legacy flag도 지워 예전 mapping helper의 효과와 맞춥니다. dup(2)

시스템·filesystem 차원의 flusher처럼 error를 직접 소비해 보고할 책임이 없는 caller를 위해서는 keep-errors helper도 있습니다. 기다리는 동작이 같아 보여도 error를 지우는지, 보존하는지, file cursor를 진전시키는지에 따라 선택이 달라집니다.

filemap_fdatawait_keep_errors()와 range variant는 내부 wait 뒤 filemap_check_and_keep_errors()를 사용합니다. 아래 세 전체 함수는 공유 flag의 error를 검사하지만 지우지 않습니다. superblock sync가 이 helper를 쓰는 이유와 inode 참조를 잡고 기다리는 순서는 superblock sync의 두 wait와 연결됩니다.

filemap_fdatawait_keep_errors_v6_18.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * Complete scope from Linux v6.18 mm/filemap.c.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

/*
 *	linux/mm/filemap.c
 *
 * Copyright (C) 1994-1999  Linus Torvalds
 */
/*
 * This file handles the generic file mmap semantics used by
 * most "normal" filesystems (but you don't /have/ to use this:
 * the NFS filesystem used to do this differently, for example)
 */

int filemap_fdatawait_keep_errors(struct address_space *mapping)
{
	__filemap_fdatawait_range(mapping, 0, LLONG_MAX);
	return filemap_check_and_keep_errors(mapping);
}

filemap_fdatawait_range_keep_errors_v6_18.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * Complete scope from Linux v6.18 mm/filemap.c.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

/*
 *	linux/mm/filemap.c
 *
 * Copyright (C) 1994-1999  Linus Torvalds
 */
/*
 * This file handles the generic file mmap semantics used by
 * most "normal" filesystems (but you don't /have/ to use this:
 * the NFS filesystem used to do this differently, for example)
 */

int filemap_fdatawait_range_keep_errors(struct address_space *mapping,
		loff_t start_byte, loff_t end_byte)
{
	__filemap_fdatawait_range(mapping, start_byte, end_byte);
	return filemap_check_and_keep_errors(mapping);
}

filemap_check_and_keep_errors_v6_18.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * Complete scope from Linux v6.18 mm/filemap.c.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
 * Kernel excerpt; not a standalone program.
 * The book exact source tag is not asserted.
 */

/*
 *	linux/mm/filemap.c
 *
 * Copyright (C) 1994-1999  Linus Torvalds
 */
/*
 * This file handles the generic file mmap semantics used by
 * most "normal" filesystems (but you don't /have/ to use this:
 * the NFS filesystem used to do this differently, for example)
 */

static int filemap_check_and_keep_errors(struct address_space *mapping)
{
	/* Check for outstanding write errors */
	if (test_bit(AS_EIO, &mapping->flags))
		return -EIO;
	if (test_bit(AS_ENOSPC, &mapping->flags))
		return -ENOSPC;
	return 0;
}

data helper가 성공했다고 filesystem metadata와 device flush까지 모두 끝났다는 뜻은 아닙니다. 최종 fsync callback은 필요한 순서를 조합합니다. 제출, 대기, error 관찰, durability의 범위를 나누어 읽어야 각 helper의 성공값을 정확히 해석할 수 있습니다.

같은 이유로 sync_file_range()를 fsync의 대체품으로 삼지 않습니다. 이 API는 metadata와 volatile device write cache flush를 보장하지 않습니다. 범위의 data writeback을 제어하는 목적과 crash 후 data 복구를 보장하는 목적은 다릅니다. sync_file_range(2)

포함한 kernel 함수들은 표시한 tag의 전체 scope이며 주변 kernel 선언을 전제로 합니다. source의 GPL-2.0 license를 함께 확인할 수 있습니다. 이 글에서는 kernel compile, I/O 실패 주입이나 device 실측을 수행하지 않았습니다.

참고 자료

  • Lorenzo Stoakes, The Linux Memory Manager, Early Access 2025, Chapter 10, File Writeback.
  • Linux v6.18 mm/filemap.c: filemap 제출·대기·error helper 전체 구현.
  • Linux v6.18 fs/libfs.c: generic fsync에서의 data helper 사용과 후속 처리.
  • fsync(2): 비동기 writeback error의 사용자 보고.