[커널 17차] 112 ~ 116주차

2022.12.05 21:53

ㅇㅇㅇ 조회 수:73

swap_duplicate()에서 continuation count 처리하는 방법

 

swap_duplicate()
- swap entry를 인자로 받는다
- L3507 : 함수 __swap_duplicate()를 이용, swap entry에 대한 count를 1 증가시키고 성공이면 종료. 실패하면 그 이유가 ENOMEM, 즉 메모리 부족인 경우 메모리 할당을 위해 함수 add_swap_count_continuation()을 호출한다. 메모리 할당이 실패하면 while 문이 종료되고 성공하면 __swap_duplicate()를 다시 호출하여 count를 1 증가시킨다.

 

__swap_duplicate()
- 인자로 swap entry, usage를 받는다
- L3433 : get_swap_device()로 entry에 대한 si를 구하고 si->users ref를 증가
- L3437 : entry에서 swap offset을 구한다
- L3438 : ci를 lock 잡고 구한다
- L3440 : p->swap_map[offset]을 count로 가져온다
- L3446 : count가 SWAP_MAP_BAD이면 에러 리턴
- L3451 : count로부터 SWAP_HAS_CACHE 플래그를 has_cache로 저장
- L3452 : count에서 플래그 제거
- L3455 : usage가 SWAP_HAS_CACHE로 들어온 경우
- L3458 : swap cache는 없지만 count가 있으면 has_cache에 SWAP_HAS_CACHE를 할당
- L3460 : swap cache가 이미 있으면 EEXIST 에러 리턴
- L3462 : swap cache도 없고 count도 없으면 ENOENT를 리턴
- L3465 : usage가 SWAP_HAS_CACHE가 아니고 count 또는 swap cache가 있는 경우
- L3467 : count가 SWAP_MAP_MAX보다 작으면 usage만큼 count 증가
- L3469 : count가 SWAP_MAP_MAX보다 크면 EINVAL 에러 리턴
- L3471 : count == SWAP_MAP_MAX이면 함수 swap_count_continued() 호출하여 continuation page가 count를 기록한다. 그리고 기존 count는 0으로 리셋하고 대신 COUNT_CONTINUED 플래그를 붙여준다
- L3473 : 함수 swap_count_continued()가 메모리 부족으로 인해 실패하면 ENOMEM 리턴한다
- L3475 : usage가 SWAP_HAS_CACHE도 아니고, count 및 swap cache도 없으면 ENOENT 에러 리턴
- L3478 : p->swap_map[offset]에 증가된 count를 기록
- L3481 : ci unlock
- L3483 : put_swap_device()로 si->users ref를 감소
- L3484 : err 리턴

 

swap_count_continued()
- 인자로 swap info struct si, swap map offset, count를 받는다
- L3687 : si->swap_map + offset 가상 주소에 대해 함수 vmalloc_to_page()로 해당 가상 주소에 대한 struct page를 가져와서 head에 할당
- L3688 : head의 private가 SWP_CONTINUED가 아니면, count에 COUNT_CONTINUED가 있으면 bug on 처리하고 아니면 false 리턴한다.
- L3693 : si->cont_lock을 잡는다
- L3694 : offset에 하위 12bit만 남긴다
- L3695 : head에 연결된 다음 page를 가져온다
- L3696 : page + offset의 linear mapping 가상 주소를 가져온다
- L3698 : count == SWAP_MAP_MAX이면 init_map으로 점프한다
- L3701 : count == (SWAP_MAP_MAX | COUNT_CONTINUED) 이면 if 문으로 아래를 수행
- L3705 : *map == (SWAP_CONT_MAX | COUNT_CONTINUED) 이면 while 문으로 아래를 수행
- L3706 : kunmap()은 빈 함수
- L3707 : page의 다음 연결된 page를 가져온다
- L3708 : page == head이면 버그 (*map에 COUNT_CONTINUED 표시가 있었으므로)
- L3709 : page + offset에 해당하는 linear mapping 가상 주소를 map에 연결하고 다시 L3705로 이동
- L3710 : while 문 종료
- L3711 : *map == SWAP_CONT_MAX이면 if 문으로 아래를 수행
- L3713 : page에 연결된 다음 page를 가져온다
- L3714 : page == head면 다음 page가 할당이 안된 것이므로 false 리턴한다
- L3718 : page + offset에 해당하는 linear mapping 가상 주소를 map에 할당한다
- L3719 : init_map : *map = 0으로 초기화
- L3720 : if 문 종료
- L3721 : *map을 1 증가
- L3723 : while 문을 돌면서 이전 페이지들에 대해서 모두 COUNT_CONTINUED로 값 초기화
- L3728 : true 리턴
- L3753 : if 문 종료
- L3755 : si->cont_lock을 푼다
- L3756 : ret 값 리턴

 

vmalloc_to_page()
- 인자로 가상 주소를 받아 해당되는 pte를 구한뒤, pte에 적혀있는 물리주소에 해당하는 struct page를 구해준다

 

