SetPageWriteback()가 있을 것이라고 짐작해 호출하는 순간 문제가 시작됩니다. Linux v6.1의 PG_writeback에는 그런 unconditional setter가 없습니다. 비슷한 이름의 helper가 여러 개 보인다고 모든 test·set·clear 조합을 조립할 수 있는 것은 아닙니다.
이 글은 v6.1에서 page와 folio의 상태를 읽고 변경하는 인터페이스를 기능별로 정리합니다. 실제로 bit를 변경하는 것, descriptor의 종류를 판별하는 것, mapping에 붙은 tag를 읽는 것은 다른 연산입니다. 마지막에는 v6.18에서 그대로 사용할 수 없는 부분도 구별합니다.
제공 조합은 생성 macro에서 결정됩니다
아래 표의 T는 test, S와 C는 atomic bit set·clear, TS와 TC는 이전 값을 반환하는 atomic test-and-set·test-and-clear입니다. __S와 __C는 non-atomic 변경입니다. 표에 없는 조합은 v6.1의 해당 선언이 제공하지 않는 조합입니다.
Page stem이 Active라면 T는 PageActive(), S는 SetPageActive(), TC는 TestClearPageActive()가 됩니다. folio suffix가 active라면 각각 folio_test_active(), folio_set_active(), folio_test_clear_active()입니다. double underscore는 __SetPageActive() 또는 __folio_set_active()처럼 붙습니다. 수동 구현의 예외는 별도 표에서 실제 이름을 사용합니다.
두 namespace가 함께 만들어지는 v6.1/include/linux/page-flags.h의 생성부 전문은 다음과 같습니다. code는 책의 crop이 아니라 Linux v6.1의 공인 source에서 가져왔습니다. 원본의 SPDX-License-Identifier: GPL-2.0를 표시하며, macro 끝의 정렬 공백과 들여쓰기만 정돈했습니다. kernel header 문맥의 code이며 standalone program은 아닙니다.
/* SPDX-License-Identifier: GPL-2.0 */
#define TESTPAGEFLAG(uname, lname, policy) \
static __always_inline bool folio_test_##lname(struct folio *folio) \
{ return test_bit(PG_##lname, folio_flags(folio, FOLIO_##policy)); } \
static __always_inline int Page##uname(struct page *page) \
{ return test_bit(PG_##lname, &policy(page, 0)->flags); }
#define SETPAGEFLAG(uname, lname, policy) \
static __always_inline \
void folio_set_##lname(struct folio *folio) \
{ set_bit(PG_##lname, folio_flags(folio, FOLIO_##policy)); } \
static __always_inline void SetPage##uname(struct page *page) \
{ set_bit(PG_##lname, &policy(page, 1)->flags); }
#define CLEARPAGEFLAG(uname, lname, policy) \
static __always_inline \
void folio_clear_##lname(struct folio *folio) \
{ clear_bit(PG_##lname, folio_flags(folio, FOLIO_##policy)); } \
static __always_inline void ClearPage##uname(struct page *page) \
{ clear_bit(PG_##lname, &policy(page, 1)->flags); }
#define __SETPAGEFLAG(uname, lname, policy) \
static __always_inline \
void __folio_set_##lname(struct folio *folio) \
{ __set_bit(PG_##lname, folio_flags(folio, FOLIO_##policy)); } \
static __always_inline void __SetPage##uname(struct page *page) \
{ __set_bit(PG_##lname, &policy(page, 1)->flags); }
#define __CLEARPAGEFLAG(uname, lname, policy) \
static __always_inline \
void __folio_clear_##lname(struct folio *folio) \
{ __clear_bit(PG_##lname, folio_flags(folio, FOLIO_##policy)); } \
static __always_inline void __ClearPage##uname(struct page *page) \
{ __clear_bit(PG_##lname, &policy(page, 1)->flags); }
#define TESTSETFLAG(uname, lname, policy) \
static __always_inline \
bool folio_test_set_##lname(struct folio *folio) \
{ return test_and_set_bit(PG_##lname, folio_flags(folio, FOLIO_##policy)); } \
static __always_inline int TestSetPage##uname(struct page *page) \
{ return test_and_set_bit(PG_##lname, &policy(page, 1)->flags); }
#define TESTCLEARFLAG(uname, lname, policy) \
static __always_inline \
bool folio_test_clear_##lname(struct folio *folio) \
{ return test_and_clear_bit(PG_##lname, folio_flags(folio, FOLIO_##policy)); } \
static __always_inline int TestClearPage##uname(struct page *page) \
{ return test_and_clear_bit(PG_##lname, &policy(page, 1)->flags); }
#define PAGEFLAG(uname, lname, policy) \
TESTPAGEFLAG(uname, lname, policy) \
SETPAGEFLAG(uname, lname, policy) \
CLEARPAGEFLAG(uname, lname, policy)
#define __PAGEFLAG(uname, lname, policy) \
TESTPAGEFLAG(uname, lname, policy) \
__SETPAGEFLAG(uname, lname, policy) \
__CLEARPAGEFLAG(uname, lname, policy)
#define TESTSCFLAG(uname, lname, policy) \
TESTSETFLAG(uname, lname, policy) \
TESTCLEARFLAG(uname, lname, policy)
#define TESTPAGEFLAG_FALSE(uname, lname) \
static inline bool folio_test_##lname(const struct folio *folio) { return false; } \
static inline int Page##uname(const struct page *page) { return 0; }
#define SETPAGEFLAG_NOOP(uname, lname) \
static inline void folio_set_##lname(struct folio *folio) { } \
static inline void SetPage##uname(struct page *page) { }
#define CLEARPAGEFLAG_NOOP(uname, lname) \
static inline void folio_clear_##lname(struct folio *folio) { } \
static inline void ClearPage##uname(struct page *page) { }
#define __CLEARPAGEFLAG_NOOP(uname, lname) \
static inline void __folio_clear_##lname(struct folio *folio) { } \
static inline void __ClearPage##uname(struct page *page) { }
#define TESTSETFLAG_FALSE(uname, lname) \
static inline bool folio_test_set_##lname(struct folio *folio) \
{ return 0; } \
static inline int TestSetPage##uname(struct page *page) { return 0; }
#define TESTCLEARFLAG_FALSE(uname, lname) \
static inline bool folio_test_clear_##lname(struct folio *folio) \
{ return 0; } \
static inline int TestClearPage##uname(struct page *page) { return 0; }
#define PAGEFLAG_FALSE(uname, lname) TESTPAGEFLAG_FALSE(uname, lname) \
SETPAGEFLAG_NOOP(uname, lname) CLEARPAGEFLAG_NOOP(uname, lname)
#define TESTSCFLAG_FALSE(uname, lname) \
TESTSETFLAG_FALSE(uname, lname) TESTCLEARFLAG_FALSE(uname, lname)
PAGEFLAG는 T·S·C를 만들지만 __PAGEFLAG는 T·__S·__C를 만듭니다. TESTSCFLAG는 TS·TC만 더합니다. 따라서 어떤 상태에 non-atomic clear가 추가되었다고 non-atomic set도 있다고 결론 내릴 수 없습니다.
PAGEFLAG_FALSE는 이름을 없애는 대신 test=false, set·clear=no-op인 stub을 만듭니다. 반면 어떤 조건 분기에는 TS·TC stub까지 선언하지 않습니다. “기능이 꺼짐”, “symbol이 없음”, “symbol은 있으나 아무 상태도 바꾸지 않음”을 나누어야 합니다.
Policy는 저장 위치와 입력 조건을 함께 고릅니다
Page policy는 같은 flags를 어느 descriptor에서 읽을지 정합니다. PF_HEAD는 head로 정규화하고, PF_ONLY_HEAD는 tail 자체를 금지합니다. PF_NO_TAIL은 조회에서는 tail을 정규화할 수 있지만 변경에서는 tail 입력을 금지합니다. PF_NO_COMPOUND의 변경은 compound 입력을 금지하며, 조회가 자동으로 head로 변환되는 것은 아닙니다.
PF_SECOND는 head를 입력받아 first tail의 flags를 고릅니다. folio 쪽 FOLIO_PF_*는 이 policy의 Page 검사를 그대로 재실행하는 대신 0 또는 1의 descriptor offset을 선택합니다. folio_flags() 자체의 sanity assertion은 여전히 있습니다.
다음은 같은 source의 policy와 offset 정의 전체입니다. PF_POISONED_CHECK가 결합되지만 runtime assertion은 CONFIG_DEBUG_VM_PGFLAGS 조건입니다. CONFIG_DEBUG_VM_PGTABLE은 별개의 설정입니다.
/* SPDX-License-Identifier: GPL-2.0 */
#define PF_POISONED_CHECK(page) ({ \
VM_BUG_ON_PGFLAGS(PagePoisoned(page), page); \
page; })
#define PF_ANY(page, enforce) PF_POISONED_CHECK(page)
#define PF_HEAD(page, enforce) PF_POISONED_CHECK(compound_head(page))
#define PF_ONLY_HEAD(page, enforce) ({ \
VM_BUG_ON_PGFLAGS(PageTail(page), page); \
PF_POISONED_CHECK(page); })
#define PF_NO_TAIL(page, enforce) ({ \
VM_BUG_ON_PGFLAGS(enforce && PageTail(page), page); \
PF_POISONED_CHECK(compound_head(page)); })
#define PF_NO_COMPOUND(page, enforce) ({ \
VM_BUG_ON_PGFLAGS(enforce && PageCompound(page), page); \
PF_POISONED_CHECK(page); })
#define PF_SECOND(page, enforce) ({ \
VM_BUG_ON_PGFLAGS(!PageHead(page), page); \
PF_POISONED_CHECK(&page[1]); })
/* Which page is the flag stored in */
#define FOLIO_PF_ANY 0
#define FOLIO_PF_HEAD 0
#define FOLIO_PF_ONLY_HEAD 0
#define FOLIO_PF_NO_TAIL 0
#define FOLIO_PF_NO_COMPOUND 0
#define FOLIO_PF_SECOND 1
LRU와 reclaim은 같은 bit 하나로 결정되지 않습니다
| Page stem / folio suffix | 제공 조합 | Page policy | 조건과 의미 |
|---|---|---|---|
Active / active |
T S C __C TC |
PF_HEAD |
상시 |
LRU / lru |
T S C __C TC |
PF_HEAD |
상시 |
Referenced / referenced |
T S C __S TC |
PF_HEAD |
__C는 생성하지 않습니다 |
Workingset / workingset |
T S C TC |
PF_HEAD |
상시 |
Unevictable / unevictable |
T S C __C TC |
PF_HEAD |
상시 |
Mlocked / mlocked |
T S C __C TS TC |
PF_NO_TAIL |
MMU; off에서도 같은 조합의 false/no-op stub입니다 |
Waiters / waiters |
T S C |
PF_ONLY_HEAD |
상시 |
Isolated / isolated |
T S C |
PF_ANY |
non-LRU movable 문맥의 reclaim alias |
Reclaim / reclaim |
T S C TC |
PF_NO_TAIL |
상시 |
Readahead / readahead |
T S C TC |
PF_NO_COMPOUND |
읽기 문맥의 reclaim alias입니다 |
Referenced의 조합에는 __S가 있지만 __C는 없습니다. Waiters의 head-only 조건을 Active의 head 정규화와 같게 취급해서도 안 됩니다. Mlocked의 변경 helper가 atomic이어도 unevictable 상태와 LRU 연결을 한꺼번에 갱신해 주는 transaction은 아닙니다.
Readahead와 Reclaim은 bit를 공유하지만 각각 읽기와 쓰기 문맥에서 사용합니다. Isolated 역시 non-LRU movable 용도에서 같은 bit 위치를 다른 의미로 사용합니다. alias 이름을 여러 독립 boolean이라고 세면 존재할 수 없는 상태 조합을 만들게 됩니다.
I/O와 owner-private 상태의 조합을 구별합니다
| Page stem / folio suffix | 제공 조합 | Page policy | 조건과 의미 |
|---|---|---|---|
Locked / locked |
T __S __C |
PF_NO_TAIL |
상시; atomic S/C는 생성하지 않습니다 |
Dirty / dirty |
T S C __C TS TC |
PF_HEAD |
상시 |
Writeback / writeback |
T TS TC |
PF_NO_TAIL |
무조건 S/C는 생성하지 않습니다. 상위 accounting API를 사용합니다 |
Error / error |
T S C TC |
PF_NO_TAIL |
상시 |
MappedToDisk / mappedtodisk |
T S C |
PF_NO_TAIL |
상시 |
Private / private |
T S C |
PF_ANY |
owner별 private 상태입니다 |
Private2 / private_2 |
T S C TS TC |
PF_ANY |
owner별 두 번째 private 상태입니다 |
OwnerPriv1 / owner_priv_1 |
T S C TC |
PF_ANY |
owner별 의미입니다 |
Checked / checked |
T S C |
PF_NO_COMPOUND |
filesystem owner bit |
SwapBacked / swapbacked |
T S C __S __C |
PF_NO_TAIL |
상시 |
SwapCache / swapcache |
T S C |
PF_NO_TAIL |
SWAP; T는 swapbacked도 검사합니다. off에는 false/no-op입니다 |
Writeback에는 T·TS·TC만 있습니다. SetPageWriteback(), ClearPageWriteback(), folio_set_writeback(), folio_clear_writeback()를 이 naming pattern으로 만들어 사용할 수 없습니다. 실제 I/O 경로는 folio_start_writeback()·folio_end_writeback() 등 accounting과 대기자 처리가 연결된 상위 API를 따라야 합니다. low-level TS·TC가 있다는 사실도 상위 protocol을 생략할 허가가 아닙니다.
SwapCache의 test는 PG_swapcache bit 하나만 읽지 않고 folio_test_swapbacked()도 확인합니다. CONFIG_SWAP이 꺼지면 false/no-op stub입니다. Checked·SwapCache·OwnerPriv1은 owner 문맥에 따라 같은 bit 위치를 다르게 해석합니다.
Uptodate는 아래 특수 표에 포함됩니다. setter는 folio에서 folio_mark_uptodate()이며, data store가 bit 표시보다 먼저 관찰되도록 하는 ordering이 있습니다. __folio_mark_uptodate()도 barrier를 생략하는 함수가 아니라 non-atomic bit operation을 사용하는 함수입니다.
/* SPDX-License-Identifier: GPL-2.0 */
static inline bool folio_test_uptodate(struct folio *folio)
{
bool ret = test_bit(PG_uptodate, folio_flags(folio, 0));
/*
* Must ensure that the data we read out of the folio is loaded
* _after_ we've loaded folio->flags to check the uptodate bit.
* We can skip the barrier if the folio is not uptodate, because
* we wouldn't be reading anything from it.
*
* See folio_mark_uptodate() for the other side of the story.
*/
if (ret)
smp_rmb();
return ret;
}
static inline int PageUptodate(struct page *page)
{
return folio_test_uptodate(page_folio(page));
}
static __always_inline void __folio_mark_uptodate(struct folio *folio)
{
smp_wmb();
__set_bit(PG_uptodate, folio_flags(folio, 0));
}
static __always_inline void folio_mark_uptodate(struct folio *folio)
{
/*
* Memory barrier must be issued before setting the PG_uptodate bit,
* so that all previous stores issued in order to bring the folio
* uptodate are actually visible before folio_test_uptodate becomes true.
*/
smp_wmb();
set_bit(PG_uptodate, folio_flags(folio, 0));
}
static __always_inline void __SetPageUptodate(struct page *page)
{
__folio_mark_uptodate((struct folio *)page);
}
static __always_inline void SetPageUptodate(struct page *page)
{
folio_mark_uptodate((struct folio *)page);
}
Allocator와 선택 기능에는 별도의 조건이 있습니다
| Page stem / folio suffix | 제공 조합 | Page policy | 조건과 의미 |
|---|---|---|---|
Slab / slab |
T __S __C |
PF_NO_TAIL |
v6.1의 slab backing-page 상태입니다 |
SlobFree / slob_free |
T __S __C |
PF_NO_TAIL |
SLOB 문맥의 private alias; atomic S/C가 아닙니다 |
Reported / reported |
T __S __C |
PF_NO_COMPOUND |
free buddy page; zone lock 보호를 전제합니다 |
Reserved / reserved |
T S C __S __C |
PF_NO_COMPOUND |
owner만 변경하는 reserved 상태입니다 |
VmemmapSelfHosted / vmemmap_self_hosted |
T S C |
PF_ANY |
MEMORY_HOTPLUG; off에는 false/no-op입니다 |
HWPoison / hwpoison |
T S C TS TC |
PF_ANY |
MEMORY_FAILURE; off의 T=false,S/C=no-op, TS/TC는 생성되지 않습니다 |
HasHWPoisoned / has_hwpoisoned |
T S C TS TC |
PF_SECOND |
MEMORY_FAILURE && TRANSPARENT_HUGEPAGE; off에서 같은 조합 false/no-op |
DoubleMap / double_map |
T S C TS TC |
PF_SECOND |
CONFIG_TRANSPARENT_HUGEPAGE; off에서도 T/TS/TC=false, S/C=no-op |
Idle / idle |
T S C |
PF_ANY |
PAGE_IDLE_FLAG && 64BIT에서 Page/flags; folio의 32-bit·off 대안은 아래 설명입니다 |
Young / young |
T S TC |
PF_ANY |
PAGE_IDLE_FLAG && 64BIT에서 Page/flags; C만 따로 생성하지 않습니다 |
Uncached / uncached |
T S C |
PF_NO_COMPOUND |
ARCH_USES_PG_UNCACHED; off에는 false/no-op입니다 |
SkipKASanPoison / skip_kasan_poison |
T S C |
PF_HEAD |
KASAN_HW_TAGS; off에는 T=false,S/C=no-op입니다 |
Pinned / pinned |
T S C TS TC |
PF_NO_COMPOUND |
Xen 용도의 alias이며 generic FOLL_PIN count와 다릅니다 |
SavePinned / savepinned |
T S C |
PF_NO_COMPOUND |
Xen domain-save 문맥의 dirty alias입니다 |
Foreign / foreign |
T S C |
PF_NO_COMPOUND |
Xen 용도이나 이 선언은 CONFIG_XEN으로 감싸지지 않습니다 |
XenRemapped / xen_remapped |
T S C TC |
PF_NO_COMPOUND |
Xen remap 문맥의 owner_priv_1 alias입니다 |
Slab·SlobFree·Reported의 변경은 __S·__C입니다. 예를 들어 __SetPageSlobFree()와 __folio_set_slob_free()는 있지만 같은 의미의 atomic SetPageSlobFree()·folio_set_slob_free()를 이 선언에서 얻을 수는 없습니다. Reported는 free buddy page를 zone lock으로 보호하는 문맥에 맞추어 non-atomic 변경을 사용합니다.
HWPoison은 CONFIG_MEMORY_FAILURE가 꺼졌을 때 T·S·C stub만 남습니다. active 구성의 TS·TC까지 항상 callable이라고 추정하면 안 됩니다. 반면 HasHWPoisoned와 DoubleMap의 disabled 분기는 TS·TC false stub도 제공합니다. 전자는 large page의 subpage 중 hardware poison이 있음을, 후자는 PTE·PMD mapping이 섞인 THP 장부를 다루므로 PF_SECOND를 사용합니다.
64-bit의 idle·young bit는 CONFIG_PAGE_IDLE_FLAG가 켜져야 생성됩니다. 32-bit에서는 PageIdle()·PageYoung() 이름을 그대로 사용할 수 있다고 가정하지 않습니다. page_idle.h의 folio helper가 page_ext_get()으로 extension을 얻어 PAGE_EXT_IDLE·PAGE_EXT_YOUNG를 읽고 변경한 뒤 page_ext_put()합니다. extension을 얻지 못하면 조회는 false, 변경은 no-op입니다. tracking이 꺼진 경우에도 이 header의 folio helper는 false/no-op stub으로 남습니다. page wrapper 이름은 page_is_idle()·set_page_idle()·clear_page_idle(), page_is_young()·set_page_young()·test_and_clear_page_young()입니다.
Pinned·SavePinned·Foreign·XenRemapped는 Xen의 용도를 나타내지만 이 helper 선언 자체를 전부 CONFIG_XEN으로 감싼 구조는 아닙니다. 특히 PagePinned()를 일반 FOLL_PIN의 DMA pin-count 판별과 동일시해서는 안 됩니다.
수동 판별과 page type은 별도 API입니다
다음 표는 앞의 생성형 상태와 구별해야 할 mapping·type·compound 판별을 정리합니다. T만 있는 행에 임의의 setter를 붙이지 않습니다.
| Page stem | 실제 folio test 또는 예외 | Page 조합 | 저장 공간과 조건 |
|---|---|---|---|
Anon |
folio_test_anon |
T |
mapping tag; PageAnon은 page_folio로 head를 고릅니다 |
Ksm |
folio_test_ksm |
T |
mapping tag; CONFIG_KSM; off에는 false입니다 |
MappingFlags |
folio_mapping_flags |
T |
mapping mask; folio는 folio_mapping_flags로 이름이 다릅니다 |
Movable |
folio_test_movable |
T __S __C |
mapping+operations; PageMovable는 lock+ops를 검사하고 __PageMovable는 tag만 봅니다 |
AnonExclusive |
대응을 추정하지 않습니다 | T S C __C |
PF_ANY + 수동 조건; PageAnon 전제; KSM·hugetlb head 조건이 있고 folio 전용 짝은 없습니다 |
Head |
folio_test_head |
T __S C __C |
PF_ANY + 수동 test; PageHead의 fake-head 보정과 folio_test_head의 bit test를 구별합니다 |
Uptodate |
folio_test_uptodate |
T S __S C |
수동 ordering / PF_NO_TAIL; folio의 set 이름은 mark_uptodate입니다 |
Buddy |
대응을 추정하지 않습니다 | T __S __C |
page_type; type encoding을 사용합니다 |
Offline |
대응을 추정하지 않습니다 | T __S __C |
page_type; online section 안에서도 개별 page는 offline일 수 있습니다 |
Table |
대응을 추정하지 않습니다 | T __S __C |
page_type; page-table page의 type입니다 |
Guard |
대응을 추정하지 않습니다 | T __S __C |
page_type; debug guard의 type입니다 |
Compound |
대응을 추정하지 않습니다 | T C |
수동 판별; C는 THP 구성에서 head에만 ClearPageCompound를 적용합니다 |
Tail |
대응을 추정하지 않습니다 | T |
수동 판별; compound_head tag 및 fake-head를 판별합니다 |
Poisoned |
대응을 추정하지 않습니다 | T |
descriptor 검사; flags의 PAGE_POISON_PATTERN 비교이며 HWPoison과 다릅니다 |
Huge |
folio_test_hugetlb |
T |
hugetlb destructor; HUGETLB_PAGE; PageHuge는 tail에서 head를 찾으며 folio 쪽은 PageHeadHuge를 씁니다 |
HeadHuge |
대응을 추정하지 않습니다 | T |
hugetlb destructor; HUGETLB_PAGE; head인지를 먼저 검사합니다 |
TransHuge |
folio_test_transhuge |
T |
head 판별; THP; PageTransHuge는 tail을 금지하며 hugetlb가 없는 core VM 문맥을 전제합니다 |
TransCompound |
대응을 추정하지 않습니다 | T |
compound 판별; THP; hugetlb도 참이 될 수 있어 호출 문맥을 제한합니다 |
TransTail |
대응을 추정하지 않습니다 | T |
tail 판별; THP; hugetlb도 참이 될 수 있어 호출 문맥을 제한합니다 |
| folio 전용 large | folio_test_large() |
Page 조합을 만들지 않습니다 | v6.1은 head bit로 large 여부를 판별합니다. |
AnonExclusive의 set과 atomic clear는 anonymous이며 KSM이 아닌 조건을 검사합니다. hugetlb에서는 head 조건도 검사하고, PTE-mapped THP에는 tail별 exclusive 상태가 필요할 수 있습니다. raw bit는 v6.1에서 PG_mappedtodisk와 같은 위치지만 file-backed disk 상태로 해석하면 안 됩니다.
PageMappingFlags()와 folio_mapping_flags()는 tag mask 자체가 아니라 mask에 걸리는 bit가 있는지를 boolean으로 돌려줍니다. 이를 PageAnon()·PageKsm()·__PageMovable()의 판별을 구분할 수 있는 raw 정수 tag로 사용하면 안 됩니다.
PageHuge()는 compound인지 확인한 뒤 head의 first-tail destructor를 비교합니다. PageHeadHuge()는 먼저 head인지 확인합니다. THP 계열의 Trans 판별은 hugetlb도 참일 수 있으므로 hugetlb가 배제된 core VM 문맥에서 써야 합니다. 관련 configuration이 꺼졌을 때의 test는 false stub입니다. PageHead()의 fake-head 보정과 folio_test_head()의 bit test도 동일한 함수 body는 아닙니다.
Buddy·Offline·Table·Guard는 flags가 아니라 page_type을 사용합니다. v6.1에서는 다음 완결된 생성부가 T·__S·__C를 만들며, set이 raw bit를 지우고 clear가 세우는 역방향 표현입니다. 이 버전에는 이 네 type을 대상으로 하는 folio 생성형 짝이 없습니다.
/* SPDX-License-Identifier: GPL-2.0 */
#define PAGE_TYPE_BASE 0xf0000000
/* Reserve 0x0000007f to catch underflows of page_mapcount */
#define PAGE_MAPCOUNT_RESERVE -128
#define PG_buddy 0x00000080
#define PG_offline 0x00000100
#define PG_table 0x00000200
#define PG_guard 0x00000400
#define PageType(page, flag) \
((page->page_type & (PAGE_TYPE_BASE | flag)) == PAGE_TYPE_BASE)
static inline int page_has_type(struct page *page)
{
return (int)page->page_type < PAGE_MAPCOUNT_RESERVE;
}
#define PAGE_TYPE_OPS(uname, lname) \
static __always_inline int Page##uname(struct page *page) \
{ \
return PageType(page, PG_##lname); \
} \
static __always_inline void __SetPage##uname(struct page *page) \
{ \
VM_BUG_ON_PAGE(!PageType(page, 0), page); \
page->page_type &= ~PG_##lname; \
} \
static __always_inline void __ClearPage##uname(struct page *page) \
{ \
VM_BUG_ON_PAGE(!Page##uname(page), page); \
page->page_type |= PG_##lname; \
}
__SetPageBuddy()와 __ClearPageBuddy()가 실제 이름입니다. PageOffline()은 containing section이 online이어도 개별 page의 내용을 읽어서는 안 되는 상태를 나타낼 수 있습니다. PFN walker는 driver와의 offline 동기화도 고려해야 합니다.
PageMovable()과 __PageMovable()의 차이는 실제 구현을 보면 분명해집니다. 앞쪽 검사는 page lock을 요구하고 유효한 operations pointer까지 확인합니다. 뒷쪽 검사는 mapping tag만 확인합니다. 아래는 v6.1 mm/compaction.c의 세 함수 전문입니다. 원본 파일은 SPDX-License-Identifier: GPL-2.0, IBM Corp. 2007–2010 Mel Gorman의 copyright 표시를 갖습니다.
// SPDX-License-Identifier: GPL-2.0
bool PageMovable(struct page *page)
{
const struct movable_operations *mops;
VM_BUG_ON_PAGE(!PageLocked(page), page);
if (!__PageMovable(page))
return false;
mops = page_movable_ops(page);
if (mops)
return true;
return false;
}
EXPORT_SYMBOL(PageMovable);
void __SetPageMovable(struct page *page, const struct movable_operations *mops)
{
VM_BUG_ON_PAGE(!PageLocked(page), page);
VM_BUG_ON_PAGE((unsigned long)mops & PAGE_MAPPING_MOVABLE, page);
page->mapping = (void *)((unsigned long)mops | PAGE_MAPPING_MOVABLE);
}
EXPORT_SYMBOL(__SetPageMovable);
void __ClearPageMovable(struct page *page)
{
VM_BUG_ON_PAGE(!PageMovable(page), page);
/*
* This page still has the type of a movable page, but it's
* actually not movable any more.
*/
page->mapping = (void *)PAGE_MAPPING_MOVABLE;
}
EXPORT_SYMBOL(__ClearPageMovable);
__ClearPageMovable()는 mapping을 0으로 만드는 것이 아니라 tag만 남깁니다. 따라서 clear 뒤에도 tag-only test는 참일 수 있지만 operations가 없으므로 lock을 요구하는 PageMovable()는 거짓이 됩니다. folio_test_movable()는 migrate.h에서 PageMovable(&folio->page)를 호출합니다. CONFIG_COMPACTION이 꺼지면 PageMovable는 false, 두 변경 helper는 no-op입니다. folio setter를 naming pattern으로 추정해서는 안 됩니다.
생성 호출과 수동 helper를 함께 확인합니다
생성 macro만으로는 표의 모든 계약이 드러나지 않습니다. 어떤 조합을 실제로 생성하는지 정하는 호출과 PageAnon()·PageKsm()·PageSwapCache() 같은 수동 정의도 함께 읽어야 합니다. 다음 code는 Linux v6.1의 page-flags.h에서 complete enum과 complete declaration group을 가져온 것입니다. 앞의 생성 macro·policy 정의와 함께 읽는 kernel 내부 source이며 독립적으로 compile하는 header는 아닙니다. tag의 commit은 830b3c68c1fb1e9176028d02ef86f3cf76aa2476, 원래 SPDX는 GPL-2.0입니다. 각 범위와 원래 configuration 분기를 보존했으며 책의 미확인 tag를 복원한 code가 아닙니다.
/* SPDX-License-Identifier: GPL-2.0 */
/* Linux v6.1, commit 830b3c68c1fb1e9176028d02ef86f3cf76aa2476.
* Source: include/linux/page-flags.h
* Complete enum and complete declaration/configuration groups.
* Kernel context; not a standalone translation unit.
*/
/* Upstream lines 100-201. */
enum pageflags {
PG_locked, /* Page is locked. Don't touch. */
PG_referenced,
PG_uptodate,
PG_dirty,
PG_lru,
PG_active,
PG_workingset,
PG_waiters, /* Page has waiters, check its waitqueue. Must be bit #7 and in the same byte as "PG_locked" */
PG_error,
PG_slab,
PG_owner_priv_1, /* Owner use. If pagecache, fs may use*/
PG_arch_1,
PG_reserved,
PG_private, /* If pagecache, has fs-private data */
PG_private_2, /* If pagecache, has fs aux data */
PG_writeback, /* Page is under writeback */
PG_head, /* A head page */
PG_mappedtodisk, /* Has blocks allocated on-disk */
PG_reclaim, /* To be reclaimed asap */
PG_swapbacked, /* Page is backed by RAM/swap */
PG_unevictable, /* Page is "unevictable" */
#ifdef CONFIG_MMU
PG_mlocked, /* Page is vma mlocked */
#endif
#ifdef CONFIG_ARCH_USES_PG_UNCACHED
PG_uncached, /* Page has been mapped as uncached */
#endif
#ifdef CONFIG_MEMORY_FAILURE
PG_hwpoison, /* hardware poisoned page. Don't touch */
#endif
#if defined(CONFIG_PAGE_IDLE_FLAG) && defined(CONFIG_64BIT)
PG_young,
PG_idle,
#endif
#ifdef CONFIG_64BIT
PG_arch_2,
#endif
#ifdef CONFIG_KASAN_HW_TAGS
PG_skip_kasan_poison,
#endif
__NR_PAGEFLAGS,
PG_readahead = PG_reclaim,
/*
* Depending on the way an anonymous folio can be mapped into a page
* table (e.g., single PMD/PUD/CONT of the head page vs. PTE-mapped
* THP), PG_anon_exclusive may be set only for the head page or for
* tail pages of an anonymous folio. For now, we only expect it to be
* set on tail pages for PTE-mapped THP.
*/
PG_anon_exclusive = PG_mappedtodisk,
/* Filesystems */
PG_checked = PG_owner_priv_1,
/* SwapBacked */
PG_swapcache = PG_owner_priv_1, /* Swap page: swp_entry_t in private */
/* Two page bits are conscripted by FS-Cache to maintain local caching
* state. These bits are set on pages belonging to the netfs's inodes
* when those inodes are being locally cached.
*/
PG_fscache = PG_private_2, /* page backed by cache */
/* XEN */
/* Pinned in Xen as a read-only pagetable page. */
PG_pinned = PG_owner_priv_1,
/* Pinned as part of domain save (see xen_mm_pin_all()). */
PG_savepinned = PG_dirty,
/* Has a grant mapping of another (foreign) domain's page. */
PG_foreign = PG_owner_priv_1,
/* Remapped by swiotlb-xen. */
PG_xen_remapped = PG_owner_priv_1,
/* SLOB */
PG_slob_free = PG_private,
/* Compound pages. Stored in first tail page's flags */
PG_double_map = PG_workingset,
#ifdef CONFIG_MEMORY_FAILURE
/*
* Compound pages. Stored in first tail page's flags.
* Indicates that at least one subpage is hwpoisoned in the
* THP.
*/
PG_has_hwpoisoned = PG_error,
#endif
/* non-lru isolated movable page */
PG_isolated = PG_reclaim,
/* Only valid for buddy pages. Used to track pages that are reported */
PG_reported = PG_uptodate,
#ifdef CONFIG_MEMORY_HOTPLUG
/* For self-hosted memmap pages */
PG_vmemmap_self_hosted = PG_owner_priv_1,
#endif
};
/* Upstream lines 473-697. */
__PAGEFLAG(Locked, locked, PF_NO_TAIL)
PAGEFLAG(Waiters, waiters, PF_ONLY_HEAD)
PAGEFLAG(Error, error, PF_NO_TAIL) TESTCLEARFLAG(Error, error, PF_NO_TAIL)
PAGEFLAG(Referenced, referenced, PF_HEAD)
TESTCLEARFLAG(Referenced, referenced, PF_HEAD)
__SETPAGEFLAG(Referenced, referenced, PF_HEAD)
PAGEFLAG(Dirty, dirty, PF_HEAD) TESTSCFLAG(Dirty, dirty, PF_HEAD)
__CLEARPAGEFLAG(Dirty, dirty, PF_HEAD)
PAGEFLAG(LRU, lru, PF_HEAD) __CLEARPAGEFLAG(LRU, lru, PF_HEAD)
TESTCLEARFLAG(LRU, lru, PF_HEAD)
PAGEFLAG(Active, active, PF_HEAD) __CLEARPAGEFLAG(Active, active, PF_HEAD)
TESTCLEARFLAG(Active, active, PF_HEAD)
PAGEFLAG(Workingset, workingset, PF_HEAD)
TESTCLEARFLAG(Workingset, workingset, PF_HEAD)
__PAGEFLAG(Slab, slab, PF_NO_TAIL)
__PAGEFLAG(SlobFree, slob_free, PF_NO_TAIL)
PAGEFLAG(Checked, checked, PF_NO_COMPOUND) /* Used by some filesystems */
/* Xen */
PAGEFLAG(Pinned, pinned, PF_NO_COMPOUND)
TESTSCFLAG(Pinned, pinned, PF_NO_COMPOUND)
PAGEFLAG(SavePinned, savepinned, PF_NO_COMPOUND);
PAGEFLAG(Foreign, foreign, PF_NO_COMPOUND);
PAGEFLAG(XenRemapped, xen_remapped, PF_NO_COMPOUND)
TESTCLEARFLAG(XenRemapped, xen_remapped, PF_NO_COMPOUND)
PAGEFLAG(Reserved, reserved, PF_NO_COMPOUND)
__CLEARPAGEFLAG(Reserved, reserved, PF_NO_COMPOUND)
__SETPAGEFLAG(Reserved, reserved, PF_NO_COMPOUND)
PAGEFLAG(SwapBacked, swapbacked, PF_NO_TAIL)
__CLEARPAGEFLAG(SwapBacked, swapbacked, PF_NO_TAIL)
__SETPAGEFLAG(SwapBacked, swapbacked, PF_NO_TAIL)
/*
* Private page markings that may be used by the filesystem that owns the page
* for its own purposes.
* - PG_private and PG_private_2 cause release_folio() and co to be invoked
*/
PAGEFLAG(Private, private, PF_ANY)
PAGEFLAG(Private2, private_2, PF_ANY) TESTSCFLAG(Private2, private_2, PF_ANY)
PAGEFLAG(OwnerPriv1, owner_priv_1, PF_ANY)
TESTCLEARFLAG(OwnerPriv1, owner_priv_1, PF_ANY)
/*
* Only test-and-set exist for PG_writeback. The unconditional operators are
* risky: they bypass page accounting.
*/
TESTPAGEFLAG(Writeback, writeback, PF_NO_TAIL)
TESTSCFLAG(Writeback, writeback, PF_NO_TAIL)
PAGEFLAG(MappedToDisk, mappedtodisk, PF_NO_TAIL)
/* PG_readahead is only used for reads; PG_reclaim is only for writes */
PAGEFLAG(Reclaim, reclaim, PF_NO_TAIL)
TESTCLEARFLAG(Reclaim, reclaim, PF_NO_TAIL)
PAGEFLAG(Readahead, readahead, PF_NO_COMPOUND)
TESTCLEARFLAG(Readahead, readahead, PF_NO_COMPOUND)
#ifdef CONFIG_HIGHMEM
/*
* Must use a macro here due to header dependency issues. page_zone() is not
* available at this point.
*/
#define PageHighMem(__p) is_highmem_idx(page_zonenum(__p))
#else
PAGEFLAG_FALSE(HighMem, highmem)
#endif
#ifdef CONFIG_SWAP
static __always_inline bool folio_test_swapcache(struct folio *folio)
{
return folio_test_swapbacked(folio) &&
test_bit(PG_swapcache, folio_flags(folio, 0));
}
static __always_inline bool PageSwapCache(struct page *page)
{
return folio_test_swapcache(page_folio(page));
}
SETPAGEFLAG(SwapCache, swapcache, PF_NO_TAIL)
CLEARPAGEFLAG(SwapCache, swapcache, PF_NO_TAIL)
#else
PAGEFLAG_FALSE(SwapCache, swapcache)
#endif
PAGEFLAG(Unevictable, unevictable, PF_HEAD)
__CLEARPAGEFLAG(Unevictable, unevictable, PF_HEAD)
TESTCLEARFLAG(Unevictable, unevictable, PF_HEAD)
#ifdef CONFIG_MMU
PAGEFLAG(Mlocked, mlocked, PF_NO_TAIL)
__CLEARPAGEFLAG(Mlocked, mlocked, PF_NO_TAIL)
TESTSCFLAG(Mlocked, mlocked, PF_NO_TAIL)
#else
PAGEFLAG_FALSE(Mlocked, mlocked) __CLEARPAGEFLAG_NOOP(Mlocked, mlocked)
TESTSCFLAG_FALSE(Mlocked, mlocked)
#endif
#ifdef CONFIG_ARCH_USES_PG_UNCACHED
PAGEFLAG(Uncached, uncached, PF_NO_COMPOUND)
#else
PAGEFLAG_FALSE(Uncached, uncached)
#endif
#ifdef CONFIG_MEMORY_FAILURE
PAGEFLAG(HWPoison, hwpoison, PF_ANY)
TESTSCFLAG(HWPoison, hwpoison, PF_ANY)
#define __PG_HWPOISON (1UL << PG_hwpoison)
#define MAGIC_HWPOISON 0x48575053U /* HWPS */
extern void SetPageHWPoisonTakenOff(struct page *page);
extern void ClearPageHWPoisonTakenOff(struct page *page);
extern bool take_page_off_buddy(struct page *page);
extern bool put_page_back_buddy(struct page *page);
#else
PAGEFLAG_FALSE(HWPoison, hwpoison)
#define __PG_HWPOISON 0
#endif
#if defined(CONFIG_PAGE_IDLE_FLAG) && defined(CONFIG_64BIT)
TESTPAGEFLAG(Young, young, PF_ANY)
SETPAGEFLAG(Young, young, PF_ANY)
TESTCLEARFLAG(Young, young, PF_ANY)
PAGEFLAG(Idle, idle, PF_ANY)
#endif
#ifdef CONFIG_KASAN_HW_TAGS
PAGEFLAG(SkipKASanPoison, skip_kasan_poison, PF_HEAD)
#else
PAGEFLAG_FALSE(SkipKASanPoison, skip_kasan_poison)
#endif
/*
* PageReported() is used to track reported free pages within the Buddy
* allocator. We can use the non-atomic version of the test and set
* operations as both should be shielded with the zone lock to prevent
* any possible races on the setting or clearing of the bit.
*/
__PAGEFLAG(Reported, reported, PF_NO_COMPOUND)
#ifdef CONFIG_MEMORY_HOTPLUG
PAGEFLAG(VmemmapSelfHosted, vmemmap_self_hosted, PF_ANY)
#else
PAGEFLAG_FALSE(VmemmapSelfHosted, vmemmap_self_hosted)
#endif
/*
* On an anonymous page mapped into a user virtual memory area,
* page->mapping points to its anon_vma, not to a struct address_space;
* with the PAGE_MAPPING_ANON bit set to distinguish it. See rmap.h.
*
* On an anonymous page in a VM_MERGEABLE area, if CONFIG_KSM is enabled,
* the PAGE_MAPPING_MOVABLE bit may be set along with the PAGE_MAPPING_ANON
* bit; and then page->mapping points, not to an anon_vma, but to a private
* structure which KSM associates with that merged page. See ksm.h.
*
* PAGE_MAPPING_KSM without PAGE_MAPPING_ANON is used for non-lru movable
* page and then page->mapping points to a struct movable_operations.
*
* Please note that, confusingly, "page_mapping" refers to the inode
* address_space which maps the page from disk; whereas "page_mapped"
* refers to user virtual address space into which the page is mapped.
*/
#define PAGE_MAPPING_ANON 0x1
#define PAGE_MAPPING_MOVABLE 0x2
#define PAGE_MAPPING_KSM (PAGE_MAPPING_ANON | PAGE_MAPPING_MOVABLE)
#define PAGE_MAPPING_FLAGS (PAGE_MAPPING_ANON | PAGE_MAPPING_MOVABLE)
/*
* Different with flags above, this flag is used only for fsdax mode. It
* indicates that this page->mapping is now under reflink case.
*/
#define PAGE_MAPPING_DAX_COW 0x1
static __always_inline bool folio_mapping_flags(struct folio *folio)
{
return ((unsigned long)folio->mapping & PAGE_MAPPING_FLAGS) != 0;
}
static __always_inline int PageMappingFlags(struct page *page)
{
return ((unsigned long)page->mapping & PAGE_MAPPING_FLAGS) != 0;
}
static __always_inline bool folio_test_anon(struct folio *folio)
{
return ((unsigned long)folio->mapping & PAGE_MAPPING_ANON) != 0;
}
static __always_inline bool PageAnon(struct page *page)
{
return folio_test_anon(page_folio(page));
}
static __always_inline bool __folio_test_movable(const struct folio *folio)
{
return ((unsigned long)folio->mapping & PAGE_MAPPING_FLAGS) ==
PAGE_MAPPING_MOVABLE;
}
static __always_inline int __PageMovable(struct page *page)
{
return ((unsigned long)page->mapping & PAGE_MAPPING_FLAGS) ==
PAGE_MAPPING_MOVABLE;
}
#ifdef CONFIG_KSM
/*
* A KSM page is one of those write-protected "shared pages" or "merged pages"
* which KSM maps into multiple mms, wherever identical anonymous page content
* is found in VM_MERGEABLE vmas. It's a PageAnon page, pointing not to any
* anon_vma, but to that page's node of the stable tree.
*/
static __always_inline bool folio_test_ksm(struct folio *folio)
{
return ((unsigned long)folio->mapping & PAGE_MAPPING_FLAGS) ==
PAGE_MAPPING_KSM;
}
static __always_inline bool PageKsm(struct page *page)
{
return folio_test_ksm(page_folio(page));
}
#else
TESTPAGEFLAG_FALSE(Ksm, ksm)
#endif
이어서 같은 header의 poison 판별, head·large 판별, HugeTLB·THP 조건부 helper와 PageAnonExclusive() 계열의 전체 정의입니다. page-type 생성 호출도 포함하므로 앞에서 본 PAGE_TYPE_OPS가 어떤 이름을 만드는지 확인할 수 있습니다. PageHead()의 fake-head 정규화는 compound descriptor 글의 실제 정의와 연결됩니다.
/* SPDX-License-Identifier: GPL-2.0 */
/* Linux v6.1, commit 830b3c68c1fb1e9176028d02ef86f3cf76aa2476.
* Source: include/linux/page-flags.h
* Whole helper definitions and complete configuration groups.
* Kernel context; not a standalone translation unit.
*/
/* Upstream lines 300-313. */
#define PAGE_POISON_PATTERN -1l
static inline int PagePoisoned(const struct page *page)
{
return READ_ONCE(page->flags) == PAGE_POISON_PATTERN;
}
#ifdef CONFIG_DEBUG_VM
void page_init_poison(struct page *page, size_t size);
#else
static inline void page_init_poison(struct page *page, size_t size)
{
}
#endif
/* Upstream lines 780-804. */
static __always_inline bool folio_test_head(struct folio *folio)
{
return test_bit(PG_head, folio_flags(folio, FOLIO_PF_ANY));
}
static __always_inline int PageHead(struct page *page)
{
PF_POISONED_CHECK(page);
return test_bit(PG_head, &page->flags) && !page_is_fake_head(page);
}
__SETPAGEFLAG(Head, head, PF_ANY)
__CLEARPAGEFLAG(Head, head, PF_ANY)
CLEARPAGEFLAG(Head, head, PF_ANY)
/**
* folio_test_large() - Does this folio contain more than one page?
* @folio: The folio to test.
*
* Return: True if the folio is larger than one page.
*/
static inline bool folio_test_large(struct folio *folio)
{
return folio_test_head(folio);
}
/* Upstream lines 825-914. */
#ifdef CONFIG_HUGETLB_PAGE
int PageHuge(struct page *page);
int PageHeadHuge(struct page *page);
static inline bool folio_test_hugetlb(struct folio *folio)
{
return PageHeadHuge(&folio->page);
}
#else
TESTPAGEFLAG_FALSE(Huge, hugetlb)
TESTPAGEFLAG_FALSE(HeadHuge, headhuge)
#endif
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
/*
* PageHuge() only returns true for hugetlbfs pages, but not for
* normal or transparent huge pages.
*
* PageTransHuge() returns true for both transparent huge and
* hugetlbfs pages, but not normal pages. PageTransHuge() can only be
* called only in the core VM paths where hugetlbfs pages can't exist.
*/
static inline int PageTransHuge(struct page *page)
{
VM_BUG_ON_PAGE(PageTail(page), page);
return PageHead(page);
}
static inline bool folio_test_transhuge(struct folio *folio)
{
return folio_test_head(folio);
}
/*
* PageTransCompound returns true for both transparent huge pages
* and hugetlbfs pages, so it should only be called when it's known
* that hugetlbfs pages aren't involved.
*/
static inline int PageTransCompound(struct page *page)
{
return PageCompound(page);
}
/*
* PageTransTail returns true for both transparent huge pages
* and hugetlbfs pages, so it should only be called when it's known
* that hugetlbfs pages aren't involved.
*/
static inline int PageTransTail(struct page *page)
{
return PageTail(page);
}
/*
* PageDoubleMap indicates that the compound page is mapped with PTEs as well
* as PMDs.
*
* This is required for optimization of rmap operations for THP: we can postpone
* per small page mapcount accounting (and its overhead from atomic operations)
* until the first PMD split.
*
* For the page PageDoubleMap means ->_mapcount in all sub-pages is offset up
* by one. This reference will go away with last compound_mapcount.
*
* See also __split_huge_pmd_locked() and page_remove_anon_compound_rmap().
*/
PAGEFLAG(DoubleMap, double_map, PF_SECOND)
TESTSCFLAG(DoubleMap, double_map, PF_SECOND)
#else
TESTPAGEFLAG_FALSE(TransHuge, transhuge)
TESTPAGEFLAG_FALSE(TransCompound, transcompound)
TESTPAGEFLAG_FALSE(TransCompoundMap, transcompoundmap)
TESTPAGEFLAG_FALSE(TransTail, transtail)
PAGEFLAG_FALSE(DoubleMap, double_map)
TESTSCFLAG_FALSE(DoubleMap, double_map)
#endif
#if defined(CONFIG_MEMORY_FAILURE) && defined(CONFIG_TRANSPARENT_HUGEPAGE)
/*
* PageHasHWPoisoned indicates that at least one subpage is hwpoisoned in the
* compound page.
*
* This flag is set by hwpoison handler. Cleared by THP split or free page.
*/
PAGEFLAG(HasHWPoisoned, has_hwpoisoned, PF_SECOND)
TESTSCFLAG(HasHWPoisoned, has_hwpoisoned, PF_SECOND)
#else
PAGEFLAG_FALSE(HasHWPoisoned, has_hwpoisoned)
TESTSCFLAG_FALSE(HasHWPoisoned, has_hwpoisoned)
#endif
/* Upstream lines 969-1016. */
/*
* PageBuddy() indicates that the page is free and in the buddy system
* (see mm/page_alloc.c).
*/
PAGE_TYPE_OPS(Buddy, buddy)
/*
* PageOffline() indicates that the page is logically offline although the
* containing section is online. (e.g. inflated in a balloon driver or
* not onlined when onlining the section).
* The content of these pages is effectively stale. Such pages should not
* be touched (read/write/dump/save) except by their owner.
*
* If a driver wants to allow to offline unmovable PageOffline() pages without
* putting them back to the buddy, it can do so via the memory notifier by
* decrementing the reference count in MEM_GOING_OFFLINE and incrementing the
* reference count in MEM_CANCEL_OFFLINE. When offlining, the PageOffline()
* pages (now with a reference count of zero) are treated like free pages,
* allowing the containing memory block to get offlined. A driver that
* relies on this feature is aware that re-onlining the memory block will
* require to re-set the pages PageOffline() and not giving them to the
* buddy via online_page_callback_t.
*
* There are drivers that mark a page PageOffline() and expect there won't be
* any further access to page content. PFN walkers that read content of random
* pages should check PageOffline() and synchronize with such drivers using
* page_offline_freeze()/page_offline_thaw().
*/
PAGE_TYPE_OPS(Offline, offline)
extern void page_offline_freeze(void);
extern void page_offline_thaw(void);
extern void page_offline_begin(void);
extern void page_offline_end(void);
/*
* Marks pages in use as page tables.
*/
PAGE_TYPE_OPS(Table, table)
/*
* Marks guardpages used with debug_pagealloc.
*/
PAGE_TYPE_OPS(Guard, guard)
extern bool is_free_buddy_page(struct page *page);
PAGEFLAG(Isolated, isolated, PF_ANY);
/* Upstream lines 1018-1045. */
static __always_inline int PageAnonExclusive(struct page *page)
{
VM_BUG_ON_PGFLAGS(!PageAnon(page), page);
VM_BUG_ON_PGFLAGS(PageHuge(page) && !PageHead(page), page);
return test_bit(PG_anon_exclusive, &PF_ANY(page, 1)->flags);
}
static __always_inline void SetPageAnonExclusive(struct page *page)
{
VM_BUG_ON_PGFLAGS(!PageAnon(page) || PageKsm(page), page);
VM_BUG_ON_PGFLAGS(PageHuge(page) && !PageHead(page), page);
set_bit(PG_anon_exclusive, &PF_ANY(page, 1)->flags);
}
static __always_inline void ClearPageAnonExclusive(struct page *page)
{
VM_BUG_ON_PGFLAGS(!PageAnon(page) || PageKsm(page), page);
VM_BUG_ON_PGFLAGS(PageHuge(page) && !PageHead(page), page);
clear_bit(PG_anon_exclusive, &PF_ANY(page, 1)->flags);
}
static __always_inline void __ClearPageAnonExclusive(struct page *page)
{
VM_BUG_ON_PGFLAGS(!PageAnon(page), page);
VM_BUG_ON_PGFLAGS(PageHuge(page) && !PageHead(page), page);
__clear_bit(PG_anon_exclusive, &PF_ANY(page, 1)->flags);
}
folio_test_hugetlb()가 호출하는 두 함수는 header 선언으로 끝나지 않습니다. 다음은 mm/hugetlb.c의 PageHuge()와 PageHeadHuge() 전체 정의이며 SPDX는 GPL-2.0-only입니다. 전자는 compound를 head로 정규화하고, 후자는 처음부터 head인지를 확인한 뒤 첫 tail의 destructor를 검사합니다. 큰 order라는 사실만으로 HugeTLB라고 판별하지 않습니다.
// SPDX-License-Identifier: GPL-2.0-only
/*
* Generic hugetlb support.
* (C) Nadia Yvette Chambers, April 2004
*/
/* Linux v6.1, commit 830b3c68c1fb1e9176028d02ef86f3cf76aa2476.
* mm/hugetlb.c lines 1891-1912: whole PageHuge and PageHeadHuge.
* Kernel context; not a standalone translation unit.
*/
int PageHuge(struct page *page)
{
if (!PageCompound(page))
return 0;
page = compound_head(page);
return page[1].compound_dtor == HUGETLB_PAGE_DTOR;
}
EXPORT_SYMBOL_GPL(PageHuge);
/*
* PageHeadHuge() only returns true for hugetlbfs head page, but not for
* normal or transparent huge pages.
*/
int PageHeadHuge(struct page *page_head)
{
if (!PageHead(page_head))
return 0;
return page_head[1].compound_dtor == HUGETLB_PAGE_DTOR;
}
EXPORT_SYMBOL_GPL(PageHeadHuge);
movable 판별은 migrate.h의 COMPACTION 조건과 wrapper도 필요합니다. 앞서 제시한 mm/compaction.c의 실제 구현과 설정이 없는 경우의 false/no-op 정의를 구별합니다. 다음 범위의 SPDX는 GPL-2.0입니다.
/* SPDX-License-Identifier: GPL-2.0 */
/* Linux v6.1 include/linux/migrate.h, upstream lines 105-123. */
#ifdef CONFIG_COMPACTION
bool PageMovable(struct page *page);
void __SetPageMovable(struct page *page, const struct movable_operations *ops);
void __ClearPageMovable(struct page *page);
#else
static inline bool PageMovable(struct page *page) { return false; }
static inline void __SetPageMovable(struct page *page,
const struct movable_operations *ops)
{
}
static inline void __ClearPageMovable(struct page *page)
{
}
#endif
static inline bool folio_test_movable(struct folio *folio)
{
return PageMovable(&folio->page);
}
마지막으로 page_idle.h의 전체 header입니다. SPDX는 GPL-2.0이며 원래 include와 configuration guard도 보존했습니다. 64-bit의 page flag 구현, 32-bit의 page extension 구현, 기능이 없는 경우의 stub을 함께 확인할 수 있습니다. 32-bit 경로는 page extension을 얻지 못하면 false를 반환하거나 변경을 건너뛰며, 얻었다면 사용 뒤 page_ext_put()으로 접근 수명을 마칩니다. 앞 표의 offset을 모든 build의 실제 저장 위치로 일반화해서는 안 됩니다. 이 source들은 kernel의 supporting type·macro를 필요로 하며 별도로 실행한 예제가 아닙니다. license 본문은 GPL-2.0에 함께 둡니다.
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _LINUX_MM_PAGE_IDLE_H
#define _LINUX_MM_PAGE_IDLE_H
#include <linux/bitops.h>
#include <linux/page-flags.h>
#include <linux/page_ext.h>
#ifdef CONFIG_PAGE_IDLE_FLAG
#ifndef CONFIG_64BIT
/*
* If there is not enough space to store Idle and Young bits in page flags, use
* page ext flags instead.
*/
static inline bool folio_test_young(struct folio *folio)
{
struct page_ext *page_ext = page_ext_get(&folio->page);
bool page_young;
if (unlikely(!page_ext))
return false;
page_young = test_bit(PAGE_EXT_YOUNG, &page_ext->flags);
page_ext_put(page_ext);
return page_young;
}
static inline void folio_set_young(struct folio *folio)
{
struct page_ext *page_ext = page_ext_get(&folio->page);
if (unlikely(!page_ext))
return;
set_bit(PAGE_EXT_YOUNG, &page_ext->flags);
page_ext_put(page_ext);
}
static inline bool folio_test_clear_young(struct folio *folio)
{
struct page_ext *page_ext = page_ext_get(&folio->page);
bool page_young;
if (unlikely(!page_ext))
return false;
page_young = test_and_clear_bit(PAGE_EXT_YOUNG, &page_ext->flags);
page_ext_put(page_ext);
return page_young;
}
static inline bool folio_test_idle(struct folio *folio)
{
struct page_ext *page_ext = page_ext_get(&folio->page);
bool page_idle;
if (unlikely(!page_ext))
return false;
page_idle = test_bit(PAGE_EXT_IDLE, &page_ext->flags);
page_ext_put(page_ext);
return page_idle;
}
static inline void folio_set_idle(struct folio *folio)
{
struct page_ext *page_ext = page_ext_get(&folio->page);
if (unlikely(!page_ext))
return;
set_bit(PAGE_EXT_IDLE, &page_ext->flags);
page_ext_put(page_ext);
}
static inline void folio_clear_idle(struct folio *folio)
{
struct page_ext *page_ext = page_ext_get(&folio->page);
if (unlikely(!page_ext))
return;
clear_bit(PAGE_EXT_IDLE, &page_ext->flags);
page_ext_put(page_ext);
}
#endif /* !CONFIG_64BIT */
#else /* !CONFIG_PAGE_IDLE_FLAG */
static inline bool folio_test_young(struct folio *folio)
{
return false;
}
static inline void folio_set_young(struct folio *folio)
{
}
static inline bool folio_test_clear_young(struct folio *folio)
{
return false;
}
static inline bool folio_test_idle(struct folio *folio)
{
return false;
}
static inline void folio_set_idle(struct folio *folio)
{
}
static inline void folio_clear_idle(struct folio *folio)
{
}
#endif /* CONFIG_PAGE_IDLE_FLAG */
static inline bool page_is_young(struct page *page)
{
return folio_test_young(page_folio(page));
}
static inline void set_page_young(struct page *page)
{
folio_set_young(page_folio(page));
}
static inline bool test_and_clear_page_young(struct page *page)
{
return folio_test_clear_young(page_folio(page));
}
static inline bool page_is_idle(struct page *page)
{
return folio_test_idle(page_folio(page));
}
static inline void set_page_idle(struct page *page)
{
folio_set_idle(page_folio(page));
}
static inline void clear_page_idle(struct page *page)
{
folio_clear_idle(page_folio(page));
}
#endif /* _LINUX_MM_PAGE_IDLE_H */
v6.18에는 같은 이름의 짝이 남아 있지 않을 수 있습니다
위 조합은 v6.1의 계약입니다. v6.18 page-flags.h는 folio 전용 FOLIO_FLAG 계열을 늘렸으며 flags.f 표현과 typed-page 표현도 사용합니다. 아래는 고정 tag 두 개에서 직접 비교한 차이입니다.
| v6.1에서 보던 형태 | v6.18에서 확인할 형태 |
|---|---|
| Waiters·Referenced·Active의 Page/folio 동시 생성 | 해당 선언은 folio 전용 생성 계열입니다. 옛 Page 조합을 이 header에서 얻지 못합니다. |
| SwapBacked·Private2·MappedToDisk·Readahead·Unevictable·Mlocked의 동시 생성 | folio 전용 선언으로 이동한 조합을 확인해야 합니다. |
PG_double_map·PF_SECOND의 DoubleMap |
같은 double_map 선언은 없고 large_rmappable·partially_mapped 등 다른 장부가 있습니다. 일대일 rename으로 취급하지 않습니다. |
PG_anon_exclusive = PG_mappedtodisk |
두 상태는 PG_owner_2를 문맥별로 재사용합니다. |
| bit clear/set 방식의 page_type | 상위 byte의 enum pagetype를 비교·기록하며 FOLIO_TYPE_OPS도 있습니다. |
| Slab·SlobFree·Error·Uncached·SkipKASanPoison의 옛 생성 선언 | 이 header에는 같은 선언이 없습니다. 다른 tag의 dump에 옛 bit 번호를 대입하지 않습니다. |
PAGE_MAPPING_*의 non-LRU movable 표현 |
folio mapping 표현과 movable-operations 표현이 달라졌으므로 v6.1 tag 검사를 이식하지 않습니다. |
이 비교는 upstream v6.18에 한정합니다. 실제 WSL 6.18.33.2와 동일한 build라는 뜻은 아닙니다. source를 읽을 때는 helper 이름, 생성 선언, configuration의 disabled 분기, 실제 caller protocol을 같은 tag 안에서 함께 확인해야 합니다.
참고 자료
- Lorenzo Stoakes, The Linux Memory Manager, Early Access, 2025, Physical Memory 장.
- Linux
v6.1: page flags와 생성 macro, idle·young의 대체 구현, movable interface, movable 구현, hugetlb 판별, debug 조건. - Linux
v6.18: 고정 tag의 helper·type 구현. - Code의 원본 SPDX 표시는 각 code block에 따릅니다. Linux GPL-2.0 license text와 원본 파일의 조건을 함께 확인합니다.