앞선 글에서는 struct page가 어떻게 생겼는지 간단하게 살펴봤다. compound page에서는 이 자료구조가 어떻게 처리되는지 알아보자.
What is compound page
일반적인 non-compound page (order == 0)와는 달리 compound page는 2^n개의 페이지 프레임을 하나의 페이지로 취급한 것이다. 아래 사진은 order-2 compound page를 그림으로 나타내 보았다. compound page에서는 가장 맨 앞에 있는 page가 head page이고, 그 외의 모든 페이지는 tail page라고 한다.
pages = alloc_pages(GFP_KERNEL | __GFP_COMP, 2);
compound page를 얻는 방법은 간단하다. 버디 할당자에서 할당할 때 __GFP_COMP라는 gfp flag를 넘기면 된다.
pages = alloc_pages(GFP_KERNEL, 2); /* no __GFP_COMP */
만약 __GFP_COMP를 넘기지 않고 그냥 order > 0 page를 할당하면, 이건 compound page가 아니다. [1] 물론 order == 2로 할당했으므로 물리적으로 연속적인 페이지 프레임 4개를 할당받은 건 맞는데, 커널이 보기에는 order-0 page 4개이지 compound page라고 여기지는 않는다.
커널은 어떤 page가 compound page인지 아닌지를 판단하기 위해 어떤 트릭을 사용하는지 살펴보자.
struct page {
[...]
struct { /* Tail pages of compound page */
unsigned long compound_head; /* Bit zero is set */
/* First tail page only */
unsigned char compound_dtor;
unsigned char compound_order;
atomic_t compound_mapcount;
atomic_t compound_pincount;
#ifdef CONFIG_64BIT
unsigned int compound_nr; /* 1 << compound_order */
#endif
};
[...]
} _struct_page_alignment;
compound page의 tail page들은 위의 필드들을 사용하니 참고하자. (second tail page 제외)
그림을 그려봤다. 어떤 페이지가 head page라면 PG_head 플래그가 켜져있어야 하고, tail page라면 compound_head의 비트 0이 1이어야 한다. 따라서 compound_head와 같은 오프셋을 갖는 필드의 비트 0이 1이면 compound page가 아니더라도 tail page라고 착각할 수 있다.
struct page {
[...]
struct { /* Page table pages */
unsigned long _pt_pad_1; /* compound_head */
pgtable_t pmd_huge_pte; /* protected by page->ptl */
unsigned long _pt_pad_2; /* mapping */
union {
struct mm_struct *pt_mm; /* x86 pgds only */
atomic_t pt_frag_refcount; /* powerpc */
};
#if ALLOC_SPLIT_PTLOCKS
spinlock_t *ptl;
#else
spinlock_t ptl;
#endif
};
[...]
} _struct_page_alignment;
예를 들어서 어떤 page table page의 struct page에서 어떤 이유로 _pt_pad_1 필드를 사용하게 되었다면 반드시 비트 0이 항상 0이도록 보장해야 한다. (최소 2 byte로 align된 포인터를 저장한다던가 등등) 또한 tail page는 head page의 주소를 compound_head로 가리킨다.
PageCompound()
static __always_inline int PageCompound(struct page *page)
{
return test_bit(PG_head, &page->flags) ||
READ_ONCE(page->compound_head) & 1;
}
어떤 페이지가 compound page (head or tail)인지 확인하는 데에 사용되는 함수다. head page (PG_head set)이거나 tail page (compound_head & 1) 인지를 확인한다.
PageHead()
static __always_inline int PageHead(struct page *page)
{
PF_POISONED_CHECK(page);
return test_bit(PG_head, &page->flags) && !page_is_fake_head(page);
}
PageHead()는 PG_head 플래그를 확인해서 이 페이지가 head page인지를 확인한다. page_is_fake_head()는 HugeTLB에서 메모리 overhead를 줄이기 위한 부분인데 이 글에서는 자세히 설명하지 않는다.
PageTail()
static __always_inline int PageTail(struct page *page)
{
return READ_ONCE(page->compound_head) & 1 || page_is_fake_head(page);
}
PageTail()은 compound_head의 비트 0을 확인해서 이 페이지가 tail page인지를 확인한다.
First and second tail page
compound page에서 first tail page는 특별하다. head page의 struct page에 모든 메타데이터를 넣을 수 없기 때문에 compound page에서는 first tail page의 struct page에 몇가지 메타데이터를 저장한다. 이러한 이유로 order-0 compound page는 만들 수 없다. (최소 order >= 1 이어야 한다.)
THP의 compound page는 order > 1이기 때문에 second tail page도 별도의 용도로 사용하는데 이 글에서 자세히 다루지는 않는다.
preparing compound page in buddy
compound page에 어떤 메타데이터를 저장하는지 보기 위해서 buddy의 일부분을 살펴보자. buddy 할당자에서는 order > 0이고 (order == 0인 경우에는 무시) __GFP_COMP 플래그가 켜진 경우 compound page 초기화 루틴을 실행한다.
static void prep_new_page(struct page *page, unsigned int order, gfp_t gfp_flags,
unsigned int alloc_flags)
{
post_alloc_hook(page, order, gfp_flags);
if (order && (gfp_flags & __GFP_COMP))
prep_compound_page(page, order);
/*
* page is set pfmemalloc when ALLOC_NO_WATERMARKS was necessary to
* allocate the page. The expectation is that the caller is taking
* steps that will free more memory. The caller should avoid the page
* being used for !PFMEMALLOC purposes.
*/
if (alloc_flags & ALLOC_NO_WATERMARKS)
set_page_pfmemalloc(page);
else
clear_page_pfmemalloc(page);
}
/*
* Higher-order pages are called "compound pages". They are structured thusly:
*
* The first PAGE_SIZE page is called the "head page" and have PG_head set.
*
* The remaining PAGE_SIZE pages are called "tail pages". PageTail() is encoded
* in bit 0 of page->compound_head. The rest of bits is pointer to head page.
*
* The first tail page's ->compound_dtor holds the offset in array of compound
* page destructors. See compound_page_dtors.
*
* The first tail page's ->compound_order holds the order of allocation.
* This usage means that zero-order pages may not be compound.
*/
친절하게 주석도 있다. first tail page에는 compound_dtor라는 page destructor의 index와 compound_order로 compound page의 order를 저장한다고 한다.
compound_dtor, compound_page_dtors
compound_page_dtor * const compound_page_dtors[NR_COMPOUND_DTORS] = {
[NULL_COMPOUND_DTOR] = NULL,
[COMPOUND_PAGE_DTOR] = free_compound_page,
#ifdef CONFIG_HUGETLB_PAGE
[HUGETLB_PAGE_DTOR] = free_huge_page,
#endif
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
[TRANSHUGE_PAGE_DTOR] = free_transhuge_page,
#endif
};
first tail page의 page->compound_dtor에는 compound_page_dtors 배열에 대한 인덱스가 저장된다. 배열의 원소는 각각 페이지가 해제될 때 호출되어야 할 callback 함수이다. compound_dtor은 기본적으로 COMPOUND_PAGE_DTOR로 설정하지만 이후에 THP나 HugeTLB에서 필요에 따라 바꿔준다.
void free_compound_page(struct page *page)
{
mem_cgroup_uncharge(page_folio(page));
free_the_page(page, compound_order(page));
}
free_compound_page()는 memcg uncharging을 수행하고 first tail page에 기록된 order를 free_the_page()로 넘겨서 해제한다.
static void prep_compound_head(struct page *page, unsigned int order)
{
set_compound_page_dtor(page, COMPOUND_PAGE_DTOR);
set_compound_order(page, order);
atomic_set(compound_mapcount_ptr(page), -1);
atomic_set(compound_pincount_ptr(page), 0);
}
static void prep_compound_tail(struct page *head, int tail_idx)
{
struct page *p = head + tail_idx;
p->mapping = TAIL_MAPPING;
set_compound_head(p, head);
}
void prep_compound_page(struct page *page, unsigned int order)
{
int i;
int nr_pages = 1 << order;
__SetPageHead(page);
for (i = 1; i < nr_pages; i++)
prep_compound_tail(page, i);
prep_compound_head(page, order);
}
prep_compound_page()는 head page에 PG_head를 설정하고, 각각 prep_compound_head()와 prep_compound_tail()로 head/tail page의 struct page를 초기화한다.
_mapcount of compound page
예전에는 compound page는 전체를 사용자 공간에 매핑하거나 아예 매핑하지 않거나 둘 중 하나만 가능했었다. 하지만 [2]에 의해서 compound page의 일부 sub-page들만 사용자 공간에 매핑할 수 있게 되면서 _mapcount가 조금 바뀌었다.
[2]가 도입될 당시의 diff를 좀 가져와서 살펴보자.
diff --git a/include/linux/mm.h b/include/linux/mm.h
index dad667d99304..33cb3aa647a6 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -393,6 +393,19 @@ static inline int is_vmalloc_or_module_addr(const void *x)
extern void kvfree(const void *addr);
+static inline atomic_t *compound_mapcount_ptr(struct page *page)
+{
+ return &page[1].compound_mapcount;
+}
+
+static inline int compound_mapcount(struct page *page)
+{
+ if (!PageCompound(page))
+ return 0;
+ page = compound_head(page);
+ return atomic_read(compound_mapcount_ptr(page)) + 1;
+}
+
/*
* The atomic page->_mapcount, starts from -1: so that transitions
* both from it and to it can be tracked, using atomic_inc_and_test
@@ -405,8 +418,16 @@ static inline void page_mapcount_reset(struct page *page)
static inline int page_mapcount(struct page *page)
{
+ int ret;
VM_BUG_ON_PAGE(PageSlab(page), page);
- return atomic_read(&page->_mapcount) + 1;
+ ret = atomic_read(&page->_mapcount) + 1;
+ /*
+ * Positive compound_mapcount() offsets ->_mapcount in every page by
+ * one. Let's substract it here.
+ */
+ if (compound_mapcount(page))
+ ret += compound_mapcount(page) - 1;
+ return ret;
}
static inline int page_count(struct page *page)
@@ -888,7 +909,7 @@ static inline pgoff_t page_file_index(struct page *page)
*/
static inline int page_mapped(struct page *page)
{
- return atomic_read(&(page)->_mapcount) >= 0;
+ return atomic_read(&(page)->_mapcount) + compound_mapcount(page) >= 0;
}
compound page 전체에 대한 mapping 개수는 compound_mapcount()를 통해서 first tail page의 _mapcount를 저장하며, page_mapcount()는 compound page의 일부 sub-page에 대한 _mapcount와 compound page 전체에 대한 _mapcount 를 더한다.
예를 들어서 사진의 compound page는 왼쪽에는 compound page 전체가 매핑되었지만 오른쪽에는 second tail page만 매핑되었다. 이러한 경우에 second tail page는 2번 매핑된다고 보는 것이다. (타당해 보인다.)
_refcount of compound page
이전 글에서 알아봤듯 non-compound page (order-0) 에 대한 _refcount는 작업 도중 페이지가 해제되는 것을 방지하는 데에 사용된다. compound page에서는 tail page의 _refcount를 사용하지 않고 head page의 _refcount만 사용한다. [3] 다시 말해서 compound page 전체를 해제하거나, 해제하지 않거나 둘 중 하나만 하는 것이다.
get_page(), put_page()를 보면 이것이 명확하다.
compound_pincount
우린 이제 buddy에서 prep_compound_{head,tail}()에서 하는 일의 대부분을 이해했다. 그런데 first tail page에는 compound_mapcount 말고도 compound_pincount라는 값을 설정하는데, 이건 뭘까?
질문에 대한 답은 [4], [5]에서 찾아볼 수 있었다. 아직 GUP ( = get_user_pages() ) 쪽은 잘 모르지만... 요지는 get_user_pages()에서는 페이지의 해제를 막기 위해 _refcount를 1씩 증가시키지만, _refcount를 증가시켰다고 해서 GUP로 pinning을 했는지는 알 수 없기 때문에, explicit하게 GUP_PIN_COUNTING_BIAS (1024)만큼 _refcount를 증가시켜서 _refcount >= 1024인 경우에 페이지가 누군가에 의해 pinned 되었다는걸 표시하는 것이다.
하지만 HugeTLB에서 쓰는 아주 큰 compound page에서는 explicit pinning count가 부족할 수 있기 때문에 compound_pincount를 사용한다.
따라서 누군가 1024번 GUP를 수행하면 order-0 page에서는 _refcount >= 1024이므로 누군가 explicit하게 pinning을 했나? 하고 착각할 수 있지만 (false positive, but it's acceptable according to [4]) compound page에서는 그러한 false positive가 발생하지 않는다.
References
[1] An introduction to compound pages, LWN.net
[2] THP refcount redesign, lore
[3] struct page, the Linux physical page frame data structure, Oracle Linux Blog
[4] pin_user_pages() and related calls, kernel documentation
[5] Explicit pinning of user-space pages
TODO
page_mapcount() 최신 코드 반영
'Kernel > Memory Management' 카테고리의 다른 글
Page Frame Reclamation and Swapout (v2.4.22) (0) | 2022.12.24 |
---|---|
Process Address Space (0) | 2022.11.05 |
struct page 메모 (2) | 2022.09.14 |
Virtual Memory: vmalloc(), vm_map_ram() 분석 (0) | 2022.07.13 |
Direct Map Fragmentation 문제 (0) | 2022.05.11 |
댓글