add_swap_count_continuation()
- 인자로 entry, gfp_mask를 받는다
- L3582 : gfp_mask에 __GFP_HIGHMEM을 추가하여 alloc_page()로 page를 할당받는다
- L3584 : get_swap_device()로 entry로부터 si를 가져온다
- L3585 : si가 NULL이면 out으로 나감
- L3592 : si->lock을 잡는다
- L3594 : entry로부터 swap offset을 구한다
- L3596 : si, offset으로부터 ci를 lock 잡고 구한다
- L3598 : si->swap_map[offset]으로부터 count를 구한다
- L3600 : count에서 COUNT_CONTINUED 플래그를 제외한 값이 SWAP_MAP_MAX가 아니면 잘못되었으므로 out으로 나간다
- L3609 : page 할당이 실패하면 ENOMEM 리턴
- L3619 : 함수 vmalloc_to_page()로 si->swap_map + offset 가상주소의 struct page를 head로 가져온다
- L3620 : offset에서 하위 12bit만 남긴다
- L3627 : head의 private가 없으면 head->lru를 초기화하고 private에 SWP_CONTINUED를 적는다. 그리고 si->flags에 SWP_CONTINUED를 추가한다
- L3634 : head에 달려있는 page에 대해 루프를 돌면서 아래를 수행한다
- L3641 : count에 COUNT_CONTINUED가 없으면 out_unlock_cont로 이동
- L3644 : page + offset을 map으로 가져온다
- L3645 : count에 *map을 쓴다
- L3652 : count & ~COUNT_CONTINUED가 SWP_CONT_MAX가 아니면 out_ulock_cont로 이동
- L3656 : page를 head->lru의 tail에 추가한다
- L3657 : page = NULL
- L3658 : out_unlock_cont
- L3659 : si->cont_lock을 푼다
- L3660 : out
- L3661 : ci lock을 푼다
- L3662 : si->lock을 푼다
- L3663 : put_swap_device()로 si->users의 refcount 1 감소
- L3664 : outer
- L3665 : page가 존재하면 __free_page()로 page를 free
- L3667 : ret 리턴

 

get_swap_device()
- 인자로 swap entry를 받는다
- L1270 : entry가 NULL이면 NULL 리턴
- L1272 : entry로부터 si를 가져온다
- L1273 : si == NULL이면 NULL 리턴
- L1275 : percpu_ref_tryget_live() 함수와 si->users의 refcount를 증가시킨다
- L1276 : 실패 시 NULL 리턴
- L1284 : read memory barrier
- L1285 : entry로부터 swap offset을 구한다
- L1286 : offset이 si->max 보다 크면 put_out으로 이동
- L1289 : si를 리턴
- L1290 : bad_nofile
- L1291 : 에러 로그 출력
- L1292 : out
- L1293 : NULL 리턴
- L1294 : put_out
- L1295 : 함수 percpu_ref_put()로 si->users의 refcount 감소

 

percpu_ref_tryget_live()
- 인자로 percpu_ref ref를 받는다
- L287 : ret = false 초기화
- L289 : rcu read lock
- L291 : ref가 percpu이면 this_cpu_inc()로 ref->percpu_count_ptr를 증가시킨다. ret = true
- L294 : ref가 percpu가 아니면 ref->data->count를 증가시킨다
- L298 : rcu read unlock
- L300 : ret 리턴

 

percpu_ref_put()
- 인자로 percpu_ref ref를 받는다
- percpu_ref_put_many() 함수로 ref의 count를 1 감소시킴

 

percpu_ref_put_many()
- 인자로 percpu_ref ref, nr을 받는다
- L317 : rcu read lock
- L319 : ref가 percpu이면 this_cpu_sub()로 ref->percpu_count_ptr를 감소시킨다
- L321 : ref가 percpu가 아니면 ref->data->count를 감소시킨다
- L324 : rcu read unlock

 

put_swap_device()
- percpu_ref_put()으로 si->users의 refcount 감소

 


Dirty throttling
- 리눅스는 dirty 페이지가 생성되는 속도와 dirty 페이지가 처리되는 속도를 맞추기 위해 I/O Throttling이라는 기법을 사용한다. dirty 페이지가 처리되는 속도는 dirty 페이지가 생성되는 속도보다 상대적으로 느리다. 이러한 특징으로 인해 페이지 캐시 내의 free 페이지가 고갈되는 문제가 발생할 수 있다. 이를 방지하기 위해 리눅스는 dirty 페이지를 생성하는 프로세스가 sleep 하도록 하여 dirty 페이지 생성 속도를 늦추는 I/O Throttling이라는 기법을 사용한다. 하지만 I/O Throttling 기법으로 인해 write() 시스템 콜의 반환 시간이 느려질 수 있다.
- https://lwn.net/Articles/456904/
- https://oslab.kaist.ac.kr/wp-content/uploads/esos_files/publication/conferences/korean/WC_ojt.pdf


add_to_swap()에서 set dirty page 수행하는 이유
- https://github.com/torvalds/linux/commit/9625456cc76391b7f3f2809579126542a8ed4d39
- https://www.mail-archive.com/linux-kernel@vger.kernel.org/msg1494319.html
- MAdvisor에서 free를 수행하면 page의 pte에서 dirty bit를 clear하고 이후 swapbacked도 clear한다
- 그러나 그 사이에 reclaim이 수행되면 page를 swap cache에 추가하고 pageout을 하지는 않는다 (clean이므로)
- 이후 해당 page에서 fault가 나면 존재하지 않는 page를 swap in하므로 data corruption이 발생한다


Unevictable LRU infrastructure 패치
- https://github.com/torvalds/linux/commit/894bc310419ac95f4fa4142dc364401a7e607f65

 

