pfn_to_page()로 pointer를 계산했는데 그 page를 allocator에서 사용할 수 없을 수 있습니다. memory hole이거나 offline 상태일 수 있고, device memory이거나 이미 다른 사용자가 점유한 page일 수도 있습니다. Linux가 “descriptor의 위치”, “memory의 존재”, “현재 사용 가능성”을 따로 관리하기 때문입니다.
이 구분을 이해하려면 memory model에서 PFN을 어떻게 descriptor에 연결하는지 살펴보아야 합니다. 여기서는 v6.1의 SPARSEMEM과 VMEMMAP 구성을 중심으로 설명하되 architecture별 선택지를 함께 구별하겠습니다.
Memory model이 관리하는 것은 descriptor의 배치입니다
FLATMEM은 넓은 PFN 범위를 mem_map 배열과 연결하는 단순한 모델입니다. 시작 PFN이 0이 아닌 architecture에서는 ARCH_PFN_OFFSET도 변환에 반영됩니다. 계산이 간단한 대신 물리 주소 공간에 큰 hole이 있으면 descriptor 공간을 낭비하기 쉽습니다. hole의 descriptor는 충분히 초기화되지 않았거나 architecture가 일부 공간을 반환할 수 있어, 배열 위치를 계산할 수 있다는 사실만으로 usable RAM이라 할 수 없습니다. memory hotplug·hot-remove를 지원하는 모델은 SPARSEMEM이므로 FLATMEM을 같은 확장성의 선택지로 보아서는 안 됩니다.
SPARSEMEM은 주소 공간을 section으로 나누고 section별로 memory와 descriptor의 존재를 관리합니다. SPARSEMEM_VMEMMAP은 descriptor들을 kernel virtual address 공간에서 PFN에 대응하는 연속 배열처럼 보이게 합니다. 물리 RAM의 hole이 사라지는 것이 아니라 descriptor를 찾는 virtual layout이 단순해지는 것입니다. 모든 architecture가 이 조합을 사용하는 것은 아닙니다.
VMEMMAP에서 pfn_to_page(pfn)은 개념적으로 vmemmap + pfn 형태이고, page_to_pfn(page)는 descriptor pointer 차이를 구하는 형태입니다. 여기서 더해지는 단위는 byte가 아니라 struct page입니다. 이 pointer는 physical page의 data 주소도 아니고 임의의 PFN을 검증해 주는 결과도 아닙니다. data에 접근하는 direct-map 변환과 혼동하지 않아야 합니다.
Section과 subsection은 관리 granularity가 다릅니다
section 크기는 SECTION_SIZE_BITS로 정해집니다. 4 KiB base page에 section 크기가 128 MiB인 예에서는 section마다 32,768개의 PFN을 포함합니다. PFN_SECTION_SHIFT는 PFN 번호에서 section 번호를 얻는 데 사용하므로 byte 주소의 section shift와 base-page shift를 구별해야 합니다.
이 예의 byte 크기는 2^SECTION_SIZE_BITS이며, v6.1 x86-64의 SECTION_SIZE_BITS는 27입니다. 그러면 PFN_SECTION_SHIFT = SECTION_SIZE_BITS - PAGE_SHIFT = 15가 되고 pfn_to_section_nr()는 PFN을 15 bit 오른쪽으로 옮겨 section 번호를 얻습니다. 32-bit x86이나 다른 base-page 구성에 같은 상수를 그대로 적용하면 안 됩니다.
struct mem_section에는 section_mem_map과 usage 등이 있습니다. section_mem_map은 pointer 성격의 값과 section 상태 bit를 함께 담습니다. 존재 여부, mem_map 준비 여부, online·early 관련 상태를 판별할 때 tag를 고려해야 하므로 raw 값을 무조건 배열 시작 주소로 읽을 수 없습니다. VMEMMAP의 빠른 PFN 변환은 매번 이 pointer를 따라가는 방식과도 다릅니다.
초기 boot에는 아직 mem_map을 넣기 전인 같은 field에 node ID를 임시로 encoding하기도 합니다. 이후의 mem_map 표현도 section 시작 PFN을 보정한 값에 상태 bit를 더한 형태입니다. __section_mem_map_addr()는 낮은 상태 bit를 제거하지만, 그 반환값을 모든 구성에서 section 첫 descriptor의 raw 주소와 같다고 가정해서는 안 됩니다. sparse_encode_mem_map()·sparse_decode_mem_map()의 PFN 보정과 VMEMMAP 직접 변환을 나누어 보아야 합니다.
낮은 bit를 tag로 쓸 수 있는 근거도 encoding의 정렬 여유입니다. v6.1은 architecture 전반에서 최소 6 bit의 여유를 확보한다는 조건을 설명하며, tag의 끝인 SECTION_MAP_LAST_BIT가 PFN_SECTION_SHIFT를 넘지 않는지 build-time 검사합니다. 모든 pointer의 낮은 6 bit를 임의로 사용해도 된다는 일반적인 pointer-tagging 규칙은 아닙니다.
CONFIG_SPARSEMEM_EXTREME은 section descriptor를 root 단위로 나누어 필요한 부분을 마련합니다. section 번호를 root index와 그 안의 index로 분해하며, root 하나에 들어가는 section descriptor 수는 구조체 크기와 page 크기의 영향을 받습니다. “항상 고정 길이 2차원 배열이 전부 allocation되어 있다”는 설명보다 필요한 root를 따로 두는 구성이 핵심입니다.
subsection bitmap은 그보다 작은 범위의 존재를 표현합니다. 128 MiB section과 2 MiB subsection을 가정하면 section 하나에 64개의 subsection이 있습니다. 부분적인 memory hotplug나 device memory 범위를 section 전체와 구별해 관리할 수 있습니다. 이 bit는 해당 범위의 모든 page가 free라는 뜻이 아닙니다.

