filesystem lock을 잡은 채 memory를 요청했는데 reclaim이 다시 filesystem으로 들어오면 deadlock의 조건이 만들어질 수 있습니다. interrupt나 다른 atomic context에서는 allocation을 위해 잠들 수도 없습니다. 이런 제약은 “몇 byte가 필요한가”와 별개로 caller가 allocator에 전달해야 하는 정보입니다.
GFP는 Get Free Pages에서 온 이름입니다. GFP mask는 주소 범위, 배치 의도, reclaim 허용 범위, reserve 접근과 실패 정책을 함께 나타냅니다. Linux v6.1을 기준으로 각 선택이 어떤 행동을 허용하는지 살펴보겠습니다. 최신 kernel에서 같은 내부 bit 이름이 그대로 남는다고 가정하지는 않습니다.
먼저 잠들 수 있는지와 무엇을 재진입할 수 있는지 정합니다
일반적인 GFP_KERNEL은 direct reclaim을 허용하는 sleepable kernel allocation입니다. lock을 잡은 모든 문맥에서 쓸 수 있다는 뜻은 아닙니다. GFP_NOWAIT는 direct reclaim에 들어가 기다리지 않으며, GFP_ATOMIC은 잠들 수 없는 caller를 위해 reserve 접근 등 더 높은 우선순위를 요청합니다. 둘 다 반드시 성공하는 allocation은 아닙니다. NMI나 매우 엄격한 non-preemptive context까지 무조건 지원한다고 일반화해서도 안 됩니다.
__GFP_DIRECT_RECLAIM은 caller가 직접 reclaim에 참여할 수 있음을, __GFP_KSWAPD_RECLAIM은 background reclaim을 깨울 수 있음을 나타냅니다. __GFP_RECLAIM은 이 둘을 묶는 표현입니다. 직접 잠들지 않는 요청이라도 kswapd 동작을 요청할 수 있으므로 “reclaim 관련 bit 하나라도 있으면 caller가 sleep한다”는 해석은 맞지 않습니다.
__GFP_IO는 reclaim에서 physical I/O를 시작할 수 있는지, __GFP_FS는 filesystem에 재진입할 수 있는지를 제한합니다. GFP_NOFS는 filesystem 재진입을, GFP_NOIO는 I/O까지 더 넓게 제한합니다. 이 조합도 clean cache나 적합한 slab을 회수하는 direct reclaim 자체를 전부 금지하는 것은 아닙니다.
실패 정책은 retry의 강도를 바꿉니다
__GFP_NORETRY는 가벼운 회수 뒤 실패를 받아들이는 요청에 사용합니다. __GFP_RETRY_MAYFAIL은 더 노력하되 실패를 허용하고, __GFP_NOFAIL은 caller가 실패를 처리하지 않는 계약을 나타냅니다. NOFAIL을 붙이면 부적절한 atomic context나 불가능한 크기까지 해결되는 것이 아닙니다. sleep 가능한 문맥과 지원되는 요청 조건을 먼저 지켜야 합니다.
PAGE_ALLOC_COSTLY_ORDER보다 큰 allocation은 대체 경로를 고려해야 하는 costly 요청으로 취급됩니다. v6.1에서 이 기준은 order 3입니다. NOFAIL에 대해서는 order 1보다 큰 요청을 경고하는 조건도 있으므로 “costly가 아니면 어떤 order든 NOFAIL에 적합하다”는 결론은 틀립니다. retry modifier는 해당 GFP가 reclaim을 허용하는 조건 안에서 해석해야 합니다.
__GFP_NOWARN은 allocation 실패 경고를 억제하는 선택입니다. 실패 가능성을 없애거나 state 검증을 모두 끄는 flag가 아닙니다. 정상적인 실패를 빈번히 처리하는 caller가 kernel log를 불필요하게 채우지 않도록 하는 목적과 failure handling은 별개입니다. NORETRY·RETRY_MAYFAIL처럼 실패를 허용하는 조합은 OOM으로 무조건 밀어붙이는 계약이 아니며, direct reclaim 자체가 금지된 요청도 그 경로에서 OOM killer를 호출해 해결하는 요청으로 읽으면 안 됩니다.
주소 제약과 배치·청구 조건을 나눕니다
__GFP_DMA, __GFP_DMA32, __GFP_HIGHMEM, __GFP_MOVABLE은 허용된 조합을 zone 선택 표에서 해석합니다. 예를 들어 HIGHMEM과 MOVABLE 조합은 ZONE_MOVABLE 선택과 연결될 수 있어 MOVABLE이 mobility 분류에만 쓰이는 bit도 아닙니다. 이 bit들을 모두 OR해서 가장 넓은 범위를 얻는 인터페이스가 아닙니다. DMA 계열은 architecture별 주소 제약이며, GFP_DMA를 부족할 때 꺼내 쓰는 일반 emergency reserve처럼 사용해서도 안 됩니다.
GFP_USER는 user를 위한 allocation이면서 kernel·hardware의 직접 접근 조건을 갖는 경우에 사용합니다. GFP_HIGHUSER는 HIGHMEM을 허용하고 GFP_HIGHUSER_MOVABLE은 이동 가능한 user memory라는 성격을 더합니다. 사용자 주소에 연결된다는 공통점만으로 같은 mask를 선택할 수는 없습니다. public 조합 GFP_DMA32도 device의 32-bit DMA 주소 제약에 관련되며 user memory용 조합의 대체물이 아닙니다.
__GFP_MOVABLE·__GFP_RECLAIMABLE은 mobility grouping에, __GFP_WRITE는 dirty page가 될 가능성을 고려한 배치에 관여합니다. dirty spreading은 fast-path hint이며 모든 slow-path 후보에서 같은 방식으로 강제되는 배치 보장은 아닙니다. __GFP_HARDWALL은 cpuset 제약과, __GFP_THISNODE는 요청 node 밖 fallback 금지와 연결됩니다.
__GFP_ACCOUNT는 kmemcg 청구를 요청합니다. GFP_KERNEL_ACCOUNT는 이 의미를 일반 kernel 조합에 더합니다. 실제 page 확보 뒤 charge가 실패해 allocation이 취소될 수도 있으므로, 물리 block을 얻는 일과 caller에게 성공을 반환하는 일은 구별됩니다.
Reserve는 forward progress를 위한 권한입니다
__GFP_HIGH는 우선순위가 높은 caller에 대해 watermark 문턱을 완화하는 데 관여합니다. v6.1의 __GFP_ATOMIC도 이 문맥에서 등장합니다. __GFP_MEMALLOC은 memory를 곧 돌려놓을 수 있는 reclaim 관련 caller 등을 위한 강한 reserve 접근입니다. 이 권한을 일반적인 실패 회피 수단으로 확장하면 회수 경로 자체의 진행을 막을 수 있습니다.
__GFP_NOMEMALLOC은 reserve 접근을 명시적으로 금지하며 MEMALLOC과 함께 있으면 금지 쪽이 우선합니다. 요청 이름에 ATOMIC이 있다고 모든 watermark와 protection을 무시하거나 무한한 memory를 확보하는 것은 아닙니다. 필요하다면 caller의 사전 allocation pool 같은 설계도 검토해야 합니다.
버전 차이도 있습니다. v6.18에는 public 조합 GFP_ATOMIC이 있지만 v6.1의 내부 __GFP_ATOMIC bit를 같은 방식으로 찾으면 안 됩니다. public API 이름과 내부 mask 구성이 같은 속도로 변하지 않는 사례입니다.
초기화와 계측 flag는 configuration을 확인합니다
__GFP_ZERO는 성공한 page의 zeroing을 요청하고 __GFP_COMP는 higher-order 범위에 compound metadata를 준비하도록 합니다. 연속 allocation과 compound 수명 계약을 구별해야 하므로 COMP를 단순한 성능 hint로 읽으면 안 됩니다.
tag-based KASAN 환경에서는 __GFP_ZEROTAGS, __GFP_SKIP_KASAN_UNPOISON, __GFP_SKIP_KASAN_POISON 등이 별도 의미를 갖습니다. ZEROTAGS는 data zeroing이 수행되는 조건과 연결되고 __GFP_SKIP_ZERO와도 관계가 있습니다. 이런 flag를 모든 architecture의 일반적인 memset 대체 옵션으로 사용해서는 안 됩니다.
__GFP_NOLOCKDEP는 GFP context의 lockdep 추적을 제한합니다. direct reclaim을 금지하거나 실제 lock을 취득하지 않게 하는 bit가 아닙니다. 진단 기능의 제어와 allocator 행동의 제어를 혼동하면 deadlock 원인을 감춘 채 제약은 그대로 남길 수 있습니다.
Task scope는 아래쪽 allocation에도 제약을 전달합니다
깊은 호출 경로의 모든 allocation mask를 직접 수정하기 어려울 때 memalloc_nofs_save()·restore()나 NOIO scope를 사용합니다. current_gfp_context()는 task의 PF_MEMALLOC_NOFS이면 FS를, NOIO이면 IO와 FS를 제거하는 식으로 요청을 제한합니다. helper 안에서 GFP_KERNEL을 사용해도 바깥 scope의 제약을 상속할 수 있습니다.