cmpxchg 명령어
- https://developer.arm.com/documentation/dui0801/g/A64-Data-Transfer-Instructions/SWPA--SWPAL--SWP--SWPL--SWPAL--SWP--SWPL

 

리눅스 메모리 관련
- https://ecogeo.tistory.com/255

 

PoP
- Point of Persistence
- https://wilwan01.github.io/pdf/PIRL20.pdf
- https://community.arm.com/arm-research/b/articles/posts/persistency-for-synchronization-free-regions

 

IDC/DIC capability
- https://developer.arm.com/documentation/ddi0601/2020-12/AArch64-Registers/CTR-EL0--Cache-Type-Register?lang=en#fieldset_0-28_28


set_pte_at()
- L312 : pte가 present하고, 유저 실행 코드이고, special이 아니면 __sync_icache_dcache() 함수로 icache와 dcache의 coherence를 맞춰준다
- 여기서 special은 SW defined pte bit이다
- L321 : mte 기능이 있고, user가 tag에 접근가능한 경우 새 pte에 tag가 존재하거나 기존 pte가 swap pte인 경우엔 old tag를 새 pte에도 붙여준다
- L335 : pte update에 대한 race를 체크한다. 경고가 뜨면 함수 ptep_set_access_flags()로 pte를 set할 것이 권고된다
- L337 : ptep를 새 값 pte로 업데이트한다

 

__sync_icache_dcache()
- pte를 인자로 받는다
- L53 : pte에 해당하는 page를 가져온다
- L55 : page flag에 PG_dcache_clean이 참이면 그냥 리턴하고, 아니면 아래 코드를 수행한다
- L56 : 함수 sync_icache_aliases()로 현재 page 영역에 대해서 data/instruction cache coherence를 맞춰준다
- L59 : PG_dcache_clean을 set한다
- 기본적으로 PG_dcache_clean = 0으로 (dirty) 세팅되어 있고, 커널에서 페이지에 write를 해도 0으로 세팅한다
- 함수 flush_dcache_page()에서 1인 경우 0으로 바꿔주며, 커널에서 페이지에 write할 때 이 함수를 사용한다
- 즉 페이지 첫 생성 —> dirty
- 함수 __sync_icache_dcache() 수행 —> clean
- 커널에서 write —> dirty

 

sync_icache_aliases()
- 시작주소와 끝주소를 받는다
- L19 : 함수 icache_is_aliasing()로 현재 CPU가 icache aliasing 가능한 CPU인지 판단하고 가능한 경우에는 dcache_clean_pou(), icache_inval_all_pou() 함수로 icache와 dcache의 sync를 맞춰준다. aliasing이 가능한 경우에는 icache에 동일한 물리주소를 나타내는 엔트리가 여러 개 올라와 있을 수 있는데 그 주소를 알 수 없기 때문에 icache 전체를 invalidate한다
- L27 : aliasing이 없는 경우에는 caches_clean_inval_pou() 함수로 sync를 맞춰준다. 이 경우 icache도 시작부터 끝주소까지만 invalidate한다

 

icache_is_aliasing()
- __icache_flags 전역변수의 ICACHEF_ALIASING = 0번째 bit가 0인지 1인지로 aliasing 가능 여부를 판별한다

 

dcache_clean_pou()
- L125 : IDC 기능이 있는 경우에는 dsb ishst만 사용한다
- L129 : IDC 기능이 없으면 dcache_by_line_op 매크로로 주소 영역을 dcache clean & invalidate하고 이후 dsb를 수행한다

 

icache_inval_all_pou()
- L140 : DIC 기능이 있으면 그냥 리턴
- L143 : 아니면 icache invalidate를 전 영역에 수행하고 dsb ish를 실행한다

 

caches_clean_inval_pou()
- caches_clean_inval_pou_macro() 매크로 수행

 

caches_clean_inval_pou_macro()
- L29 : IDC 기능이 있으면 dsb ishst만 수행하고 L36으로 점프
- L32 : 없으면 dcache_by_line_op 매크로로 주소 영역을 dcache clean & invalidate하고 이후 dsb를 수행
- L37 : DIC 기능이 있으면 isb만 수행하고 리턴
- L41 : 없으면 invalidate_icache_by_line 매크로로 주소 영역의 icache를 invalidate하고 dsb ish, isb를 수행한다


ARM64_UNMAP_KERNEL_AT_EL0
- https://lore.kernel.org/all/1512059986-21325-17-git-send-email-will.deacon@arm.com/

tlbivale1is
- https://developer.arm.com/documentation/ddi0595/2021-12/AArch64-Instructions/TLBI-VALE1IS--TLBI-VALE1ISNXS--TLB-Invalidate-by-VA--Last-level--EL1--Inner-Shareable?lang=en#fieldset_0-63_48

table entry format
- https://armv8-ref.codingbelief.com/en/chapter_d4/d43_2_armv8_translation_table_level_3_descriptor_formats.html
- https://armv8-ref.codingbelief.com/en/chapter_d4/d43_1_vmsav8-64_translation_table_descriptor_formats.html

 

Device memory
- https://developer.arm.com/documentation/den0024/a/Memory-Ordering/Memory-types/Device-memory

 

MAIR
- https://developer.arm.com/documentation/ddi0595/2021-12/AArch64-Registers/MAIR-EL1--Memory-Attribute-Indirection-Register--EL1-?lang=en

 

