I/O가 끝나기를 기다리는 것과 공유 객체를 잠그는 것은 겉보기에 비슷합니다. 둘 다 잠들었다가 깨어나지만, 돌아온 뒤 할 수 있는 일이 다릅니다. folio의 대기 경로에서는 이 차이가 참조를 언제 놓을 수 있는지까지 결정합니다.
folio_wait_bit_common()은 이러한 대기를 함께 처리합니다. 중요한 구분은 어떤 bit를 기다리는가뿐 아니라, 깨어날 때 그 bit를 획득해야 하는지와 기다리는 동안 folio 참조를 유지하는지입니다. 아래 설명은 upstream v6.18 구현을 기준으로 합니다.
PG_locked는 일반 mutex와 같지는 않습니다
folio_trylock()은 PG_locked를 원자적으로 검사하고 설정하는 빠른 경로입니다. 실패하면 folio_lock() 계열은 대기 경로로 들어갈 수 있습니다. lock을 얻은 쪽은 unlock을 하기 전까지 해당 folio의 관련 변경을 직렬화합니다.
그렇지만 이 lock은 task 소유자를 저장하는 mutex와 동일하지 않습니다. read I/O를 시작한 task가 folio를 잠그고 I/O completion 문맥에서 잠금을 풀 수 있습니다. 모든 folio data 접근을 자동으로 보호하는 범용 lock도 아닙니다. page-table lock이나 mapping의 invalidate lock이 별도로 필요한 이유입니다.
folio_lock_killable()의 실패도 구별해야 합니다. signal 때문에 대기가 중단되어 error가 반환되면 lock을 소유하지 않습니다. 성공한 경로의 cleanup을 그대로 실행하여 unlock하면 다른 사용자의 lock을 풀 위험이 있습니다. folio_wait_locked()는 처음부터 lock 획득이 아니라 잠금이 풀리는 사건을 기다리는 helper입니다. v6.18 mm/filemap.c
여러 folio를 함께 잠글 때에도 순서가 필요합니다. 같은 address_space에서는 index가 작은 것부터, 서로 다른 mapping에서는 memory 주소가 작은 address_space의 folio부터 얻는 것이 이 API의 계약입니다. 문서가 설명하는 write 원자성의 범위도 page 경계를 넘지 않는 write의 직렬화이며, DMA나 mmap store까지 folio lock을 따르는 것은 아닙니다. 다음 세 함수는 v6.18 pagemap.h의 전체 정의입니다.
// SPDX-License-Identifier: GPL-2.0
/*
* Complete definition from Linux v6.18 include/linux/pagemap.h.
* Source: https://github.com/torvalds/linux/blob/v6.18/include/linux/pagemap.h
* Copyright 1995 Linus Torvalds
* Kernel excerpt; not a standalone user-space program.
* The book's exact source tag is not asserted.
*/
static inline bool folio_trylock(struct folio *folio)
{
return likely(!test_and_set_bit_lock(PG_locked, folio_flags(folio, 0)));
}
// SPDX-License-Identifier: GPL-2.0
/*
* Complete definition from Linux v6.18 include/linux/pagemap.h.
* Source: https://github.com/torvalds/linux/blob/v6.18/include/linux/pagemap.h
* Copyright 1995 Linus Torvalds
* Kernel excerpt; not a standalone user-space program.
* The book's exact source tag is not asserted.
*/
static inline void folio_lock(struct folio *folio)
{
might_sleep();
if (!folio_trylock(folio))
__folio_lock(folio);
}
// SPDX-License-Identifier: GPL-2.0
/*
* Complete definition from Linux v6.18 include/linux/pagemap.h.
* Source: https://github.com/torvalds/linux/blob/v6.18/include/linux/pagemap.h
* Copyright 1995 Linus Torvalds
* Kernel excerpt; not a standalone user-space program.
* The book's exact source tag is not asserted.
*/
static inline int folio_lock_killable(struct folio *folio)
{
might_sleep();
if (!folio_trylock(folio))
return __folio_lock_killable(folio);
return 0;
}
빠른 lock 시도에 실패하면 아래 느린 helper가 EXCLUSIVE 대기를 선택합니다. killable helper의 성공은 0이고 fatal signal로 중단되면 -EINTR입니다.
// SPDX-License-Identifier: GPL-2.0-only
/*
* Complete definition from Linux v6.18 mm/filemap.c.
* Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
* Copyright (C) 1994-1999 Linus Torvalds
* Kernel excerpt; not a standalone user-space program.
* The book's exact source tag is not asserted.
*/
void __folio_lock(struct folio *folio)
{
folio_wait_bit_common(folio, PG_locked, TASK_UNINTERRUPTIBLE,
EXCLUSIVE);
}
// SPDX-License-Identifier: GPL-2.0-only
/*
* Complete definition from Linux v6.18 mm/filemap.c.
* Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
* Copyright (C) 1994-1999 Linus Torvalds
* Kernel excerpt; not a standalone user-space program.
* The book's exact source tag is not asserted.
*/
int __folio_lock_killable(struct folio *folio)
{
return folio_wait_bit_common(folio, PG_locked, TASK_KILLABLE,
EXCLUSIVE);
}
세 가지 대기는 참조 계약이 다릅니다
EXCLUSIVE는 참조를 유지하며, 성공할 때 대상 bit도 획득합니다. folio_lock()의 느린 경로가 대표적입니다. 깨어났다는 사실만으로 충분하지 않고 실제로 lock을 얻어야 합니다.
SHARED는 참조를 유지하되 bit를 획득하지 않습니다. PG_writeback이 끝나기를 기다리는 경우가 이에 해당합니다. 여기서 shared는 folio data를 읽는 reader lock을 얻었다는 뜻이 아닙니다. 여러 waiter가 같은 상태의 종료를 관찰할 수 있다는 뜻입니다.
DROP은 waitqueue에 안전하게 등록한 다음 자신의 folio 참조를 놓습니다. folio_put_wait_locked()처럼 지금 가진 객체를 기다린 뒤 상위 lookup부터 다시 시작하려는 경로에 유용합니다. 반환 후 예전 pointer를 그대로 재사용할 수는 없습니다. 기다리는 동안 cache에서 제거되거나 해제될 수 있기 때문입니다.
다음 enum은 v6.18 mm/filemap.c의 전체 선언입니다. DROP은 bit 획득 없는 대기이며, SHARED와 달리 참조까지 넘겨준다는 점이 중요합니다.
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Complete declaration from Linux v6.18 mm/filemap.c.
* Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
* Copyright (C) 1994-1999 Linus Torvalds
* Kernel declaration; not a standalone header.
*/
enum behavior {
EXCLUSIVE, /* Hold ref to page and take the bit when woken, like
* __folio_lock() waiting on then setting PG_locked.
*/
SHARED, /* Hold ref to page and check the bit when woken, like
* folio_wait_writeback() waiting on PG_writeback.
*/
DROP, /* Drop ref to page before wait, no check when woken,
* like folio_put_wait_locked() on PG_locked.
*/
};
실제 wrapper들도 이 구분을 그대로 전달합니다. folio_wait_locked()의 caller는 대기 중 folio가 사라지지 않도록 참조를 유지해야 합니다. folio_put_wait_locked()는 넘겨준 참조를 내부에서 놓습니다. 현재 이 helper는 mm/filemap.c 안의 static 함수입니다.
// SPDX-License-Identifier: GPL-2.0
/*
* Complete definition from Linux v6.18 include/linux/pagemap.h.
* Source: https://github.com/torvalds/linux/blob/v6.18/include/linux/pagemap.h
* Copyright 1995 Linus Torvalds
* Kernel excerpt; not a standalone user-space program.
* The book's exact source tag is not asserted.
*/
static inline void folio_wait_locked(struct folio *folio)
{
if (folio_test_locked(folio))
folio_wait_bit(folio, PG_locked);
}
// SPDX-License-Identifier: GPL-2.0-only
/*
* Complete definition from Linux v6.18 mm/filemap.c.
* Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
* Copyright (C) 1994-1999 Linus Torvalds
* Kernel excerpt; not a standalone user-space program.
* The book's exact source tag is not asserted.
*/
void folio_wait_bit(struct folio *folio, int bit_nr)
{
folio_wait_bit_common(folio, bit_nr, TASK_UNINTERRUPTIBLE, SHARED);
}
// SPDX-License-Identifier: GPL-2.0-only
/*
* Complete definition from Linux v6.18 mm/filemap.c.
* Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
* Copyright (C) 1994-1999 Linus Torvalds
* Kernel excerpt; not a standalone user-space program.
* The book's exact source tag is not asserted.
*/
static int folio_put_wait_locked(struct folio *folio, int state)
{
return folio_wait_bit_common(folio, PG_locked, state, DROP);
}