section flag 이름으로는 SECTION_MARKED_PRESENT, SECTION_HAS_MEM_MAP, SECTION_IS_ONLINE, SECTION_IS_EARLY 등을 구별합니다. present_section()과 valid_section()이 다른 판별인 이유입니다. device-online 상태를 나타내는 flag도 별도로 있습니다. EXTREME 구성의 root 하나에는 한 base page 분량의 section descriptor들이 들어가며 SECTIONS_PER_ROOT로 그 수를 정합니다. section 번호를 root와 SECTION_ROOT_MASK 안쪽 index로 나누는 __nr_to_section()과, PFN을 section 번호로 바꾸는 helper를 연결해 descriptor를 찾습니다.
SECTION_HAS_MEM_MAP은 descriptor 장부의 준비를, SECTION_IS_ONLINE은 online 상태를 나타냅니다. SECTION_IS_EARLY는 boot의 early 초기화 경로로 마련된 section을 구별하므로 단순히 “지금 초기화 작업 중”이라는 일시적 표시로만 읽으면 안 됩니다. CONFIG_ZONE_DEVICE의 SECTION_TAINT_ZONE_DEVICE는 online_device_section()이 online bit와 함께 확인합니다. present_section_nr()·valid_section_nr()·online_section_nr()는 번호에서 section을 찾은 뒤 각기 다른 이 질문을 하는 helper입니다.
__pfn_to_section()은 section 번호 변환과 __nr_to_section() lookup을 연결합니다. 후자는 root가 NR_SECTION_ROOTS 범위를 벗어나면 NULL을 반환하고, EXTREME 구성에서는 최상위 배열이나 해당 root가 아직 없을 때도 NULL을 반환합니다. 성공했을 때만 root와 그 안의 index로 entry 주소를 얻습니다. 이 mask 계산을 유지하려면 struct mem_section의 크기도 power-of-two라는 배치 조건을 지켜야 합니다.
subsection_map_index()는 PFN의 section 내부 offset을 subsection page 수로 나누는 계산입니다. 전체 physical address를 byte 단위 그대로 index에 쓰지 않습니다. subsection bitmap의 한 bit가 설정되면 해당 granularity의 descriptor 유효 범위를 나타내지만, 그 안의 page별 예약·allocation 상태까지 표현하지는 않습니다.
bitmap은 각 bit를 하나의 boolean으로 쓰는 저장 방식입니다. 여기서는 pfn_section_valid()가 계산된 subsection index로 usage->subsection_map의 bit를 검사합니다. 초기화가 알려진 memory 범위와 겹치는 subsection 전체를 표시할 수 있다는 점도 granularity의 한계입니다. 이를 page마다 세밀한 접근 가능성 검사로 확대 해석하면 안 됩니다.
pfn_valid는 allocator의 허가서가 아닙니다
generic pfn_valid()는 PFN의 표현 범위와 section 상태를 확인하고, 필요한 구성에서는 subsection bitmap까지 확인합니다. early section에 대한 예외도 있습니다. boot 시점에 section 단위로 준비한 descriptor와 나중에 세분화해 추가한 범위가 같은 검사 경로를 거치지 않을 수 있기 때문입니다. architecture가 자체 구현을 제공하는 경우에는 generic 구현만으로 결론 내릴 수 없습니다.
“valid”는 적어도 그 경로가 요구하는 descriptor의 유효성을 말하지, ordinary page allocation에 적합한 RAM이라는 종합 판정이 아닙니다. present, online, managed, free, movable, pinned는 각기 다른 질문입니다. online이더라도 reserved page일 수 있고, managed RAM이어도 이미 allocation되었을 수 있습니다. device page에는 정상적인 descriptor가 있어도 CPU의 일반 RAM 접근 방식이 맞지 않을 수 있습니다.
hot-remove와 동시에 이 자료를 읽는 경우에는 수명 동기화도 필요합니다. 어떤 순간 pfn_valid()가 참이었다고 이후 역참조가 무제한으로 안정되는 것은 아닙니다. 해당 경로의 hotplug lock이나 RCU 등 버전별 보호 규칙을 따라야 하며, 단순한 주소 계산 helper가 그 보호를 취득해 주지는 않습니다.
Pageblock flag는 descriptor마다 복제하지 않습니다
mem_section_usage는 subsection bitmap 외에 pageblock별 flag 저장 공간을 가집니다. pageblock의 migrate type이나 compaction skip 같은 정보는 base page마다 동일한 값을 복제하기보다 범위별 bitmap에 모읍니다. 필요한 bit 수는 configuration과 NR_PAGEBLOCK_BITS에 달려 있습니다.
v6.1의 선언에서 subsection_map은 CONFIG_SPARSEMEM_VMEMMAP 조건 아래 나타납니다. 그 뒤의 pageblock_flags[0]는 flag가 하나도 없다는 뜻이 아니라 뒤에 추가 allocation하는 use map의 시작점입니다. mem_section_usage_size()는 고정 header 크기에 usemap_size()를 더합니다. pfn_section_valid()의 짧은 bit 검사도 이미 유효한 section과 usage가 있다는 전제 아래 동작하므로, 이 helper 하나를 임의 PFN에 대한 전체 검증으로 호출해서는 안 됩니다.
get_pfnblock_flags_mask() 계열은 section 안의 PFN offset을 pageblock index로 바꾸고, 그 index에 pageblock당 bit 수를 곱해 field 위치를 찾습니다. 같은 pageblock의 PFN은 같은 field를 선택합니다. mask를 적용해 migrate type 등 필요한 부분만 읽으며, 변경 경로는 인접 pageblock의 bit를 훼손하지 않도록 정해진 atomic update를 사용합니다.
이 계산에서는 두 가지 정렬이 구별됩니다. section 안에서의 offset을 구하는 연산과 그 offset을 pageblock 크기로 나누는 연산입니다. power-of-two 크기의 section 내부 offset에는 PAGES_PER_SECTION - 1 mask를 사용할 수 있습니다. section_to_usemap()·get_pageblock_bitmap()으로 저장 공간을 고르고 pfn_to_bitidx()로 field를 선택하는 흐름입니다. migrate type용 mask와 compaction skip용 bit도 구별합니다. section 크기나 pageblock order를 다른 build의 값으로 가정하면 bitmap 위치도 잘못됩니다. NR 값이나 bit 위치를 고정된 외부 ABI처럼 사용하지 않는 것이 중요합니다.
v6.1에서는 migrate type 번호에 PB_migratetype_bits인 3 bit를 쓰고 PB_migrate_skip 한 bit를 더해 pageblock마다 NR_PAGEBLOCK_BITS인 4 bit를 둡니다. migrate type마다 독립 boolean 하나를 두는 배열은 아닙니다. section의 bit 수는 2^(PFN_SECTION_SHIFT - pageblock_order) * NR_PAGEBLOCK_BITS이고, usemap_size()는 이를 unsigned long 단위로 올림하여 byte 수를 구합니다. 4 KiB page·128 MiB section·pageblock order 9의 예라면 64 pageblock에 256 bit, 즉 32 byte의 use map이 필요합니다. subsection bitmap의 저장 공간은 이와 별도입니다.
읽기 경로인 __get_pfnblock_flags_mask()는 field의 bit index를 word index와 word 안의 offset으로 나눈 뒤 READ_ONCE()로 word를 한 번 읽고 shift와 mask를 적용합니다. 이때 setter와의 경합 자체를 없애는 lock을 취득하지는 않습니다. 일관된 한 번의 word 읽기를 확보해도 값이 즉시 바뀔 수 있다는 의미입니다. setter는 compare-and-exchange 재시도로 같은 word의 다른 pageblock bit가 concurrent 변경될 때 덮어쓰지 않도록 합니다. field가 word 경계를 가로지르지 않는다는 배치 조건도 이 get·set 구현의 전제입니다.
PFN 변환은 빠르게 pointer를 얻는 수단이고, memory model의 상태는 그 pointer를 어떤 조건에서 해석할 수 있는지 알려 주는 장부입니다. 둘을 나누어 보면 hole이 있는 시스템, memory hotplug, device memory에서도 같은 helper 이름이 왜 서로 다른 주의를 요구하는지 이해할 수 있습니다.
두 변환과 bitmap index의 실제 구현을 연결합니다
아래는 Linux v6.1의 memory_model.h의 VMEMMAP 분기에 있는 두 macro 전문입니다. 원래 파일의 SPDX는 GPL-2.0입니다. 이 분기는 이미 준비된 descriptor virtual array를 전제로 하며 검증이나 mapping 생성을 수행하지 않습니다.
#define __pfn_to_page(pfn) (vmemmap + (pfn))
#define __page_to_pfn(page) (unsigned long)((page) - vmemmap)
반면 pageblock bitmap의 index는 descriptor pointer 차이가 아니라 PFN의 범위 내 위치로 계산합니다. 다음은 mm/page_alloc.c의 pfn_to_bitidx() 전문이며 SPDX는 GPL-2.0-only입니다. 들여쓰기만 정리했습니다.
static inline int pfn_to_bitidx(const struct page *page, unsigned long pfn)
{
#ifdef CONFIG_SPARSEMEM
pfn &= (PAGES_PER_SECTION-1);
#else
pfn = pfn - pageblock_start_pfn(page_zone(page)->zone_start_pfn);
#endif /* CONFIG_SPARSEMEM */
return (pfn >> pageblock_order) * NR_PAGEBLOCK_BITS;
}
SPARSEMEM에서는 section 크기의 mask를 쓰고, 그 밖의 구성에서는 zone 시작을 pageblock 경계로 내린 PFN을 뺍니다. 따라서 zone이 pageblock 경계와 맞지 않는 경우에도 bitmap의 기준을 유지합니다. 두 분기가 모두 같은 raw PFN을 그대로 shift하는 것은 아닙니다. 이 결과를 pageblock flag 저장 공간의 bit offset으로 사용하며, VMEMMAP pointer 계산과 혼합하지 않습니다.
Section lookup과 pageblock bitmap의 실제 정의입니다
다음은 Linux v6.1의 mmzone.h에 있는 두 구조체와 root lookup 정의 전문입니다. SPDX는 GPL-2.0이며 들여쓰기를 정리했습니다.
struct mem_section_usage {
#ifdef CONFIG_SPARSEMEM_VMEMMAP
DECLARE_BITMAP(subsection_map, SUBSECTIONS_PER_SECTION);
#endif
/* See declaration of similar field in struct zone */
unsigned long pageblock_flags[0];
};
struct mem_section {
/*
* This is, logically, a pointer to an array of struct
* pages. However, it is stored with some other magic.
* (see sparse.c::sparse_init_one_section())
*
* Additionally during early boot we encode node id of
* the location of the section here to guide allocation.
* (see sparse.c::memory_present())
*
* Making it a UL at least makes someone do a cast
* before using it wrong.
*/
unsigned long section_mem_map;
struct mem_section_usage *usage;
#ifdef CONFIG_PAGE_EXTENSION
/*
* If SPARSEMEM, pgdat doesn't have page_ext pointer. We use
* section. (see page_ext.h about this.)
*/
struct page_ext *page_ext;
unsigned long pad;
#endif
/*
* WARNING: mem_section must be a power-of-2 in size for the
* calculation and use of SECTION_ROOT_MASK to make sense.
*/
};
CONFIG_PAGE_EXTENSION 분기는 section별 page extension pointer와 padding을 더합니다. 이 padding도 구조체 크기가 power-of-two여야 하는 제약과 함께 읽습니다. configuration branch를 제거한 간단한 그림만으로 실제 sizeof를 계산할 수는 없습니다.
#ifdef CONFIG_SPARSEMEM_EXTREME
#define SECTIONS_PER_ROOT (PAGE_SIZE / sizeof (struct mem_section))
#else
#define SECTIONS_PER_ROOT 1
#endif
#define SECTION_NR_TO_ROOT(sec) ((sec) / SECTIONS_PER_ROOT)
#define NR_SECTION_ROOTS DIV_ROUND_UP(NR_MEM_SECTIONS, SECTIONS_PER_ROOT)
#define SECTION_ROOT_MASK (SECTIONS_PER_ROOT - 1)
#ifdef CONFIG_SPARSEMEM_EXTREME
extern struct mem_section **mem_section;
#else
extern struct mem_section mem_section[NR_SECTION_ROOTS][SECTIONS_PER_ROOT];
#endif
static inline unsigned long *section_to_usemap(struct mem_section *ms)
{
return ms->usage->pageblock_flags;
}
static inline struct mem_section *__nr_to_section(unsigned long nr)
{
unsigned long root = SECTION_NR_TO_ROOT(nr);
if (unlikely(root >= NR_SECTION_ROOTS))
return NULL;
#ifdef CONFIG_SPARSEMEM_EXTREME
if (!mem_section || !mem_section[root])
return NULL;
#endif
return &mem_section[root][nr & SECTION_ROOT_MASK];
}
EXTREME이 아닌 구성의 SECTIONS_PER_ROOT는 1입니다. root 하나가 한 base page 분량의 descriptor를 묶는 설명은 EXTREME 분기에 해당합니다. 없는 root를 나타내는 NULL과 이미 존재하는 entry의 상태 bit를 따로 검사해야 합니다.
같은 header의 PFN·section·subsection 변환과 유효성 helper 전문입니다.
static inline unsigned long pfn_to_section_nr(unsigned long pfn)
{
return pfn >> PFN_SECTION_SHIFT;
}
static inline unsigned long section_nr_to_pfn(unsigned long sec)
{
return sec << PFN_SECTION_SHIFT;
}
static inline struct mem_section *__pfn_to_section(unsigned long pfn)
{
return __nr_to_section(pfn_to_section_nr(pfn));
}
static inline int subsection_map_index(unsigned long pfn)
{
return (pfn & ~(PAGE_SECTION_MASK)) / PAGES_PER_SUBSECTION;
}
#ifdef CONFIG_SPARSEMEM_VMEMMAP
static inline int pfn_section_valid(struct mem_section *ms, unsigned long pfn)
{
int idx = subsection_map_index(pfn);
return test_bit(idx, ms->usage->subsection_map);
}
#else
static inline int pfn_section_valid(struct mem_section *ms, unsigned long pfn)
{
return 1;
}
#endif
VMEMMAP이 없는 pfn_section_valid() 분기는 1을 반환합니다. 따라서 이 내부 helper만 호출해서 section 존재나 전체 PFN 유효성을 확인했다고 할 수 없습니다. generic pfn_valid()도 아래와 같이 앞선 범위와 section 검사를 수행합니다. 이는 CONFIG_HAVE_ARCH_PFN_VALID가 없는 경우의 실제 정의이며 architecture 고유 구현은 별도로 확인해야 합니다.
static inline int pfn_valid(unsigned long pfn)
{
struct mem_section *ms;
/*
* Ensure the upper PAGE_SHIFT bits are clear in the
* pfn. Else it might lead to false positives when
* some of the upper bits are set, but the lower bits
* match a valid pfn.
*/
if (PHYS_PFN(PFN_PHYS(pfn)) != pfn)
return 0;
if (pfn_to_section_nr(pfn) >= NR_MEM_SECTIONS)
return 0;
ms = __pfn_to_section(pfn);
if (!valid_section(ms))
return 0;
/*
* Traditionally early sections always returned pfn_valid() for
* the entire section-sized span.
*/
return early_section(ms) || pfn_section_valid(ms, pfn);
}
이 tag의 generic 함수가 자동으로 hotplug lock을 획득하는 것은 아닙니다. descriptor 존재 검사와 호출 뒤 장기간 접근의 수명 보장을 구별해야 합니다. early section은 subsection bitmap 검사를 우회한다는 점도 확인할 수 있습니다.
section_mem_map의 보정은 mm/sparse.c의 다음 두 함수 전문으로 드러납니다. SPDX는 GPL-2.0입니다. decode는 CONFIG_MEMORY_HOTPLUG 분기에 있습니다.
static unsigned long sparse_encode_mem_map(struct page *mem_map, unsigned long pnum)
{
unsigned long coded_mem_map =
(unsigned long)(mem_map - (section_nr_to_pfn(pnum)));
BUILD_BUG_ON(SECTION_MAP_LAST_BIT > PFN_SECTION_SHIFT);
BUG_ON(coded_mem_map & ~SECTION_MAP_MASK);
return coded_mem_map;
}
#ifdef CONFIG_MEMORY_HOTPLUG
/*
* Decode mem_map from the coded memmap
*/
struct page *sparse_decode_mem_map(unsigned long coded_mem_map, unsigned long pnum)
{
/* mask off the extra low bits of information */
coded_mem_map &= SECTION_MAP_MASK;
return ((struct page *)coded_mem_map) + section_nr_to_pfn(pnum);
}
#endif /* CONFIG_MEMORY_HOTPLUG */
low bit를 지운 값은 PFN 보정을 되돌리기 전의 표현입니다. encode의 build·runtime assertion도 그 공간을 임의의 pointer tag로 일반화하지 못하게 하는 조건입니다.
bitmap 배치는 pageblock-flags.h의 enum과 mmzone.h의 크기 macro에 연결됩니다. 각각 SPDX는 GPL-2.0-only, GPL-2.0입니다.
#define PB_migratetype_bits 3
/* Bit indices that affect a whole block of pages */
enum pageblock_bits {
PB_migrate,
PB_migrate_end = PB_migrate + PB_migratetype_bits - 1,
/* 3 bits required for migrate types */
PB_migrate_skip,/* If set the block is skipped by compaction */
/*
* Assume the bits will always align on a word. If this assumption
* changes then get/set pageblock needs updating.
*/
NR_PAGEBLOCK_BITS
};
#define SECTION_BLOCKFLAGS_BITS \
((1UL << (PFN_SECTION_SHIFT - pageblock_order)) * NR_PAGEBLOCK_BITS)
실제 추가 allocation 크기는 mm/sparse.c의 다음 두 함수 전문으로 구합니다.
static unsigned long usemap_size(void)
{
return BITS_TO_LONGS(SECTION_BLOCKFLAGS_BITS) * sizeof(unsigned long);
}
size_t mem_section_usage_size(void)
{
return sizeof(struct mem_section_usage) + usemap_size();
}
필드 개수에 단순히 byte 수를 곱하는 것이 아니라 word 단위로 올림합니다. section usage의 고정 부분과 뒤에 붙는 use map은 별도입니다.
마지막으로 bitmap 선택과 get·set 경로의 전문입니다. mm/page_alloc.c의 정의이며 SPDX는 GPL-2.0-only입니다. 앞에서 본 pfn_to_bitidx()와 결합합니다.
static inline unsigned long *get_pageblock_bitmap(const struct page *page,
unsigned long pfn)
{
#ifdef CONFIG_SPARSEMEM
return section_to_usemap(__pfn_to_section(pfn));
#else
return page_zone(page)->pageblock_flags;
#endif /* CONFIG_SPARSEMEM */
}
static __always_inline
unsigned long __get_pfnblock_flags_mask(const struct page *page,
unsigned long pfn,
unsigned long mask)
{
unsigned long *bitmap;
unsigned long bitidx, word_bitidx;
unsigned long word;
bitmap = get_pageblock_bitmap(page, pfn);
bitidx = pfn_to_bitidx(page, pfn);
word_bitidx = bitidx / BITS_PER_LONG;
bitidx &= (BITS_PER_LONG-1);
/*
* This races, without locks, with set_pfnblock_flags_mask(). Ensure
* a consistent read of the memory array, so that results, even though
* racy, are not corrupted.
*/
word = READ_ONCE(bitmap[word_bitidx]);
return (word >> bitidx) & mask;
}
unsigned long get_pfnblock_flags_mask(const struct page *page,
unsigned long pfn, unsigned long mask)
{
return __get_pfnblock_flags_mask(page, pfn, mask);
}
void set_pfnblock_flags_mask(struct page *page, unsigned long flags,
unsigned long pfn,
unsigned long mask)
{
unsigned long *bitmap;
unsigned long bitidx, word_bitidx;
unsigned long word;
BUILD_BUG_ON(NR_PAGEBLOCK_BITS != 4);
BUILD_BUG_ON(MIGRATE_TYPES > (1 << PB_migratetype_bits));
bitmap = get_pageblock_bitmap(page, pfn);
bitidx = pfn_to_bitidx(page, pfn);
word_bitidx = bitidx / BITS_PER_LONG;
bitidx &= (BITS_PER_LONG-1);
VM_BUG_ON_PAGE(!zone_spans_pfn(page_zone(page), pfn), page);
mask <<= bitidx;
flags <<= bitidx;
word = READ_ONCE(bitmap[word_bitidx]);
do {
} while (!try_cmpxchg(&bitmap[word_bitidx], &word, (word & ~mask) | flags));
}
setter는 실패한 compare-and-exchange가 갱신해 준 word를 사용하여 다시 계산합니다. 성공할 때까지 재시도하므로 같은 word 안의 다른 field가 바뀌어도 이전 word 전체로 덮어쓰지 않습니다. 읽기는 그 update를 막는 lock을 취득하지 않으며, 양쪽 모두 field가 word 안에 들어간다는 전제를 사용합니다.
참고 자료
- Lorenzo Stoakes, The Linux Memory Manager, Early Access, 2025, Physical Memory 장.
- Linux
v6.1: mem_section과 section helper, PFN·descriptor 변환, pageblock flag, sparse memory 초기화. - Linux
v6.1: x86의 section 크기, pageblock bitmap의 get·set 구현. - Linux kernel documentation: Memory Model.