메모리 타입 중에 MT_NORMAL_TAGGED가 있고 이는 MAIR로 설정된다 (인덱스 = 1)

 

Transient memory
- https://stackoverflow.com/questions/38636326/concept-and-advantages-of-transient-and-non-transient-memory-in-arm

 

Clean cache
- https://www.kernel.org/doc/html/v4.18/vm/cleancache.html
- https://lwn.net/Articles/454795/

 

https://stackoverflow.com/questions/9223510/how-does-linux-kernel-find-dirty-page-to-flush


shrink_page_list() 계속
- L1623 : page가 dirty이면 if 문 진입
- L1634 : page가 file이면서 1) 현재 task가 kswapd가 아니거나, 2) page가 PageReclaim이 아니거나, 3) pgdat->flags에 PGDAT_DIRTY가 clear되어 있으면
- L1643 : NR_VMSCAN_IMMEDIATE node page stat을 increment하고
- L1644 : SetPageReclaim() 수행한 뒤
- L1646 : activate_locked로 이동한다
- L1647 : 아니면 통과
- L1649 : references == PAGEREF_RECLAIM_CLEAN 이면 keep_locked로 이동
- L1651 : may_enter_fs나 sc->may_writepage가 false이면 keep_locked로 이동
- L1661 : try_to_unmap_flush_dirty()는 빈 함수
- L1662 : pageout() 함수로 pageout을 수행한다
- L1663 : PAGE_KEEP 리턴되면 keep_locked로 이동
- L1664 : PAGE_ACTIVATE 리턴되면 activate_locked로 이동
- L1667 : PAGE_SUCCESS 리턴되면
- L1668 : stat->nr_pageout을 nr page 만큼 늘려주고
- L1670 : PageWriteback()이거나 PageDirty()이면 keep으로 이동
- L1675 : PageWriteback()이 아니면 synchronous이므로 writeback이 완료되었고 page 회수 준비를 한다
- L1679 : trylock_page()로 page lock 시도하고 안되면 keep으로 이동
- L1681 : PageDirty()이거나 PageWriteback()이면 keep_locked로 이동
- L1683 : page_mapping()을 mapping으로 잡아주고 PAGE_CLEAN으로 이동
- L1685 : PAGE_CLEAN 리턴되면 아래로 이동해서 page 회수 준비
- L1711 : page private가 있으면 if 문 진입하여 page buffer 관련 처리 수행
- L1712 : try_to_release_page()로 page buffer를 release 시도하고 안되면 activate_locked로 이동
- L1714 : mapping이 없고 page count도 1 이면 page unlock하고 put_page_test_zero() 수행, 성공하면 free_it으로 이동하여 회수, 실패하면 speculative reference가 발생한 것으로, speculative reference에서 page를 곧 회수할 것이므로 nr_reclaimed를 1 증가시키고 continue하여 다음 page 처리
- L1730 : if 문 종료
- L1732 : PageAnon() & !PageSwapBacked(), 즉 lazy free일 때 if 문 진입
- L1734 : page_ref_freeze() 함수로 page의 recount가 1이면 0으로 만든다. 실패시 keep_locked로 이동
- L1744 : vm event accepting PGLAZYFREED 1 증가
- L1745 : memcg page event PGLAZYFREED 1 증가
- L1746 : lazy free가 아닌 경우 mapping이 없거나 __remove_mapping()이 실패하여 file page cache 또는 swap cache가 사라지지 않은 경우 keep_locked로 이동한다
- L1749 : if 문 종료
- L1750 : page unlock
- L1751 : free_it
- L1756 : nr_reclaimed에 nr pages를 더함
- L1762 : page가 THP이면 destructor 호출하여 destroy 수행
- L1765 : 아니면 free_pages list에 page 추가
- L1766 : continue로 다음 page 처리
- L1768 : activate_locked_split
- L1773 : nr_pages > 1 이면 sc->nr_scanned에서 nr_pages - 1 만큼 줄이고 nr_pages를 1로 만든다
- L1777 : activate_locked
- L1779 : PageSwapCache()이고 swap page가 절반 이상이 사용 중이거나 PageMlocked()이면 try_to_free_swap()으로 잡아놓은 swap 영역을 해제한다
- L1782 : PageActive()이면 버그
- L1783 : PageMlocked()가 아니면 SetPageActivate()를 수행하고 stat->nr_activate를 anon/file type에 따라 nr_pages 만큼 증가 및 memcg page event에 PGACTIVATE 1 증가 수행
- L1789 : keep_locked
- L1790 : page unlock
- L1791 : keep
- L1792 : page를 ret_pages list에 추가
- L1793 : PageLRU() 또는 PageUnevictable()이면 버그
- L1794 : while 문 종료
- L1797 : demote 관련 스킵
- L1809 : free_pages list에 대해 mem_cgroup_uncharge_list() 수행
- L1810 : try_to_unmap_flush() 함수는 CONFIG_ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH = n이므로 빈 함수
- L1811 : free_unref_page_list() 함수로 free_pages list의 page들을 pcp/buddy로 반환한다
- L1813 : ret_pages list를 page_list로 보낸다
- L1814 : PGACTIVATE vm event stat을 stat->nr_actiavate[anon/file] 합 수만큼 증가
- L1816 : nr_reclaimed를 리턴