그림은 각 대기 방식의 참조 유지 여부와 성공 시의 lock 상태만 비교합니다. signal 중단, waitqueue hash 충돌, I/O 오류는 생략했습니다.
이 구분은 대기 helper 이름보다 반환 후 계약을 읽게 해 줍니다. 예를 들어 writeback을 기다렸다고 dirty가 사라졌거나 저장이 성공했다고 단정할 수는 없습니다. I/O 중 다시 수정되어 dirty가 남을 수도 있고, 완료가 error를 기록했을 수도 있습니다.
잠들기 직전의 재검사가 wakeup 유실을 막습니다
folio마다 독립적인 waitqueue head를 무조건 두는 대신 구현은 hash된 waitqueue를 사용합니다. waiter는 folio 주소와 bit 번호를 함께 가지고 있으므로 같은 queue에 있다고 모두 같은 사건을 기다리는 것은 아닙니다. wake 함수는 이 대상을 확인합니다.
현재 table은 8-bit hash로 256개의 queue를 선택합니다. 아래 helper가 반환하는 queue는 workqueue가 아니라 잠든 task의 waitqueue입니다. 서로 다른 folio가 같은 queue에 들어갈 수 있으므로 queue가 비지 않았다는 이유만으로 이 folio의 waiter가 반드시 있다고 결론내릴 수는 없습니다.
// SPDX-License-Identifier: GPL-2.0-only
/*
* Complete definition from Linux v6.18 mm/filemap.c.
* Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
* Copyright (C) 1994-1999 Linus Torvalds
* Kernel excerpt; not a standalone user-space program.
* The book's exact source tag is not asserted.
*/
static wait_queue_head_t *folio_waitqueue(struct folio *folio)
{
return &folio_wait_table[hash_ptr(folio, PAGE_WAIT_TABLE_BITS)];
}
가장 위험한 순간은 bit를 보고 잠들기로 결정한 다음, queue에 등록되기 직전에 다른 CPU가 bit를 지우는 경우입니다. 그 CPU가 waiter를 보지 못하고 지나가면 기다리는 task는 이미 끝난 사건을 영원히 기다릴 수 있습니다.
따라서 folio_wait_bit_common()은 queue lock 아래에서 PG_waiters를 설정하고 bit를 마지막으로 검사합니다. 조건이 이미 충족되면 잠들지 않고 성공 상태를 기록합니다. 아니면 waiter를 queue 끝에 연결합니다. waker가 느린 경로를 거쳐야 한다는 표시와 실제 waiter 등록을 따로 무방비하게 두지 않는 것이 핵심입니다.
이후에는 waiter의 WQ_FLAG_WOKEN과 WQ_FLAG_DONE을 사용합니다. folio bit를 아무 때나 다시 읽는 것만으로 대기를 판단하지 않습니다. 특히 DROP은 이미 참조를 놓았으므로 예전 folio를 계속 검사하는 방식으로 구현할 수 없습니다. load-acquire 같은 ordering도 완료 상태를 다른 CPU의 업데이트와 맞추기 위해 필요합니다. v6.18 folio_wait_bit_common
마지막 bit 검사를 맡는 helper 전체는 다음과 같습니다. EXCLUSIVE이면 bit를 실제로 획득해야 하고, 나머지는 clear만 확인합니다. 성공하면 두 flag를 모두 설정합니다. 이 검사는 아래 전체 worker에서 queue lock을 보유한 상태로 수행됩니다.
// SPDX-License-Identifier: GPL-2.0-only
/*
* Complete definition from Linux v6.18 mm/filemap.c.
* Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
* Copyright (C) 1994-1999 Linus Torvalds
* Kernel excerpt; not a standalone user-space program.
* The book's exact source tag is not asserted.
*/
static inline bool folio_trylock_flag(struct folio *folio, int bit_nr,
struct wait_queue_entry *wait)
{
if (wait->flags & WQ_FLAG_EXCLUSIVE) {
if (test_and_set_bit(bit_nr, &folio->flags.f))
return false;
} else if (test_bit(bit_nr, &folio->flags.f))
return false;
wait->flags |= WQ_FLAG_WOKEN | WQ_FLAG_DONE;
return true;
}
깨워 주는 것과 lock을 넘겨주는 것은 다릅니다
exclusive waiter가 깨어났어도 새로운 task가 먼저 lock을 가져갈 수 있습니다. 일반 경로에서는 깨어난 waiter가 bit를 직접 설정해 보며, 실패하면 대기를 다시 준비합니다. wakeup을 한 번 받았다는 이유로 무조건 성공을 반환하지 않습니다.
이 방식은 새로 도착한 task가 빠르게 lock을 얻도록 허용하지만 반복해서 기다린 task가 계속 밀릴 가능성이 있습니다. 구현은 sysctl_page_lock_unfairness로 관리하는 재시도 여유를 사용하고, 이를 넘긴 exclusive waiter에는 WQ_FLAG_CUSTOM을 설정하여 wake 경로의 lock handoff를 요청합니다. 이것을 모든 상황의 엄격한 FIFO 보장이라고 읽어서는 안 됩니다.
WQ_FLAG_WOKEN은 일어났다는 표시이고 WQ_FLAG_DONE은 필요한 bit 처리까지 끝났다는 표시입니다. non-exclusive 대기에는 전자가 충분할 수 있지만 exclusive 대기에는 후자가 필요합니다. 이 차이가 반환값 계산에도 그대로 이어집니다.
아래 wake_page_function()은 본체를 생략하지 않은 v6.18 전체 정의입니다. 대상이 다르면 0입니다. exclusive 대상의 bit가 다시 설정되어 있거나 CUSTOM handoff 시도에서 bit를 얻지 못하면 -1로 순회를 멈추고 그 waiter를 깨우지 않습니다. 성공할 때에는 flags를 store-release로 공개하고 task를 깨운 뒤 list entry를 제거합니다. entry를 제거한 뒤에는 waiter가 해제될 수 있으므로 더 접근하지 않는다는 끝부분의 수명 조건도 중요합니다.
// SPDX-License-Identifier: GPL-2.0-only
/*
* Complete definition from Linux v6.18 mm/filemap.c.
* Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
* Copyright (C) 1994-1999 Linus Torvalds
* Kernel excerpt; not a standalone user-space program.
* The book's exact source tag is not asserted.
*/
static int wake_page_function(wait_queue_entry_t *wait, unsigned mode, int sync, void *arg)
{
unsigned int flags;
struct wait_page_key *key = arg;
struct wait_page_queue *wait_page
= container_of(wait, struct wait_page_queue, wait);
if (!wake_page_match(wait_page, key))
return 0;
/*
* If it's a lock handoff wait, we get the bit for it, and
* stop walking (and do not wake it up) if we can't.
*/
flags = wait->flags;
if (flags & WQ_FLAG_EXCLUSIVE) {
if (test_bit(key->bit_nr, &key->folio->flags.f))
return -1;
if (flags & WQ_FLAG_CUSTOM) {
if (test_and_set_bit(key->bit_nr, &key->folio->flags.f))
return -1;
flags |= WQ_FLAG_DONE;
}
}
/*
* We are holding the wait-queue lock, but the waiter that
* is waiting for this will be checking the flags without
* any locking.
*
* So update the flags atomically, and wake up the waiter
* afterwards to avoid any races. This store-release pairs
* with the load-acquire in folio_wait_bit_common().
*/
smp_store_release(&wait->flags, flags | WQ_FLAG_WOKEN);
wake_up_state(wait->private, mode);
/*
* Ok, we have successfully done what we're waiting for,
* and we can unconditionally remove the wait entry.
*
* Note that this pairs with the "finish_wait()" in the
* waiter, and has to be the absolute last thing we do.
* After this list_del_init(&wait->entry) the wait entry
* might be de-allocated and the process might even have
* exited.
*/
list_del_init_careful(&wait->entry);
return (flags & WQ_FLAG_EXCLUSIVE) != 0;
}
unlock 쪽은 release 성격의 flag 변경과 waiter 확인을 연결합니다. v6.18 folio_unlock()은 이미 잠기지 않은 folio에 대한 debug 검사도 포함하며, process와 interrupt 문맥에서 사용할 수 있지만 NMI 문맥은 허용하지 않습니다. I/O completion이 다른 task 대신 unlock할 수 있다는 점과 아무 pointer나 수명 보장 없이 unlock할 수 있다는 주장은 다릅니다.
// SPDX-License-Identifier: GPL-2.0-only
/*
* Complete definition from Linux v6.18 mm/filemap.c.
* Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
* Copyright (C) 1994-1999 Linus Torvalds
* Kernel excerpt; not a standalone user-space program.
* The book's exact source tag is not asserted.
*/
void folio_unlock(struct folio *folio)
{
/* Bit 7 allows x86 to check the byte's sign bit */
BUILD_BUG_ON(PG_waiters != 7);
BUILD_BUG_ON(PG_locked > 7);
VM_BUG_ON_FOLIO(!folio_test_locked(folio), folio);
if (folio_xor_flags_has_waiters(folio, 1 << PG_locked))
folio_wake_bit(folio, PG_locked);
}
현재 folio_wake_bit()은 queue lock 아래에서 matching key로 깨우며, queue가 비었거나 이 folio의 waiter를 찾지 못했으면 PG_waiters를 지웁니다. hash 충돌 때문에 flag가 잠시 남는 false positive는 허용합니다. 예전 구현에 있던 bookmark loop를 현재 함수에도 있다고 덧붙이지 않습니다.
// SPDX-License-Identifier: GPL-2.0-only
/*
* Complete definition from Linux v6.18 mm/filemap.c.
* Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
* Copyright (C) 1994-1999 Linus Torvalds
* Kernel excerpt; not a standalone user-space program.
* The book's exact source tag is not asserted.
*/
static void folio_wake_bit(struct folio *folio, int bit_nr)
{
wait_queue_head_t *q = folio_waitqueue(folio);
struct wait_page_key key;
unsigned long flags;
key.folio = folio;
key.bit_nr = bit_nr;
key.page_match = 0;
spin_lock_irqsave(&q->lock, flags);
__wake_up_locked_key(q, TASK_NORMAL, &key);
/*
* It's possible to miss clearing waiters here, when we woke our page
* waiters, but the hashed waitqueue has waiters for other pages on it.
* That's okay, it's a rare case. The next waker will clear it.
*
* Note that, depending on the page pool (buddy, hugetlb, ZONE_DEVICE,
* other), the flag may be cleared in the course of freeing the page;
* but that is not required for correctness.
*/
if (!waitqueue_active(q) || !key.page_match)
folio_clear_waiters(folio);
spin_unlock_irqrestore(&q->lock, flags);
}
이전 이름을 따라갈 때 만나는 folio_wake() wrapper는 다음 v6.0 전체 정의입니다. 현재 정의가 아니라 비교 대상입니다.
// SPDX-License-Identifier: GPL-2.0-only
/*
* Complete definition from Linux v6.0 mm/filemap.c.
* Source: https://github.com/torvalds/linux/blob/v6.0/mm/filemap.c
* Copyright (C) 1994-1999 Linus Torvalds
* Kernel excerpt; not a standalone user-space program.
* The book's exact source tag is not asserted.
*/
static void folio_wake(struct folio *folio, int bit)
{
if (!folio_test_waiters(folio))
return;
folio_wake_bit(folio, bit);
}
signal과 wakeup은 마지막 순간에도 경쟁합니다
대기 loop는 task state를 설정하고 signal 여부를 검사하며, 필요하면 io_schedule()로 잠듭니다. 그러나 signal을 확인한 직후에도 다른 CPU가 waiter를 깨울 수 있습니다. loop를 빠져나온 이유만 보고 최종 결과를 확정하면 이 경쟁을 잘못 처리하게 됩니다.
그래서 먼저 finish_wait()로 queue 등록을 정리하고, 그 뒤 안정된 waiter flag를 읽어 반환값을 결정합니다. exclusive 대기는 DONE이면 성공이고 아니면 -EINTR입니다. 다른 대기는 WOKEN 여부를 사용합니다. signal 때문에 마지막 waiter가 빠져나가도 PG_waiters가 잠시 남을 수 있는데, 이를 억지로 여기서 지우기보다 다음 wake 경로가 정리하도록 합니다.
workingset에 속한 not-uptodate folio의 lock을 기다리는 경우에는 thrashing 지연과 PSI memory stall 추적도 연결됩니다. 이 accounting은 lock을 얻는 논리와 별개지만, 시스템이 왜 I/O 대기를 memory pressure의 비용으로 관찰하는지 설명해 줍니다.
다음은 folio_wait_bit_common()의 v6.18 전체 정의입니다. queue 등록과 DROP 참조 반납, 반복 대기, lock handoff, finish_wait(), PSI 정리, 최종 반환값을 같은 함수 안에서 확인할 수 있습니다. 위에서 나눠 설명한 단계는 독립적으로 호출할 수 있는 축약 구현이 아닙니다.
// SPDX-License-Identifier: GPL-2.0-only
/*
* Complete definition from Linux v6.18 mm/filemap.c.
* Source: https://github.com/torvalds/linux/blob/v6.18/mm/filemap.c
* Copyright (C) 1994-1999 Linus Torvalds
* Kernel excerpt; not a standalone user-space program.
* The book's exact source tag is not asserted.
*/
static inline int folio_wait_bit_common(struct folio *folio, int bit_nr,
int state, enum behavior behavior)
{
wait_queue_head_t *q = folio_waitqueue(folio);
int unfairness = sysctl_page_lock_unfairness;
struct wait_page_queue wait_page;
wait_queue_entry_t *wait = &wait_page.wait;
bool thrashing = false;
unsigned long pflags;
bool in_thrashing;
if (bit_nr == PG_locked &&
!folio_test_uptodate(folio) && folio_test_workingset(folio)) {
delayacct_thrashing_start(&in_thrashing);
psi_memstall_enter(&pflags);
thrashing = true;
}
init_wait(wait);
wait->func = wake_page_function;
wait_page.folio = folio;
wait_page.bit_nr = bit_nr;
repeat:
wait->flags = 0;
if (behavior == EXCLUSIVE) {
wait->flags = WQ_FLAG_EXCLUSIVE;
if (--unfairness < 0)
wait->flags |= WQ_FLAG_CUSTOM;
}
/*
* Do one last check whether we can get the
* page bit synchronously.
*
* Do the folio_set_waiters() marking before that
* to let any waker we _just_ missed know they
* need to wake us up (otherwise they'll never
* even go to the slow case that looks at the
* page queue), and add ourselves to the wait
* queue if we need to sleep.
*
* This part needs to be done under the queue
* lock to avoid races.
*/
spin_lock_irq(&q->lock);
folio_set_waiters(folio);
if (!folio_trylock_flag(folio, bit_nr, wait))
__add_wait_queue_entry_tail(q, wait);
spin_unlock_irq(&q->lock);
/*
* From now on, all the logic will be based on
* the WQ_FLAG_WOKEN and WQ_FLAG_DONE flag, to
* see whether the page bit testing has already
* been done by the wake function.
*
* We can drop our reference to the folio.
*/
if (behavior == DROP)
folio_put(folio);
/*
* Note that until the "finish_wait()", or until
* we see the WQ_FLAG_WOKEN flag, we need to
* be very careful with the 'wait->flags', because
* we may race with a waker that sets them.
*/
for (;;) {
unsigned int flags;
set_current_state(state);
/* Loop until we've been woken or interrupted */
flags = smp_load_acquire(&wait->flags);
if (!(flags & WQ_FLAG_WOKEN)) {
if (signal_pending_state(state, current))
break;
io_schedule();
continue;
}
/* If we were non-exclusive, we're done */
if (behavior != EXCLUSIVE)
break;
/* If the waker got the lock for us, we're done */
if (flags & WQ_FLAG_DONE)
break;
/*
* Otherwise, if we're getting the lock, we need to
* try to get it ourselves.
*
* And if that fails, we'll have to retry this all.
*/
if (unlikely(test_and_set_bit(bit_nr, folio_flags(folio, 0))))
goto repeat;
wait->flags |= WQ_FLAG_DONE;
break;
}
/*
* If a signal happened, this 'finish_wait()' may remove the last
* waiter from the wait-queues, but the folio waiters bit will remain
* set. That's ok. The next wakeup will take care of it, and trying
* to do it here would be difficult and prone to races.
*/
finish_wait(q, wait);
if (thrashing) {
delayacct_thrashing_end(&in_thrashing);
psi_memstall_leave(&pflags);
}
/*
* NOTE! The wait->flags weren't stable until we've done the
* 'finish_wait()', and we could have exited the loop above due
* to a signal, and had a wakeup event happen after the signal
* test but before the 'finish_wait()'.
*
* So only after the finish_wait() can we reliably determine
* if we got woken up or not, so we can now figure out the final
* return value based on that state without races.
*
* Also note that WQ_FLAG_WOKEN is sufficient for a non-exclusive
* waiter, but an exclusive one requires WQ_FLAG_DONE.
*/
if (behavior == EXCLUSIVE)
return wait->flags & WQ_FLAG_DONE ? 0 : -EINTR;
return wait->flags & WQ_FLAG_WOKEN ? 0 : -EINTR;
}
writeback 대기는 data의 안정성 요구에 따라 붙습니다
PG_writeback과 PG_locked는 독립적인 bit입니다. writeback을 시작할 때 folio lock을 잡았더라도 I/O 전체가 끝날 때까지 그 lock을 유지해야 하는 것은 아닙니다. 제출 중인 data와 나중의 수정이 충돌할 수 있는지는 filesystem과 device의 stable-write 요구에 달려 있습니다.
folio_wait_writeback()은 writeback bit가 끝나기를 기다립니다. folio_wait_stable() 계열은 해당 mapping의 stable-write 요구를 확인하여 필요한 대기를 수행합니다. helper 하나를 보았다고 모든 filesystem이 언제나 같은 대기를 한다고 일반화하면 안 됩니다. v6.18 include/linux/pagemap.h
두 함수의 실제 본체는 현재 v6.18 mm/page-writeback.c에 있습니다. process 문맥에서 spinlock을 보유하지 않고, folio 참조를 가진 상태로 호출해야 합니다. folio lock이 없다면 이번 writeback이 끝난 뒤 다른 writeback이 다시 시작할 수 있습니다. 따라서 while loop를 통과했다는 관찰을 이후 영구적인 not-writeback 보장으로 바꾸어 읽지 않습니다.
// SPDX-License-Identifier: GPL-2.0-only
/*
* Complete definition from Linux v6.18 mm/page-writeback.c.
* Source: https://github.com/torvalds/linux/blob/v6.18/mm/page-writeback.c
* Copyright (C) 2002, Linus Torvalds.
* Copyright (C) 2007 Red Hat, Inc., Peter Zijlstra
* 10Apr2002 Andrew Morton -- Initial version
* Kernel excerpt; not a standalone user-space program.
* The book's exact source tag is not asserted.
*/
void folio_wait_writeback(struct folio *folio)
{
while (folio_test_writeback(folio)) {
trace_folio_wait_writeback(folio, folio_mapping(folio));
folio_wait_bit(folio, PG_writeback);
}
}
// SPDX-License-Identifier: GPL-2.0-only
/*
* Complete definition from Linux v6.18 mm/page-writeback.c.
* Source: https://github.com/torvalds/linux/blob/v6.18/mm/page-writeback.c
* Copyright (C) 2002, Linus Torvalds.
* Copyright (C) 2007 Red Hat, Inc., Peter Zijlstra
* 10Apr2002 Andrew Morton -- Initial version
* Kernel excerpt; not a standalone user-space program.
* The book's exact source tag is not asserted.
*/
void folio_wait_stable(struct folio *folio)
{
if (mapping_stable_writes(folio_mapping(folio)))
folio_wait_writeback(folio);
}
대기 코드를 읽을 때에는 세 가지를 나누어 물으면 됩니다. 지금도 folio 참조를 소유하고 있는지, 성공했다면 어떤 bit를 획득한 것인지, 상태가 끝났다는 사실 외에 I/O error를 확인할 곳이 있는지입니다. 이 질문을 분리하면 깨어난 pointer를 잘못 재사용하거나 남의 lock을 푸는 실수를 피할 수 있습니다.
참고 자료
- Lorenzo Stoakes, The Linux Memory Manager, Early Access 2025, Chapter 9의 folio locking 및 waiting 설명.
- Linux v6.18 mm/filemap.c: wait behavior, wakeup, signal 정리, unlock과 I/O 완료.
- Linux v6.18 include/linux/pagemap.h: folio lock, writeback 및 stable helper 계약.