[커널 16차 스터디] 92주차 질문 사항입니다.
안녕하세요.
16기 이파란입니다.
try_to_unmap_one() 할 때,
기존 PTE 매핑을 swap_entry 로 바꿔서 이상합니다.
왜 그럴까요?
buddy system
-> slow path memory allocation
-> memory compaction
-> try_to_unmap
물리 메모리 복사 붙여넣기 이후, 기존 물리 메모리 영역 매핑 해제 부분을
pageAnon 기준으로 보고 있습니다.
코드를 보다가 swap 관련 부분이 아니고, 메모리 compaction 시에
anon page 에 대해서는 모두 이 코드를 타는 것 같아서 의문이 들어서 문의드립니다.
컴팩션은 v2.6.35-rc1 에 추가되었고,
더 이전에 pageAnon(page) 가 추가되었던 릴리즈(v2.6.7)에서 확인해보니
해당 분기에 대한 처리는 주관적인 생각으로, 의미적으로는 크게 바뀌지 않은 것 같습니다.
이렇게 해당 코드는 유구한 전통(?) 을 가지고 있습니다.
저희 보는 5.1 그리고 v5.10.37 기준으로도 많이 바뀌지 않아서
v2.6.7
v5.10.37
제가 참고해서 리뷰한 해당 부분의 코드입니다.
https://gist.github.com/paranlee/441778236268832ebeadea2954dbe803
gist embed 가 잘 안되서 우선, 아래에 나눠서 적었습니다.
v2.6.7
/**
* v2.6.7
* https://elixir.bootlin.com/linux/v2.6.7/source/mm/rmap.c#L424
*/
/*
* Subfunctions of try_to_unmap: try_to_unmap_one called
* repeatedly from either try_to_unmap_anon or try_to_unmap_file.
*/
static int try_to_unmap_one(struct page *page, struct vm_area_struct *vma)
{
struct mm_struct *mm = vma->vm_mm;
unsigned long address;
pgd_t *pgd;
pmd_t *pmd;
pte_t *pte;
pte_t pteval;
int ret = SWAP_AGAIN;
if (!mm->rss)
goto out;
address = vma_address(page, vma);
if (address == -EFAULT)
goto out;
/*
* We need the page_table_lock to protect us from page faults,
* munmap, fork, etc...
*/
if (!spin_trylock(&mm->page_table_lock))
goto out;
pgd = pgd_offset(mm, address);
if (!pgd_present(*pgd))
goto out_unlock;
pmd = pmd_offset(pgd, address);
if (!pmd_present(*pmd))
goto out_unlock;
pte = pte_offset_map(pmd, address);
if (!pte_present(*pte))
goto out_unmap;
if (page_to_pfn(page) != pte_pfn(*pte))
goto out_unmap;
/*
* If the page is mlock()d, we cannot swap it out.
* If it's recently referenced (perhaps page_referenced
* skipped over this mm) then we should reactivate it.
*/
if ((vma->vm_flags & (VM_LOCKED|VM_RESERVED)) ||
ptep_test_and_clear_young(pte)) {
ret = SWAP_FAIL;
goto out_unmap;
}
/*
* Don't pull an anonymous page out from under get_user_pages.
* GUP carefully breaks COW and raises page count (while holding
* page_table_lock, as we have here) to make sure that the page
* cannot be freed. If we unmap that page here, a user write
* access to the virtual address will bring back the page, but
* its raised count will (ironically) be taken to mean it's not
* an exclusive swap page, do_wp_page will replace it by a copy
* page, and the user never get to see the data GUP was holding
* the original page for.
*/
if (PageSwapCache(page) &&
page_count(page) != page->mapcount + 2) {
ret = SWAP_FAIL;
goto out_unmap;
}
/* Nuke the page table entry. */
flush_cache_page(vma, address);
pteval = ptep_clear_flush(vma, address, pte);
/* Move the dirty bit to the physical page now the pte is gone. */
if (pte_dirty(pteval))
set_page_dirty(page);
if (PageAnon(page)) {
swp_entry_t entry = { .val = page->private };
/*
* Store the swap location in the pte.
* See handle_pte_fault() ...
*/
BUG_ON(!PageSwapCache(page));
swap_duplicate(entry);
set_pte(pte, swp_entry_to_pte(entry));
BUG_ON(pte_file(*pte));
}
mm->rss--;
BUG_ON(!page->mapcount);
page->mapcount--;
page_cache_release(page);
out_unmap:
pte_unmap(pte);
out_unlock:
spin_unlock(&mm->page_table_lock);
out:
return ret;
}
v5.10.37
/**
* v5.10.37
* https://elixir.bootlin.com/linux/v5.10.37/source/mm/rmap.c#L1617
*/
/*
* @arg: enum ttu_flags will be passed to this argument
*/
static bool try_to_unmap_one(struct page *page, struct vm_area_struct *vma,
unsigned long address, void *arg)
{
struct mm_struct *mm = vma->vm_mm;
struct page_vma_mapped_walk pvmw = {
.page = page,
.vma = vma,
.address = address,
};
pte_t pteval;
struct page *subpage;
bool ret = true;
struct mmu_notifier_range range;
enum ttu_flags flags = (enum ttu_flags)(long)arg;
// ..
while (page_vma_mapped_walk(&pvmw)) {
// ...
} else if (IS_ENABLED(CONFIG_MIGRATION) &&
(flags & (TTU_MIGRATION|TTU_SPLIT_FREEZE))) {
swp_entry_t entry;
pte_t swp_pte;
if (arch_unmap_one(mm, vma, address, pteval) < 0) {
set_pte_at(mm, address, pvmw.pte, pteval);
ret = false;
page_vma_mapped_walk_done(&pvmw);
break;
}
/*
* Store the pfn of the page in a special migration
* pte. do_swap_page() will wait until the migration
* pte is removed and then restart fault handling.
*/
entry = make_migration_entry(subpage,
pte_write(pteval));
swp_pte = swp_entry_to_pte(entry);
if (pte_soft_dirty(pteval))
swp_pte = pte_swp_mksoft_dirty(swp_pte);
if (pte_uffd_wp(pteval))
swp_pte = pte_swp_mkuffd_wp(swp_pte);
set_pte_at(mm, address, pvmw.pte, swp_pte);
/*
* No need to invalidate here it will synchronize on
* against the special swap migration pte.
*/
} else if (PageAnon(page)) {
swp_entry_t entry = { .val = page_private(subpage) };
pte_t swp_pte;
// ...
swp_pte = swp_entry_to_pte(entry);
if (pte_soft_dirty(pteval))
swp_pte = pte_swp_mksoft_dirty(swp_pte);
if (pte_uffd_wp(pteval))
swp_pte = pte_swp_mkuffd_wp(swp_pte);
set_pte_at(mm, address, pvmw.pte, swp_pte);
/* Invalidate as we cleared the pte */
mmu_notifier_invalidate_range(mm, address,
address + PAGE_SIZE);
}
// ...
discard:
/*
* No need to call mmu_notifier_invalidate_range() it has be
* done above for all cases requiring it to happen under page
* table lock before mmu_notifier_invalidate_range_end()
*
* See Documentation/vm/mmu_notifier.rst
*/
page_remove_rmap(subpage, PageHuge(page));
put_page(page);
}
mmu_notifier_invalidate_range_end(&range);
return ret;
}
아래는 의논하면서 본 swap_entry 관련 다이어그램입니다.
<출처: http://jake.dothome.co.kr/swap-1/>
긴 글 끝까지 읽어주셔서 감사합니다.
댓글 12
-
이파란
2021.05.17 10:21
-
문c(문영일)
2021.05.17 11:04
메모리 부족시,
단편화를 줄이기 위해 compaction이 동작하며 이 때 물리 페이지(movable)들이 이동(migration)됩니다. 만일 단편화로도 해결되지 않으면 reclaim을 시도하는데 이 때 anon 페이지들을 대상으로 swap 하며, 페이지 캐시들 및 매핑된 file 페이지들은 그냥 삭제됩니다. 물론 dirty 상태의 페이지들은 삭제를 통한 회수 전에 디스크등에 저장부터 합니다.
위와 같이 물리 페이지의 언매핑은 compaction을 통한 migration 및 페이지 회수(reclaim) 등 여러 군데에서 사용합니다. 실제 anon 페이지에 대한 swap은 compaction과는 관계가 없고 reclaim과 관계가 있습니다.
참고하시기 바랍니다.
-
이파란
2021.07.05 10:12
rmap 복습 이후에도, try_to_unmap() 에서 아직 아리송한 부분이 있어서 이어서 문의드립니다!
# v2.6.7
if (PageAnon(page)) {
swp_entry_t entry = { .val = page->private };
/*
* Store the swap location in the pte.
* See handle_pte_fault() ...
*/
BUG_ON(!PageSwapCache(page));
swap_duplicate(entry);
set_pte(pte, swp_entry_to_pte(entry));
BUG_ON(pte_file(*pte));
}
# v5.10.37
} else if (PageAnon(page)) {
swp_entry_t entry = { .val = page_private(subpage) };
pte_t swp_pte;
// ...swp_pte = swp_entry_to_pte(entry);
if (pte_soft_dirty(pteval))
swp_pte = pte_swp_mksoft_dirty(swp_pte);
if (pte_uffd_wp(pteval))
swp_pte = pte_swp_mkuffd_wp(swp_pte);
set_pte_at(mm, address, pvmw.pte, swp_pte);
/* Invalidate as we cleared the pte */
mmu_notifier_invalidate_range(mm, address,
address + PAGE_SIZE);
compaction 을 진행하고, try_to_umap_one() 부분에서
anon page 를 매핑하고 있던 pte 가 set_pte_at(mm, address, pvmw.pte, swp_pte);
언 매핑한 pte 가 이렇게 Swap PTE 로 바뀌게 되나요?
해당 코드를 무조건 타는 것 같아서 궁금합니다~
Swap PTE 엔트리 식별
is_swp_pte() (include/linux/swapops.h)
/* check whether a pte points to a swap entry */
static inline int is_swap_pte(pte_t pte)
{
return !pte_none(pte) && !pte_present(pte);
}
(참고: 문C Swap 엔트리 - http://jake.dothome.co.kr/swap-entry/)
항상 감사드립니다!
-
DEWH
2021.07.05 18:39
compaction 즉, 엄밀히 얘기해서 migration할 때 해당 루틴은 타지 않아요.
해당 부분은 shrink_list에서 시작하여 try_to_unmap을 타고 내려오는 reclaim 과정에서 실행 되는 부분이에요.
2.6에서 reclaim 파트를 살펴보시면, shrink_list에서 스왑 엔트리를 할당한 후 try_to_unmap을 통해 매핑을 해제하는 전체적인 흐름을 살펴볼 수 있어요.
-
이파란
2021.07.05 19:22
음.. 좀 더 정확하게 루틴을 타지 않는 이유가 궁금한데요?
compaction -> migraion 하는 부분에서
여태까지 PageAnon(page) 으로 계속 타고 왔고
그러면 어디로 들어가서 해당 부분을 안 탈까요?
코드로 볼 때 상단의 else if 에 걸리거나, else 로 빠져야 하는데
PageAnon(page) 상단에
[1]
} else if (pte_unused(pteval) && !userfaultfd_armed(vma)) {
일까요? 아니면
[2]
else
로 빠져나갈까요?
(1) - page_vma_mapped_walk(&pvmw) // pvmw 의 PTE 는 설정한 상태임.
// ...
if (!map_pte(pvmw))
(2) -- static bool map_pte(struct page_vma_mapped_walk *pvmw)
{
pvmw->pte = pte_offset_map(pvmw->pmd, pvmw->address);설마.. 혹시나... if 조건에서 조건을 더 넣어야하는데 깜빡한게 아니라 제가 잘못 판단하거겠죠..? ^^;
-
DEWH
2021.07.06 00:31
https://elixir.bootlin.com/linux/v5.10.37/source/mm/rmap.c#L1588에서 migration이 처리가 되네요.
-
이파란
2021.07.06 07:18
TTU_MIGRATION 설정하고 들어온 상태라서 이쪽으로 타는게 맞겠네요!
아래의 주석 내용을 보고 고민해보면 될까요?
/*
* Store the pfn of the page in a special migration
* pte. do_swap_page() will wait until the migration
* pte is removed and then restart fault handling.
*/} else if (IS_ENABLED(CONFIG_MIGRATION) &&
(flags & (TTU_MIGRATION|TTU_SPLIT_FREEZE))) {
swp_entry_t entry;
pte_t swp_pte;if (arch_unmap_one(mm, vma, address, pteval) < 0) { // Arm64 안탐
set_pte_at(mm, address, pvmw.pte, pteval);
ret = false;
page_vma_mapped_walk_done(&pvmw);
break;
}/*
* Store the pfn of the page in a special migration
* pte. do_swap_page() will wait until the migration
* pte is removed and then restart fault handling.
*/
entry = make_migration_entry(subpage,
pte_write(pteval));
swp_pte = swp_entry_to_pte(entry);
if (pte_soft_dirty(pteval))
swp_pte = pte_swp_mksoft_dirty(swp_pte);
if (pte_uffd_wp(pteval))
swp_pte = pte_swp_mkuffd_wp(swp_pte);
set_pte_at(mm, address, pvmw.pte, swp_pte);
/*
* No need to invalidate here it will synchronize on
* against the special swap migration pte.
*/
}결국 스왑 엔트리를 이용하는 건 맞음! 이라는 이야기인가요?
-
문c(문영일)
2021.07.06 17:25
pte 엔트리는 물리 페이지를 매핑하는 목적으로 사용되자나요? 그런데 아시다시피 swap한 anon 페이지의 경우에는 메모리 활용을 위해 이 빈 공간을 swp_entry로 사용합니다. 그런것 처럼 migration에서도 이 pte 엔트리 공간을 활용하여 이 곳을 잠시 마이그레이션용 swp_entry로 변경하여 사용합니다. (swp_entry for migration)
swp_entry의 구조는 swap 파일(또는 디바이스)에 대한 타입 id와 offset을 가지고 있습니다. 이의 구조를 활용하여 해당 페이지의 migration이 수행될 경우에는 해당 페이지의 pfn을 offset으로 변경시키고, 타입 id에는 SWP_MIGRATION_WRITE|SWP_MIGRATION_READ구분하여 저장합니다. 이 migration 엔트리는 fault 발생 시 do_swap_page()에서 이를 읽어내어 처리하는 코드가 있습니다. 제 글도 migration에 대해 조금 더 보완을 해야 겠네요. ^^
-
이파란
2021.07.06 19:44
정말 모두 답변 해주시구, 두 분께 정말 감사드립니다!
swp_entry 쪽을 공부하면 완전히 해소가 되겠네요.
감사드립니다! (^▽^)/ ʸᵉᔆᵎ
-
이파란
2021.08.09 10:47
질문할 당시,
swap entry === 디스크로 밀어넣는 녀석들!
에 사로 잡혀 있었는데요.
공부하면서 발견한 재미있는 점은 try_to_unmap 할 때,
compaction 또는 PFRA 에 따라 migration 한 anon page는 swap entry 로 바뀝니다.
대상 페이지를 swap 서브시스템의 swap cache를 이용하여 처리하는 점입니다.
즉, __delete_from_swap_cache()를 트리거 전까지는 캐시로 메모리에 가지고 있습니다.
이후 페이지에 접근하면 페이지 폴트 핸들러가 스왑 캐시에서 페이지 프레임을 발견합니다.
do_swap_page()
- lookup_swap_cache()커널 공부하면서 다시 한번 아만보를 느끼네요. 감사합니다~
-
커널열공
2021.08.12 12:15
swap entry가 스왑영역에서 스왑아웃하기 위한 키값이기도 하지만, 동시에 페이지 폴트 핸들러가 스왑캐시에서 페이지를 찾기위한 키값이군요.
-
이파란
2021.08.12 14:44
네 고민해보니까 swap의 사전적인 의미가 떠올라서 살펴봤는데,
영한 사전에는 "디스크에 밀어넣는다!" 라는 뜻은 없더라구요. (공대 유머 ㅜ.ㅜ)
.
개인적으로는
pte_entry & swap_entry 에 상관없이
그냥 공통적으로 사용하기 위해 일괄적으로 변한다고 생각하면,
끼워 맞춰질 것 같긴합니다.
그렇다면
try_to_unmap_one 은 try_to_unmap 옛날 주석대로 보면 worker 일 뿐이고
시키는 대로 일만 해줄 뿐이니, 호출한 상위 레이어 단에서
어떤 필요성이 있어서 요청했을지는 신경을 안쓰는 것 같아서
swap 이 없으면 더미 코드가 되고,
compaction 으로 사용할 때도 의미가 없고
이렇게 되어야 할 것 같긴한데요. 과연..