pageout()
- 인자로 page, page_mapping()에 해당하는 address_space mapping을 받는다
- L1043 : is_page_cache_freeable() 함수로 page의 page cache를 free할 수 있는지 판단하고 없으면 PAGE_KEEP 리턴
- L1045 : mapping이 없는 경우, private가 있으면 try_to_free_buffers()로 buffer를 free하고 성공하면 ClearPageDirty() 수행 후 PAGE_CLEAN 리턴, 만약 private가 없으면 PAGE_KEEP 리턴
- L1059 : mapping->a_ops->writepage == NULL 이면 PAGE_ACTIVATE 리턴
- L1061 : 함수 may_write_to_inode()로 mapping->host inode에 write 가능한지 보고 불가능하면 PAGE_KEEP 리턴
- L1064 : clear_page_dirty_for_io()로 page를 clear하고 원래 dirty였으면 1 리턴되어 if 문 진입
- L1066 : writeback_control wbc 셋업
- L1074 : SetPageReclaim()
- L1075 : mapping->a_ops->writepage() 수행하여 writeback
- L1078 : writeback이 완료되었으나 여전히 active page로 판단된 경우 ClearPageReclaim() 수행 후 PAGE_ACTIVATE 리턴
- L1083 : PageWriteback()이 0이면 sync write이거나 a_ops broken이므로 ClearPageReclaim() 수행
- L1088 : node의 NR_VMSCAN_WRITE를 1 증가
- L1089 : PAGE_SUCCESS 리턴
- L1090 : if 문 종료
- L1092 : page clean이므로 page out 안하고 그냥 PAGE_CLEAN 리턴

 

is_page_cache_freeable()
- 인자로 page를 받는다
- isolation에서 refcount 1 증가, add to page cache에서 nr 만큼 refcount 증가, buffer head allocation에서 refcount 1 증가하고 private에 1 추가한 것 고려하여 caller 외에 refcount 증가시킨 곳이 없음을 확인한다

 

may_write_to_inode()
- 인자로 inode를 받는다
- L979 : current->flags에 PF_SWAPWRITE가 있으면 1 리턴
- L981 : inode_write_congested()가 거짓이면 1 리턴
- L983 : inode의 backing device info가 current->backing_dev_info와 같으면 1 리턴
- L985 : 아니면 0 리턴
- 현재 상황에서는 anon의 경우 congested()가 항상 false로 1 리턴되고, file의 경우 보통의 경우 inode write congested 여부에 의해 1/0이 결정된다

 

inode_write_congested()
- inode를 인자로 받아 inode_congested()를 호출
- 호출할 때 WB_async_congested로 호출, read의 경우는 WB_sync_congested로 호출

 

inode_congested()
- 인자로 inode, congested bits cong_bits를 받는다
- L196 : inode가 존재하고 inode->i_wb이 존재하면 즉 write back 가능하면
- L921 : 함수 unlocked_inode_to_wb_begin()로 rcu read lock과 inode->i_mapping->i_pages에 대한 xarray lock을 잡고
- L922 : inode->i_wb->congested & cong_bits를 구해서 congested에 넣고
- L923 : unlocked_inode_to_wb_end()로 cu read lock과 inode->i_mapping->i_pages에 대한 xarray lock을 풀고 congested를 리턴한다
- L927 : 아니면 바로 wb_congested()로 inode의 backing device info의 write back의 congested인지 확인하고 리턴
- anon의 경우 inode_to_bdi()는 dummy devce info인 noop_backing_dev_info를 리턴하므로 그냥 NULL이 리턴되고 따라서 congested는 항상 0으로 리턴된다

 

clear_page_dirty_for_io()
- 인자로 page를 받는다
- L2692 : mapping에 page_mapping()을 할당
- L2695 : PageLocked가 아니면 버그
- L2697 : mapping이 존재하고 mapping_can_writeback()이 참이면 if 문 진입
- L2698 : inode에 mapping->host 할당
- L2727 : page_mkclean() 함수로 rmap을 통해 page와 연결된 모든 pte를 clean, readonly, dbm disable한다.
- L2728 : clear된 pte가 있으면 set_page_dirty()로 SetPageDirty 수행
- L2737 : 함수 unlocked_inode_to_wb_begin()로 rcu read lock과 inode->i_mapping->i_pages에 대한 xarray lock을 잡는다
- L2738 : TestClearPageDirty() 수행
- L2739 : 성공시 lruvec에서 NR_FILE_DIRTY 1 감소, zone에서 NR_ZONE_WRITE_PENDING 1 감소, wb에서 WB_RECLAIMABLE 1 감소하고 ret = 1
- L2744 : unlocked_inode_to_wb_end()로 cu read lock과 inode->i_mapping->i_pages에 대한 xarray lock을 풀고 ret을 리턴한다
- L2746 : if 문 종료
- L2748 : TestClearPageDirty() 수행하고 결과 리턴

 

mapping_can_writeback()
- 인자로 address_space mapping을 받는다
- mapping->host (inode)의 backing device info에서 capabilities 중 BDI_CAP_WRITEBACK이 있는지 리턴한다
- anon인 경우에는 false가 리턴된다

 