저장한 상태를 error path에서도 복구해야 합니다. 중첩 scope 안에서 나왔다고 해당 bit를 무조건 clear하면 바깥 scope를 깨뜨릴 수 있습니다. PFMEMALLOC 관련 context는 reserve 접근과 연결되고, PF_MEMALLOC_PIN에 따른 movable 제한은 장기 pin 용도와 연결됩니다. 이 task flag가 실제 pin reference 자체를 취득하는 것은 아닙니다.
PF_MEMALLOC 문맥은 reclaim 작업이 다시 같은 direct reclaim으로 재진입하는 것을 피하는 조건과도 연결됩니다. reserve 접근 권한 하나만 보고 이 task가 일반 allocation과 똑같이 reclaim을 반복한다고 가정하면 안 됩니다.
THP용 GFP_TRANSHUGE_LIGHT와 GFP_TRANSHUGE도 같은 원칙으로 읽습니다. v6.1의 LIGHT는 reclaim을 빼고 실패를 빨리 받아들이는 조합이며 TRANSHUGE는 여기에 direct reclaim을 더합니다. huge allocation이라는 크기 의도뿐 아니라 reclaim·compaction에 얼마나 개입할지 정한 조합입니다. 이름을 외우기보다 호출 문맥, 실패 처리, 재진입 허용 범위를 먼저 정하면 GFP mask가 훨씬 명확해집니다.
Public 조합의 실제 정의를 펼쳐 봅니다
같은 이름을 쓰더라도 내부 bit 조합은 tag에 따라 달라질 수 있습니다. 아래는 Linux v6.1의 include/linux/gfp_types.h에 있는 public 조합 정의 전체입니다. SPDX는 GPL-2.0이며 공백만 정리했습니다. 숫자 bit 위치를 외부 ABI처럼 고정하려는 표가 아닙니다.
#define GFP_ATOMIC (__GFP_HIGH|__GFP_ATOMIC|__GFP_KSWAPD_RECLAIM)
#define GFP_KERNEL (__GFP_RECLAIM | __GFP_IO | __GFP_FS)
#define GFP_KERNEL_ACCOUNT (GFP_KERNEL | __GFP_ACCOUNT)
#define GFP_NOWAIT (__GFP_KSWAPD_RECLAIM)
#define GFP_NOIO (__GFP_RECLAIM)
#define GFP_NOFS (__GFP_RECLAIM | __GFP_IO)
#define GFP_USER (__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL)
#define GFP_DMA __GFP_DMA
#define GFP_DMA32 __GFP_DMA32
#define GFP_HIGHUSER (GFP_USER | __GFP_HIGHMEM)
#define GFP_HIGHUSER_MOVABLE (GFP_HIGHUSER | __GFP_MOVABLE | \
__GFP_SKIP_KASAN_POISON | __GFP_SKIP_KASAN_UNPOISON)
#define GFP_TRANSHUGE_LIGHT ((GFP_HIGHUSER_MOVABLE | __GFP_COMP | \
__GFP_NOMEMALLOC | __GFP_NOWARN) & ~__GFP_RECLAIM)
#define GFP_TRANSHUGE (GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM)
GFP_DMA와 GFP_DMA32는 여기서 주소 zone bit만 지정합니다. 단독으로 쓰면 GFP_KERNEL의 reclaim·IO·FS 허용이 자동으로 따라오지 않습니다. 또한 이 page-allocation mask를 kmalloc()의 모든 cache 선택에 그대로 대입할 수는 없습니다. 이 tag의 header는 DMA32용 kmalloc cache 배열이 구현되지 않았다는 제한도 명시합니다.
GFP_HIGHUSER_MOVABLE에는 이 tag에서 KASAN의 poison 생략뿐 아니라 unpoison 생략도 포함됩니다. 두 동작의 시점은 각각 free와 allocation으로 다릅니다. CONFIG_KASAN_HW_TAGS가 없으면 두 내부 bit의 값은 0입니다. __GFP_SKIP_ZERO도 같은 구성 조건을 가지며, 사용될 때는 data·tag zeroing을 건너뛰는 분기에 관여합니다. __GFP_ZEROTAGS는 실제 zeroing이 수행되는 조건과 묶어 읽어야 합니다. 이름이 선언되어 있다는 사실과 현재 build에서 동작이 있다는 사실은 같지 않습니다.
LIGHT는 __GFP_RECLAIM을 지우므로 background reclaim 요청도 빠집니다. TRANSHUGE는 DIRECT만 다시 더하며 KSWAPD까지 자동 복구하지 않습니다. GFP_NOWAIT도 direct reclaim을 하지 않는 계약이지 spinlock 경합이나 모든 실행 지연까지 0으로 만드는 실시간 보장은 아닙니다.
Task 제약을 적용하는 함수와 reserve 판정을 나눕니다
다음은 include/linux/sched/mm.h의 current_gfp_context() 전문입니다. SPDX는 GPL-2.0입니다. NOIO를 먼저 처리하고 NOFS를 그다음에 처리하며, PIN은 독립적으로 MOVABLE을 제거합니다.
static inline gfp_t current_gfp_context(gfp_t flags)
{
unsigned int pflags = READ_ONCE(current->flags);
if (unlikely(pflags & (PF_MEMALLOC_NOIO | PF_MEMALLOC_NOFS | PF_MEMALLOC_PIN))) {
/*
* NOIO implies both NOIO and NOFS and it is a weaker context
* so always make sure it makes precedence
*/
if (pflags & PF_MEMALLOC_NOIO)
flags &= ~(__GFP_IO | __GFP_FS);
else if (pflags & PF_MEMALLOC_NOFS)
flags &= ~__GFP_FS;
if (pflags & PF_MEMALLOC_PIN)
flags &= ~__GFP_MOVABLE;
}
return flags;
}
PF_MEMALLOC_PIN은 memalloc_pin_save()·memalloc_pin_restore()로 scope를 열고 닫습니다. PF_MEMALLOC에 대해서는 memalloc_noreclaim_save()·memalloc_noreclaim_restore()가 있습니다. 이들 역시 이전 flag 상태를 복구하는 계약입니다. PIN이 migration 제약을 요청한다고 그 호출에서 page를 실제 pin하거나 reference를 취득하는 것은 아닙니다.
PF_MEMALLOC은 위 함수에서 GFP를 수정하는 bit가 아닙니다. reserve 접근 여부는 다음 __gfp_pfmemalloc_flags() 전문에서 따로 확인합니다. 이 파일의 SPDX는 GPL-2.0-only입니다.
static inline int __gfp_pfmemalloc_flags(gfp_t gfp_mask)
{
if (unlikely(gfp_mask & __GFP_NOMEMALLOC))
return 0;
if (gfp_mask & __GFP_MEMALLOC)
return ALLOC_NO_WATERMARKS;
if (in_serving_softirq() && (current->flags & PF_MEMALLOC))
return ALLOC_NO_WATERMARKS;
if (!in_interrupt()) {
if (current->flags & PF_MEMALLOC)
return ALLOC_NO_WATERMARKS;
else if (oom_reserves_allowed(current))
return ALLOC_OOM;
}
return 0;
}
NOMEMALLOC을 가장 먼저 확인하므로 task의 MEMALLOC 상태보다 금지 요청이 우선합니다. softirq와 일반 task context도 나누며 OOM reserve는 별도의 ALLOC_OOM 권한입니다. ALLOC_NO_WATERMARKS가 선택되어도 실제 free block, order, zone, 접근 가능한 topology까지 생기는 것은 아닙니다.
이름이 비슷한 lockdep 판정은 실제 reclaim 금지가 아닙니다
다음 __need_reclaim() 전문은 CONFIG_LOCKDEP 영역 안의 함수입니다. SPDX는 GPL-2.0-only입니다.
static bool __need_reclaim(gfp_t gfp_mask)
{
/* no reclaim without waiting on it */
if (!(gfp_mask & __GFP_DIRECT_RECLAIM))
return false;
/* this guy won't enter reclaim */
if (current->flags & PF_MEMALLOC)
return false;
if (gfp_mask & __GFP_NOLOCKDEP)
return false;
return true;
}
이 false는 filesystem reclaim의 lock dependency를 계측할 필요가 있는지에 대한 판단입니다. 이 함수가 allocation slow path의 실제 reclaim loop 전체를 끄는 관문은 아닙니다. __GFP_NOLOCKDEP로 추적만 빠진 상황을 NOFS·NOIO scope나 DIRECT_RECLAIM 제거와 같은 것으로 취급하면 재진입 위험은 남은 채 진단 근거만 줄어들 수 있습니다.
참고 자료
- Lorenzo Stoakes, The Linux Memory Manager, Early Access, 2025, Physical Memory 장.
- Linux
v6.1: GFP modifier와 조합의 계약, zone·migratetype 해석, task allocation scope. - Linux
v6.18: GFP bit와 public 조합. - Linux kernel documentation: Memory Allocation Guide, NOFS·NOIO scope.