page_mkclean()
- 인자로 page를 받아 rmap을 통해 page와 연결된 모든 pte를 clean, readonly, dbm disable한다
- L994 : PageLocked가 아니면 버그
- L996 : page가 mapped되어 있지 않으면 그냥 0 리턴
- L999 : page_mapping()이 NULL이면 그냥 0 리턴
- L1003 : rmap_walk() 수행
- L1005 : clean된 pte 수를 리턴
- rmap 수행시 invalid_mkclean_vma() 함수로 VM_SHARED인 vma만 처리하고 아닌 vma는 그냥 패스한다

 

page_mkclean_one()
- L909 : pvmw flags로 PVMW_SYNC를 넣어준다
- L923 : page_vma_mapped_walk() 함수로 page, vma에 해당하는 pte를 구한다
- L927 : pte가 존재하면 if 문 진입
- L931 : pte_dirty()도 아니고 pte_write()도 아니면 continue로 pte 스킵
- L934 : flush_cache_page()는 빈 함수
- L935 : ptep_clear_flush()로 pte를 clear하고 원래 값을 entry로 옮긴다
- L936 : pte_wrprotect()로 DBM를 끄고 read only로 만든다. 대신 SW dirty를 set 한다
- L937 : pte_mkclean()으로 SW/HW dirty를 clear한다
- L938 : DBM, SW/HW dirty clear된 entry를 다시 pte에 기록한다
- L939 : ret = 1로 설정
- L958 : if 문 종료
- L967 : ret = 1이면 cleaned를 1 올려준다
- L969 : while 문 종료
- L973 : true 리턴

 

pte_dirty()
- pte_sw_dirty(pte) || pte_hw_dirty(pte)

 

pte_sw_dirty()
- pte 중 SW에서 관리하는 55번째 비트가 1인지 체크한다

 

pte_hw_dirty()
- DBM가 참이고 read only가 거짓인지 (write 가능한지) 체크한다

 

pte_wrprotect()
- 인자로 pte를 받는다
- L200 : pte_hw_dirty()이면 pte_mkdirty()로 SW dirty, HW dirty로 만든다
- L203 : DBM를 끈다
- L204 : read only로 만든다
- 즉 pte를 read only로 만드는데, 이러면 DBM 기능을 사용하는 경우 dirty 정보가 날라가므로 SW dirty를 set해준다
- L205 : pte 리턴

 

pte_mkdirty()
- pte를 인자로 받아 SW dirty인 55번째 bit set, DBM이 켜져 있는 경우 read only도 clear하여 HW dirty로 만든다

 

pte_mkclean()
- pte를 받아서 SW dirty bit (55th bit)을 클리어하고, read only로 만들어 HW dirty도 clear한다

 

set_page_dirty()
- 인자로 page를 받아 PG_Dirty를 set 한다
- L2593 : page_mapping()으로 address_space를 mapping으로 가져온다
- L2596 : mapping이 있으면
- L2607 : PageReclaim()이 참이면 clear하고
- L2609 : mapping->a_ops->set_page_dirty()를 수행하고 결과 리턴
- L2611 : 아니면 PageDirty()가 아닌 경우 TestSetPageDirty()로 page를 dirty로 만들고 1 리턴한다
- L2615 : 애초에 dirty이거나 set dirty에 실패하면 0 리턴

 

swap_set_page_dirty()
- swap의 경우 mapping->a_ops->set_page_dirty() 수행 시 이 함수를 사용한다
- L444 : page로부터 sis를 가져온다
- L446 : sis->flags에 SWP_FS_OPS가 있으면
- L447 : sis->swap_file->f_mapping을 mapping으로 가져오고
- L450 : mapping->a_ops->set_page_dirty()를 수행, 결과값 리턴
- L451 : 아니면 __set_page_dirty_no_writeback()으로 PageDirty가 아닌 경우 TestSetPageDirty() 수행하고 결과 리턴
- 보통의 경우 SWP_FS_OPS가 설정 안되어 있을 것이므로 PageDirty만 건드린다

 

swap_writepage()
- swap의 경우 mapping->a_ops->writepage()에서 본 함수 호출
- L232 : try_to_free_swap()로 굳이 write할 필요없는 page는 걸러내고, page unlock 후 0 리턴
- L240 : arch_prepare_to_swap() 함수로 page의 mte tag를 mte_pages xarray(전역 변수 xarray)에 저장한다
- L241 : 실패하면 다시 page를 dirty로 만들고 unlock 후 1 에러 리턴
- L246 : page에 대해 frontswap이 있을 경우 frontswap_store()로 frontswap에 write하고
- L247 : 성공시 set_page_writeback() 수행하고
- L248 : page unlock하고
- L249 : end_page_writeback() 수행 후 0 리턴한다
- L252 : frontswap이 없거나 실패하면 __swap_writepage() 수행하고 결과 리턴

 

frontswap_store()
- frontswap_enabled()가 참이면 __frontswap_store() 호출하고 아니면 -1 리턴

 

__frontswap_store()
- L268 : sis, offset에 해당하는 old page가 이미 frontswap에 존재하는지 __frontswap_test()으로 체크하고 존재하면 __frontswap_clear()한뒤 각 frontswap implementation에 대해 루프를 돌면서 invalidate_page()를 수행한다. __frontswap_test() / __frontswap_clear()는 sis->frontswap_map의 bit로 수행한다. (offset에 해당하는 bit set/clear)
- L275 : 각 frontswap implementation에 대해 ops->store()를 성공할 때까지 시도하고, 성공하면 break
- L280 : 성공한 경우 sis->frontswap_map에 offset bit를 set하고 frontswap store success stat을 1 증가
- L284 : 실패하면 frontswap store failure stat을 1 증가
- L286 : frontswap_writethrough_enabled = true이면 무조건 frontswap 실패로 간주 -1 리턴
- L289 : frontswap 결과를 리턴한다

 

set_page_writeback()
- test_set_page_writeback() 호출

 

test_set_page_writeback()
- __test_set_page_writeback() 호출

 

__test_set_page_writeback()
- page_mapping()에 해당하는 mapping->i_pages xarray에 writeback tag를 mark로 저장한다
- 동시에 TestSetPageWriteback()도 수행

 

end_page_writeback()
- L1591 : PageReclaim이 참이면 clear하고 rotate_reclaimable_page()로 page를 inactive list의 tail로 보낸다
- L1602 : get_page()
- L1603 : test_clear_page_writeback()로 PG_Writeback clear
- L1606 : memory barrier
- L1607 : wake_up_page()로 PG_writeback이었던 page를 wake up(?) —> 아마 해당 page의 PG_writeback이 사라지기를 기다리고 있던 waiter task들이 존재하는 듯함
- L1608 : put_page()

 

test_clear_page_writeback()
- page_mapping()에 해당하는 mapping->i_pages xarray에 writeback tag를 clear한다
- 동시에 TestClearPageWriteback()도 수행

 

__swap_writepage()
- L293 : sis->flags에 SWP_FS_OPS가 있으면 파일 시스템 레이어를 통해서 direct_IO 수행 (sync IO)
- L308 : set_page_writeback()
- L310 : mapping->a_ops->direct_IO()
- L330 : end_page_writeback()
- L332 : sis->flags에 SWP_FS_OPS가 없으면 노말 패스이다
- L334 : sis->bdev에 rw_page()가 존재하면 bdev_write_page()로 write 수행. 이는 zram, ramdisk, nvdimm, pmem 등 빠른 메모리로 sync로 수행되는 것으로 보임
- L335 : 성공하면 accounting하고 0 리턴
- L340 : 실패 시 정석적인 storage IO 수행
- L349 : set_page_writeback()
- L350 : page unlock
- L351 : submit_bio()로 io submit
- L353 : async이므로 work queue에 submit만 하고 0 리턴 —> 끝나면 async하게 end_page_writeback() 호출 됨

 

bdev_write_page()
- L369 : set_page_writeback()
- L370 : ops->rw_page(). 이 함수의 각 구현 내부에서 page_endio()를 호출하여 end_page_writeback()을 부르게 되어 있다
- L373 : end_page_writeback()

 

page_endio()
- 인자로 page, is_write, err를 받는다
- L1618 : is_write가 아니면, 즉 read이면
- L1619 : err가 없이 정상적으로 io 처리된 경우 SetPageUptodate() 수행
- L1621 : 에러가 발생하여 정상적으로 read io가 처리되지 못한 경우 ClearPageUptodate() 수행하고 SetPageError() 처리
- L1625 : 에러 발생 유무 무관하게 이후에 page unlock하고 리턴
- L1626 : write인 경우엔
- L1627 : 에러 발생하여 정상적으로 write io 처리 안되었으면
- L1630 : SetPageError() 처리하고
- L1632 : page_mapping()의 mapping에 에러 정보를 기록한다
- L1635 : 에러 발생 유무 무관하게 이후 end_page_writeback() 수행

 

ext4_writepage()
- file이고 ext4 파일 시스템인 경우 이 함수 호출
- L2064 : ext4_bio_write_page() 호출

 

ext4_bio_write_page()
- L452 : set_page_writeback()
- L490 : ext4_io_submit() 호출하여 io submit

 

ext4_end_bio()
- IO 끝나면 본 함수가 호출되는 것으로 보임
- L365 : ext4_finish_bio() 호출하여 내부에서 end_page_writeback() 호출

 

ext4_finish_bio()
- L143 : end_page_writeback() 호출하여 writeback clear

 

try_to_release_page()
- L3960 : PageWriteback()이면 그냥 리턴
- L3963 : page->mapping에 mapping->a_ops->releasepage() 있으면 수행하고 결과 리턴
- L3965 : 없으면 try_to_free_buffer()로 page buffer release

 

page_ref_freeze()
- cmpxchg로 page->refcount를 0으로 만들고 성공 여부를 반환한다

 

__remove_mapping()
- 인자로 address_space mapping, page, reclaimed 여부, mem_cgroup target_memcg를 받아 xarray에 저장된 file page cache 또는 swap cache를 삭제한다
- L1103 : shadow = NULL
- L1105 : !PageLocked 이거나 mapping != page_mapping(page) 이면 버그
- L1108 : mapping->i_pages xarray lock을 잡는다
- L1135 : page_ref_freeze() 수행하여 page의 refcount를 0으로 설정 시도 (예상 기존값은 1 + page nr), 실패하면 xarray unlock하고 0 리턴
- L1138 : PageDirty()이면 page_ref_unfreeze() 수행 후 xarray unlock하고 0 리턴
- L1143 : PageSwapcache()인 경우 if 문 진입
- L1144 : page의 private로부터 swap entry를 읽어온다
- L1145 : mem_cgroup_swapout() 함수로 memcg 관련 page swap 처리 수행(?)
- L1146 : reclaimed == true 이고 (현 상황은 true), mapping_exiting() == false이면 workingset_eviction()으로 shadow에 refault distance를 기록한다
- L1148 : __delete_from_swap_cache() 함수로 xarray의 swap cache page를 삭제하고 대신 shadow를 기록한다
- L1149 : xarray unlock
- L1150 : put_swap_page() 수행 (add_to_swap()에서 get_swap_page() 수행)
- L1151 : page가 file이면 else 문 진입
- L1154 : mapping->a_ops->freepage()를 freepage()로 가져온다
- L1171 : reclaimed == true 이고 page가 file이고 mapping exiting이 아니고 dax_mapping()이 아니면 workingset_eviction()으로 shadow에 refault distance를 기록한다
- L1174 : __delete_from_swap_cache() 함수로 xarray의 file cache page를 삭제하고 대신 shadow를 기록한다
- L1175 : xarray unlock
- L1177 : freepage()가 존재하면 freepage() 호출하여 free 수행
- L1179 : if else 문 종료
- L1181 : 1 리턴
- L1183 : cannot_free
- L1184 : xarray unlock하고 0 리턴

 

page_ref_unfreeze()
- 인자로 page, count를 받아 page의 refcount를 count로 set
- 이 때 page refcount != 0이면 버그이고, count == 0이어도 버그이다

 

mapping_exiting()
- 인자로 address_space mapping을 받아서, mapping->flags에 AS_EXITING이 있는지 체크한다
- exit 중인 address space를 체크하는 용도인 듯

 

workingset_eviction()
- 인자로 page, mem_cgroup target_memcg를 받는다
- L257 : page로부터 pgdat node 정보를 가져온다
- L263 : PageLRU()이면 버그
- L264 : page refcount 0이 아니면 버그
- L265 : PageLocked() 아니면 버그
- L267 : target_memcg 및 pgdat 노드에 따른 lruvec을 가져온다
- L269 : memcgid에 lruvec에 해당하는 memcg id를 가져온다
- L270 : lruvec->nonresident_age를 읽어서 eviction에 가져온다
- L271 : workingset_age_nonresident() 수행하여 lruvec->nonresident_age에 nr page를 더해준다
- L272 : pack_shadow() 함수로 eviction 값을 shadow 값으로 encoding하여 리턴한다

 

workingset_age_nonresident()
- 인자로 lruvec, nr_pages를 받는다
- L243 : lruvec과 모든 parent lruvec에 대해서 lruvec->nonresident_age에 nr_pages를 더해준다

 

pack_shadow()
- 인자로 memcgid, pgdat, eviction, workingset을 받아서 shadow로 encoding하여 리턴
- eviction | memcg id | node id | workingset 형태로 encoding한다
- encoding 이후 xarray value 형태로 만들어서 리턴

 

__delete_from_swap_cache()
- 인자로 page, swap entry, shadow를 받는다
- L155 : entry로부터 swap address space를 받는다
- L156 : page의 nr page를 구한다
- L157 : entry로부터 swap offset, 즉 index를 구한다
- L158 : address space->i_pages와 index에 해당하는 xarray를 설정한다
- L160 : PageLocked()가 아니면 버그
- L161 : PageSwapCache()가 아니면 버그
- L162 : PageWriteback()이면 버그
- L164 : nr page 수 만큼 루프를 돌면서 xarray node에 shadow를 기록하고 page private를 0으로 클리어한다
- L170 : ClearPageSwapCache()
- L171 : address space->nrpages를 nr 만큼 줄인다
- L172 : page node의 NR_FILE_PAGES를 nr 만큼 줄인다
- L173 : page lruvec의 NR_SWAPCACHE를 nr 만큼 줄인다
- L174 : swap_cache_info.del_total을 nr 만큼 늘린다

번호 제목 글쓴이 날짜 조회 수
공지 [공지] 스터디 정리 노트 공간입니다. woos 2016.05.14 626
186 [커널 19차] 41 주차 이태백 2023.03.04 101
185 [커널 18차] 93주차 kkr 2023.03.04 54
184 [커널 18차] 91주차 kkr 2023.02.18 97
183 [커널 19차] 39 주차 Min 2023.02.18 53
182 [커널 18차] 90주차 kkr 2023.02.13 63
181 [커널 19차] 38 주차 Min 2023.02.11 45
180 [커널 19차] 37 주차 Min 2023.02.04 478
179 [커널 19차] 36 주차 Min 2023.01.28 85
178 [커널 18차] 88주차 kkr 2023.01.28 55
177 [커널 19차] 35 주차 Min 2023.01.14 93
176 [커널 17차] 120 ~ 121주차 ㅇㅇㅇ 2023.01.08 111
175 [커널 18차] 85주차 kkr 2023.01.07 53
174 [커널 19차] 34 주차 Min 2023.01.07 42
173 [커널 18차] 84주차 kkr 2022.12.31 104
172 [커널 19차] 33 주차 Min 2022.12.31 51
171 [커널 17차] 117 ~ 119주차 ㅇㅇㅇ 2022.12.25 62
170 [커널 19차] 31 주차 Min 2022.12.17 63
169 [커널 19차] 30 주차 Min 2022.12.10 61
» [커널 17차] 112 ~ 116주차 ㅇㅇㅇ 2022.12.05 73
167 [커널 18차] 80주차 kkr 2022.12.03 156
XE Login