← Architecture 비교DUJINLABS.COM

Linux 6.18.37 LTS · Architecture comparison 05/21

TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA

PTE 변경을 다른 CPU가 언제부터 관찰하는지 range flush, ASID tag, IPI와 completion 경계로 분석합니다.

비교 대상
arm64 / x86-64 / RISC-V
실제 원본
3 files · 203 annotated lines
기준 tag
Linux v6.18.37
분석 축
state · ordering · lifetime · latency

01 · QUESTION

무엇을 확인할 것인가

PTE를 memory에서 바꾼 뒤 remote CPU가 old translation을 더 이상 사용하지 않는 시점은 언제인가?

initiator는 PTE write를 publish하고 mm를 실행할 수 있는 CPU mask를 얻어 local invalidate와 remote request를 보낸다. page-table page free는 모든 대상 CPU의 invalidate completion 뒤에만 가능하다.

range와 full-mm 선택, freed_tables 여부, huge mapping stride와 lazy CPU 제외가 shootdown 비용과 correctness를 결정한다.

지연 시간 관점Tshoot = Tpublish + max(Tipi_delivery + Tremote_flush) + Tack. CPU 수보다 실제 mm cpumask 크기와 interrupt-off 구간이 tail latency를 지배한다.

02 · CONTRACT

공통 계약과 architecture 구현

architecture핵심 mechanism실패 형태확인할 상태
arm64TLBI ASIDE1/VAAE1 계열과 DSB/ISB sequenceTLBI만 실행하고 completion barrier를 빼면 page free 뒤 remote walker가 old PA를 사용한다.TLBI operand의 ASID/VA, range scale, DSB domain과 mm cpumask를 확인한다.
x86-64INVLPG, INVPCID 또는 CR3 reload와 TLB generationCPU가 mm를 막 load하는 순간 대상 mask에서 빠지면 stale PTE를 무기한 사용한다.start/end, stride_shift, freed_tables, new_tlb_gen, INVPCID capability와 target cpumask를 본다.
RISC-Vlocal/remote SFENCE.VMA와 SBI RFENCE 또는 IPISBI/firmware가 잘못된 hart mask나 ASID를 받으면 일부 hart에만 stale translation이 남는다.start/end, page size, ASID, hart mask, SBI return과 remote completion을 확인한다.

03 · DIAGRAMS

세 그림으로 먼저 읽기

그림 1. 같은 목적, 서로 다른 mechanism각 ISA에서 실제로 추적할 state와 checkpoint를 한 줄에 맞췄습니다.

arm64

mechanism
TLBI ASIDE1/VAAE1 계열과 DSB/ISB sequence
state
range TLBI 지원 여부와 granule에 따라 한 명령 또는 반복 invalidate를 선택한다. inner-shareable TLBI와 DSB가 다른 PE의 completion을 보장한다.
checkpoint
TLBI operand의 ASID/VA, range scale, DSB domain과 mm cpumask를 확인한다.

x86-64

mechanism
INVLPG, INVPCID 또는 CR3 reload와 TLB generation
state
range page 수, ceiling과 freed_tables에 따라 per-page INVLPG와 full context flush를 고른다. remote CPU는 flush info의 new_tlb_gen을 적용한다.
checkpoint
start/end, stride_shift, freed_tables, new_tlb_gen, INVPCID capability와 target cpumask를 본다.

RISC-V

mechanism
local/remote SFENCE.VMA와 SBI RFENCE 또는 IPI
state
ASID와 range 크기에 따라 local fence, IPI 기반 remote fence 또는 SBI remote sfence를 선택한다. hardware range invalidate가 제한적이면 page별 또는 전체 fence로 확대한다.
checkpoint
start/end, page size, ASID, hart mask, SBI return과 remote completion을 확인한다.
그림 2. 공통 kernel과 architecture hook의 소유권공통 정책이 hardware state를 직접 소유하지 않는 경계를 표시합니다.
Linux common contractinitiator는 PTE write를 publish하고 mm를 실행할 수 있는 CPU mask를 얻어 local invalidate와 remote request를 보낸다. page-table page free는 모든 대상 CPU의 invalidate completion 뒤에만 가능하다.
arm64TLBI ASIDE1/VAAE1 계열과 DSB/ISB sequencePTE store 뒤 DSB, TLBI, completion DSB와 필요한 ISB 순서를 지킨다.
x86-64INVLPG, INVPCID 또는 CR3 reload와 TLB generationswitch_mm의 loaded_mm/tlb_gen publication과 shootdown side의 mm_cpumask read가 ordering pair를 이룬다.
RISC-Vlocal/remote SFENCE.VMA와 SBI RFENCE 또는 IPIPTE store의 visibility와 remote fence request ordering이 보장되어야 receiver가 새 PTE를 읽는다.
lifetime boundaryinvalidated physical page와 page-table page는 remote TLB entry가 사라지기 전까지 재사용하면 안 된다. mmu_gather가 이 deferred free를 관리한다.
그림 3. publication과 관찰 순서state를 준비한 뒤 architecture ordering을 거쳐 관찰 가능한 checkpoint가 됩니다.
arm64state 준비PTE store 뒤 DSB, TLBI, completion DSB와 필요한 ISB 순서를 지킨다.관찰: TLBI operand의 ASID/VA, range scale, DSB domain과 mm cpumask를 확인한다.
x86-64state 준비switch_mm의 loaded_mm/tlb_gen publication과 shootdown side의 mm_cpumask read가 ordering pair를 이룬다.관찰: start/end, stride_shift, freed_tables, new_tlb_gen, INVPCID capability와 target cpumask를 본다.
RISC-Vstate 준비PTE store의 visibility와 remote fence request ordering이 보장되어야 receiver가 새 PTE를 읽는다.관찰: start/end, page size, ASID, hart mask, SBI return과 remote completion을 확인한다.

04 · SOURCE

Linux 6.18.37 원본 코드와 줄별 설명

소스 위치를 고정된 숫자로 복사하지 않고 Linux v6.18.37 tree에서 함수 선언을 다시 찾아 발췌했습니다. 아래 코드와 각 줄의 설명은 1:1로 대응합니다.

arm64 · Linux 6.18.37

TLBI ASIDE1/VAAE1 계열과 DSB/ISB sequence

range TLBI 지원 여부와 granule에 따라 한 명령 또는 반복 invalidate를 선택한다. inner-shareable TLBI와 DSB가 다른 PE의 completion을 보장한다.

원본 코드: arch/arm64/include/asm/tlbflush.h:364-420

364static inline void flush_tlb_all(void)
365{
366	dsb(ishst);
367	__tlbi(vmalle1is);
368	__tlbi_sync_s1ish_kernel();
369	isb();
370}
371 
372static inline void flush_tlb_mm(struct mm_struct *mm)
373{
374	unsigned long asid;
375 
376	dsb(ishst);
377	asid = __TLBI_VADDR(0, ASID(mm));
378	__tlbi(aside1is, asid);
379	__tlbi_user(aside1is, asid);
380	__tlbi_sync_s1ish(mm);
381	mmu_notifier_arch_invalidate_secondary_tlbs(mm, 0, -1UL);
382}
383 
384static inline void __flush_tlb_page_nosync(struct mm_struct *mm,
385					   unsigned long uaddr)
386{
387	unsigned long addr;
388 
389	dsb(ishst);
390	addr = __TLBI_VADDR(uaddr, ASID(mm));
391	__tlbi(vale1is, addr);
392	__tlbi_user(vale1is, addr);
393	mmu_notifier_arch_invalidate_secondary_tlbs(mm, uaddr & PAGE_MASK,
394						(uaddr & PAGE_MASK) + PAGE_SIZE);
395}
396 
397static inline void flush_tlb_page_nosync(struct vm_area_struct *vma,
398					 unsigned long uaddr)
399{
400	return __flush_tlb_page_nosync(vma->vm_mm, uaddr);
401}
402 
403static inline void flush_tlb_page(struct vm_area_struct *vma,
404				  unsigned long uaddr)
405{
406	flush_tlb_page_nosync(vma, uaddr);
407	__tlbi_sync_s1ish(vma->vm_mm);
408}
409 
410static inline bool arch_tlbbatch_should_defer(struct mm_struct *mm)
411{
412	return true;
413}
414 
415/*
416 * To support TLB batched flush for multiple pages unmapping, we only send
417 * the TLBI for each page in arch_tlbbatch_add_pending() and wait for the
418 * completion at the end in arch_tlbbatch_flush(). Since we've already issued
419 * TLBI for each page so only a DSB is needed to synchronise its effect on the
420 * other CPUs.

라인 바이 라인 주석

빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 57개 줄에 각각 설명을 붙였습니다.

L364static inline void flush_tlb_all(void)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L365{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L366 dsb(ishst);

memory, translation 또는 instruction-fetch 관찰 순서를 확정하는 architecture 명령이다. 앞에서 publish한 상태와 뒤에서 재사용하는 상태의 경계를 이 줄에 둔다.

L367 __tlbi(vmalle1is);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L368 __tlbi_sync_s1ish_kernel();

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L369 isb();

memory, translation 또는 instruction-fetch 관찰 순서를 확정하는 architecture 명령이다. 앞에서 publish한 상태와 뒤에서 재사용하는 상태의 경계를 이 줄에 둔다.

L370}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L371(blank)

빈 줄은 arm64 TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L372static inline void flush_tlb_mm(struct mm_struct *mm)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L373{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L374 unsigned long asid;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L375(blank)

빈 줄은 arm64 TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L376 dsb(ishst);

memory, translation 또는 instruction-fetch 관찰 순서를 확정하는 architecture 명령이다. 앞에서 publish한 상태와 뒤에서 재사용하는 상태의 경계를 이 줄에 둔다.

L377 asid = __TLBI_VADDR(0, ASID(mm));

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L378 __tlbi(aside1is, asid);

inner-shareable domain에서 해당 ASID의 stage-1 translation을 무효화한다.

L379 __tlbi_user(aside1is, asid);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L380 __tlbi_sync_s1ish(mm);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L381 mmu_notifier_arch_invalidate_secondary_tlbs(mm, 0, -1UL);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L382}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L383(blank)

빈 줄은 arm64 TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L384static inline void __flush_tlb_page_nosync(struct mm_struct *mm,

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L385 unsigned long uaddr)

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L386{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L387 unsigned long addr;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L388(blank)

빈 줄은 arm64 TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L389 dsb(ishst);

memory, translation 또는 instruction-fetch 관찰 순서를 확정하는 architecture 명령이다. 앞에서 publish한 상태와 뒤에서 재사용하는 상태의 경계를 이 줄에 둔다.

L390 addr = __TLBI_VADDR(uaddr, ASID(mm));

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L391 __tlbi(vale1is, addr);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L392 __tlbi_user(vale1is, addr);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L393 mmu_notifier_arch_invalidate_secondary_tlbs(mm, uaddr & PAGE_MASK,

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L394 (uaddr & PAGE_MASK) + PAGE_SIZE);

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L395}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L396(blank)

빈 줄은 arm64 TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L397static inline void flush_tlb_page_nosync(struct vm_area_struct *vma,

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L398 unsigned long uaddr)

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L399{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L400 return __flush_tlb_page_nosync(vma->vm_mm, uaddr);

이 함수가 TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L401}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L402(blank)

빈 줄은 arm64 TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L403static inline void flush_tlb_page(struct vm_area_struct *vma,

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L404 unsigned long uaddr)

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L405{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L406 flush_tlb_page_nosync(vma, uaddr);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L407 __tlbi_sync_s1ish(vma->vm_mm);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L408}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L409(blank)

빈 줄은 arm64 TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L410static inline bool arch_tlbbatch_should_defer(struct mm_struct *mm)

이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L411{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L412 return true;

이 함수가 TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L413}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L414(blank)

빈 줄은 arm64 TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L415/*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L416 * To support TLB batched flush for multiple pages unmapping, we only send

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L417 * the TLBI for each page in arch_tlbbatch_add_pending() and wait for the

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L418 * completion at the end in arch_tlbbatch_flush(). Since we've already issued

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L419 * TLBI for each page so only a DSB is needed to synchronise its effect on the

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L420 * other CPUs.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

x86-64 · Linux 6.18.37

INVLPG, INVPCID 또는 CR3 reload와 TLB generation

range page 수, ceiling과 freed_tables에 따라 per-page INVLPG와 full context flush를 고른다. remote CPU는 flush info의 new_tlb_gen을 적용한다.

원본 코드: arch/x86/mm/tlb.c:1440-1520

1440{
1441#ifdef CONFIG_DEBUG_VM
1442	/* Complete reentrancy prevention checks */
1443	barrier();
1444	this_cpu_dec(flush_tlb_info_idx);
1445#endif
1446}
1447 
1448void flush_tlb_mm_range(struct mm_struct *mm, unsigned long start,
1449				unsigned long end, unsigned int stride_shift,
1450				bool freed_tables)
1451{
1452	struct flush_tlb_info *info;
1453	int cpu = get_cpu();
1454	u64 new_tlb_gen;
1455 
1456	/* This is also a barrier that synchronizes with switch_mm(). */
1457	new_tlb_gen = inc_mm_tlb_gen(mm);
1458 
1459	info = get_flush_tlb_info(mm, start, end, stride_shift, freed_tables,
1460				  new_tlb_gen);
1461 
1462	/*
1463	 * flush_tlb_multi() is not optimized for the common case in which only
1464	 * a local TLB flush is needed. Optimize this use-case by calling
1465	 * flush_tlb_func_local() directly in this case.
1466	 */
1467	if (mm_global_asid(mm)) {
1468		broadcast_tlb_flush(info);
1469	} else if (cpumask_any_but(mm_cpumask(mm), cpu) < nr_cpu_ids) {
1470		info->trim_cpumask = should_trim_cpumask(mm);
1471		flush_tlb_multi(mm_cpumask(mm), info);
1472		consider_global_asid(mm);
1473	} else if (mm == this_cpu_read(cpu_tlbstate.loaded_mm)) {
1474		lockdep_assert_irqs_enabled();
1475		local_irq_disable();
1476		flush_tlb_func(info);
1477		local_irq_enable();
1478	}
1479 
1480	put_flush_tlb_info();
1481	put_cpu();
1482	mmu_notifier_arch_invalidate_secondary_tlbs(mm, start, end);
1483}
1484 
1485static void do_flush_tlb_all(void *info)
1486{
1487	count_vm_tlb_event(NR_TLB_REMOTE_FLUSH_RECEIVED);
1488	__flush_tlb_all();
1489}
1490 
1491void flush_tlb_all(void)
1492{
1493	count_vm_tlb_event(NR_TLB_REMOTE_FLUSH);
1494 
1495	/* First try (faster) hardware-assisted TLB invalidation. */
1496	if (cpu_feature_enabled(X86_FEATURE_INVLPGB))
1497		invlpgb_flush_all();
1498	else
1499		/* Fall back to the IPI-based invalidation. */
1500		on_each_cpu(do_flush_tlb_all, NULL, 1);
1501}
1502 
1503/* Flush an arbitrarily large range of memory with INVLPGB. */
1504static void invlpgb_kernel_range_flush(struct flush_tlb_info *info)
1505{
1506	unsigned long addr, nr;
1507 
1508	for (addr = info->start; addr < info->end; addr += nr << PAGE_SHIFT) {
1509		nr = (info->end - addr) >> PAGE_SHIFT;
1510 
1511		/*
1512		 * INVLPGB has a limit on the size of ranges it can
1513		 * flush. Break up large flushes.
1514		 */
1515		nr = clamp_val(nr, 1, invlpgb_count_max);
1516 
1517		invlpgb_flush_addr_nosync(addr, nr);
1518	}
1519	__tlbsync();
1520}

라인 바이 라인 주석

빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 81개 줄에 각각 설명을 붙였습니다.

L1440{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1441#ifdef CONFIG_DEBUG_VM

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L1442 /* Complete reentrancy prevention checks */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1443 barrier();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1444 this_cpu_dec(flush_tlb_info_idx);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1445#endif

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L1446}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1447(blank)

빈 줄은 x86-64 TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1448void flush_tlb_mm_range(struct mm_struct *mm, unsigned long start,

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L1449 unsigned long end, unsigned int stride_shift,

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L1450 bool freed_tables)

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L1451{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1452 struct flush_tlb_info *info;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L1453 int cpu = get_cpu();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1454 u64 new_tlb_gen;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L1455(blank)

빈 줄은 x86-64 TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1456 /* This is also a barrier that synchronizes with switch_mm(). */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1457 new_tlb_gen = inc_mm_tlb_gen(mm);

PTE 변경 세대를 올려 switch path와 remote flush가 stale generation을 탐지하게 한다.

L1458(blank)

빈 줄은 x86-64 TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1459 info = get_flush_tlb_info(mm, start, end, stride_shift, freed_tables,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L1460 new_tlb_gen);

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L1461(blank)

빈 줄은 x86-64 TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1462 /*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1463 * flush_tlb_multi() is not optimized for the common case in which only

선택한 CPU mask에 shootdown callback을 보내고 completion을 기다리는 경계다.

L1464 * a local TLB flush is needed. Optimize this use-case by calling

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1465 * flush_tlb_func_local() directly in this case.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1466 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1467 if (mm_global_asid(mm)) {

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L1468 broadcast_tlb_flush(info);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1469 } else if (cpumask_any_but(mm_cpumask(mm), cpu) < nr_cpu_ids) {

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L1470 info->trim_cpumask = should_trim_cpumask(mm);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1471 flush_tlb_multi(mm_cpumask(mm), info);

선택한 CPU mask에 shootdown callback을 보내고 completion을 기다리는 경계다.

L1472 consider_global_asid(mm);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1473 } else if (mm == this_cpu_read(cpu_tlbstate.loaded_mm)) {

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L1474 lockdep_assert_irqs_enabled();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1475 local_irq_disable();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1476 flush_tlb_func(info);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1477 local_irq_enable();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1478 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1479(blank)

빈 줄은 x86-64 TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1480 put_flush_tlb_info();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1481 put_cpu();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1482 mmu_notifier_arch_invalidate_secondary_tlbs(mm, start, end);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1483}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1484(blank)

빈 줄은 x86-64 TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1485static void do_flush_tlb_all(void *info)

이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L1486{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1487 count_vm_tlb_event(NR_TLB_REMOTE_FLUSH_RECEIVED);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1488 __flush_tlb_all();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1489}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1490(blank)

빈 줄은 x86-64 TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1491void flush_tlb_all(void)

이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L1492{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1493 count_vm_tlb_event(NR_TLB_REMOTE_FLUSH);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1494(blank)

빈 줄은 x86-64 TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1495 /* First try (faster) hardware-assisted TLB invalidation. */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1496 if (cpu_feature_enabled(X86_FEATURE_INVLPGB))

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L1497 invlpgb_flush_all();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1498 else

앞 조건이 성립하지 않았을 때의 대체 경로다. fast path와 같은 ownership, ordering과 반환 계약을 제공해야 한다.

L1499 /* Fall back to the IPI-based invalidation. */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1500 on_each_cpu(do_flush_tlb_all, NULL, 1);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1501}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1502(blank)

빈 줄은 x86-64 TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1503/* Flush an arbitrarily large range of memory with INVLPGB. */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1504static void invlpgb_kernel_range_flush(struct flush_tlb_info *info)

이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L1505{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1506 unsigned long addr, nr;

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L1507(blank)

빈 줄은 x86-64 TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1508 for (addr = info->start; addr < info->end; addr += nr << PAGE_SHIFT) {

range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.

L1509 nr = (info->end - addr) >> PAGE_SHIFT;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L1510(blank)

빈 줄은 x86-64 TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1511 /*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1512 * INVLPGB has a limit on the size of ranges it can

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1513 * flush. Break up large flushes.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1514 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L1515 nr = clamp_val(nr, 1, invlpgb_count_max);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1516(blank)

빈 줄은 x86-64 TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L1517 invlpgb_flush_addr_nosync(addr, nr);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1518 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L1519 __tlbsync();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L1520}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

RISC-V · Linux 6.18.37

local/remote SFENCE.VMA와 SBI RFENCE 또는 IPI

ASID와 range 크기에 따라 local fence, IPI 기반 remote fence 또는 SBI remote sfence를 선택한다. hardware range invalidate가 제한적이면 page별 또는 전체 fence로 확대한다.

원본 코드: arch/riscv/mm/tlbflush.c:149-213

149		mmu_notifier_arch_invalidate_secondary_tlbs(mm, start, start + size);
150}
151 
152void flush_tlb_mm(struct mm_struct *mm)
153{
154	__flush_tlb_range(mm, mm_cpumask(mm), 0, FLUSH_TLB_MAX_SIZE, PAGE_SIZE);
155}
156 
157void flush_tlb_mm_range(struct mm_struct *mm,
158			unsigned long start, unsigned long end,
159			unsigned int page_size)
160{
161	__flush_tlb_range(mm, mm_cpumask(mm), start, end - start, page_size);
162}
163 
164void flush_tlb_page(struct vm_area_struct *vma, unsigned long addr)
165{
166	__flush_tlb_range(vma->vm_mm, mm_cpumask(vma->vm_mm),
167			  addr, PAGE_SIZE, PAGE_SIZE);
168}
169 
170void flush_tlb_range(struct vm_area_struct *vma, unsigned long start,
171		     unsigned long end)
172{
173	unsigned long stride_size;
174 
175	if (!is_vm_hugetlb_page(vma)) {
176		stride_size = PAGE_SIZE;
177	} else {
178		stride_size = huge_page_size(hstate_vma(vma));
179 
180		/*
181		 * As stated in the privileged specification, every PTE in a
182		 * NAPOT region must be invalidated, so reset the stride in that
183		 * case.
184		 */
185		if (has_svnapot()) {
186			if (stride_size >= PGDIR_SIZE)
187				stride_size = PGDIR_SIZE;
188			else if (stride_size >= P4D_SIZE)
189				stride_size = P4D_SIZE;
190			else if (stride_size >= PUD_SIZE)
191				stride_size = PUD_SIZE;
192			else if (stride_size >= PMD_SIZE)
193				stride_size = PMD_SIZE;
194			else
195				stride_size = PAGE_SIZE;
196		}
197	}
198 
199	__flush_tlb_range(vma->vm_mm, mm_cpumask(vma->vm_mm),
200			  start, end - start, stride_size);
201}
202 
203void flush_tlb_kernel_range(unsigned long start, unsigned long end)
204{
205	__flush_tlb_range(NULL, cpu_online_mask,
206			  start, end - start, PAGE_SIZE);
207}
208 
209#ifdef CONFIG_TRANSPARENT_HUGEPAGE
210void flush_pmd_tlb_range(struct vm_area_struct *vma, unsigned long start,
211			unsigned long end)
212{
213	__flush_tlb_range(vma->vm_mm, mm_cpumask(vma->vm_mm),

라인 바이 라인 주석

빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 65개 줄에 각각 설명을 붙였습니다.

L149 mmu_notifier_arch_invalidate_secondary_tlbs(mm, start, start + size);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L150}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L151(blank)

빈 줄은 RISC-V TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L152void flush_tlb_mm(struct mm_struct *mm)

이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L153{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L154 __flush_tlb_range(mm, mm_cpumask(mm), 0, FLUSH_TLB_MAX_SIZE, PAGE_SIZE);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L155}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L156(blank)

빈 줄은 RISC-V TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L157void flush_tlb_mm_range(struct mm_struct *mm,

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L158 unsigned long start, unsigned long end,

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L159 unsigned int page_size)

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L160{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L161 __flush_tlb_range(mm, mm_cpumask(mm), start, end - start, page_size);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L162}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L163(blank)

빈 줄은 RISC-V TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L164void flush_tlb_page(struct vm_area_struct *vma, unsigned long addr)

이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L165{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L166 __flush_tlb_range(vma->vm_mm, mm_cpumask(vma->vm_mm),

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L167 addr, PAGE_SIZE, PAGE_SIZE);

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L168}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L169(blank)

빈 줄은 RISC-V TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L170void flush_tlb_range(struct vm_area_struct *vma, unsigned long start,

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L171 unsigned long end)

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L172{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L173 unsigned long stride_size;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L174(blank)

빈 줄은 RISC-V TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L175 if (!is_vm_hugetlb_page(vma)) {

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L176 stride_size = PAGE_SIZE;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L177 } else {

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L178 stride_size = huge_page_size(hstate_vma(vma));

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L179(blank)

빈 줄은 RISC-V TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L180 /*

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L181 * As stated in the privileged specification, every PTE in a

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L182 * NAPOT region must be invalidated, so reset the stride in that

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L183 * case.

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L184 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L185 if (has_svnapot()) {

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L186 if (stride_size >= PGDIR_SIZE)

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L187 stride_size = PGDIR_SIZE;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L188 else if (stride_size >= P4D_SIZE)

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L189 stride_size = P4D_SIZE;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L190 else if (stride_size >= PUD_SIZE)

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L191 stride_size = PUD_SIZE;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L192 else if (stride_size >= PMD_SIZE)

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L193 stride_size = PMD_SIZE;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L194 else

앞 조건이 성립하지 않았을 때의 대체 경로다. fast path와 같은 ownership, ordering과 반환 계약을 제공해야 한다.

L195 stride_size = PAGE_SIZE;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L196 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L197 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L198(blank)

빈 줄은 RISC-V TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L199 __flush_tlb_range(vma->vm_mm, mm_cpumask(vma->vm_mm),

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L200 start, end - start, stride_size);

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L201}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L202(blank)

빈 줄은 RISC-V TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L203void flush_tlb_kernel_range(unsigned long start, unsigned long end)

이 함수의 진입 계약이 시작된다. RISC-V에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.

L204{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L205 __flush_tlb_range(NULL, cpu_online_mask,

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L206 start, end - start, PAGE_SIZE);

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L207}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L208(blank)

빈 줄은 RISC-V TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L209#ifdef CONFIG_TRANSPARENT_HUGEPAGE

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L210void flush_pmd_tlb_range(struct vm_area_struct *vma, unsigned long start,

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L211 unsigned long end)

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L212{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L213 __flush_tlb_range(vma->vm_mm, mm_cpumask(vma->vm_mm),

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. TLB shootdown: TLBI, INVLPG/INVPCID와 SFENCE.VMA의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

05 · WORKED EXAMPLE

숫자로 검산하기

01

4KiB range flush와 full flush 선택

256KiB 범위, 4KiB page, mm 사용 CPU 6개, per-page invalidate 80ns, IPI 왕복 2us라고 가정한다.

  1. pages256KiB / 4KiB = 64개 translation이다.
  2. local costpage별이면 64x80ns=5.12us로 full-context flush 비용과 비교한다.
  3. remote cost6개 CPU에 병렬 IPI를 보내도 완료 시간은 가장 늦은 CPU의 2us+flush 시간이다.
  4. free boundary모든 ack 뒤에만 unmapped physical page와 table page를 allocator에 반환한다.

결론threshold는 page 수만이 아니라 CPU mask 크기, PCID/ASID 유지 이점과 remote interrupt latency에 따라 달라진다.

06 · DEEP DIVE

경계별 상세 분석

01

공통 kernel core와 architecture hook의 경계

initiator는 PTE write를 publish하고 mm를 실행할 수 있는 CPU mask를 얻어 local invalidate와 remote request를 보낸다. page-table page free는 모든 대상 CPU의 invalidate completion 뒤에만 가능하다.

range와 full-mm 선택, freed_tables 여부, huge mapping stride와 lazy CPU 제외가 shootdown 비용과 correctness를 결정한다.

02

arm64: TLBI ASIDE1/VAAE1 계열과 DSB/ISB sequence

range TLBI 지원 여부와 granule에 따라 한 명령 또는 반복 invalidate를 선택한다. inner-shareable TLBI와 DSB가 다른 PE의 completion을 보장한다.

PTE store 뒤 DSB, TLBI, completion DSB와 필요한 ISB 순서를 지킨다. 디버깅할 때는 TLBI operand의 ASID/VA, range scale, DSB domain과 mm cpumask를 확인한다.

03

x86-64: INVLPG, INVPCID 또는 CR3 reload와 TLB generation

range page 수, ceiling과 freed_tables에 따라 per-page INVLPG와 full context flush를 고른다. remote CPU는 flush info의 new_tlb_gen을 적용한다.

switch_mm의 loaded_mm/tlb_gen publication과 shootdown side의 mm_cpumask read가 ordering pair를 이룬다. 디버깅할 때는 start/end, stride_shift, freed_tables, new_tlb_gen, INVPCID capability와 target cpumask를 본다.

04

RISC-V: local/remote SFENCE.VMA와 SBI RFENCE 또는 IPI

ASID와 range 크기에 따라 local fence, IPI 기반 remote fence 또는 SBI remote sfence를 선택한다. hardware range invalidate가 제한적이면 page별 또는 전체 fence로 확대한다.

PTE store의 visibility와 remote fence request ordering이 보장되어야 receiver가 새 PTE를 읽는다. 디버깅할 때는 start/end, page size, ASID, hart mask, SBI return과 remote completion을 확인한다.

05

객체 수명과 소유권을 먼저 고정한다

invalidated physical page와 page-table page는 remote TLB entry가 사라지기 전까지 재사용하면 안 된다. mmu_gather가 이 deferred free를 관리한다.

주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.

06

latency upper bound는 hardware instruction 하나가 아니다

Tshoot = Tpublish + max(Tipi_delivery + Tremote_flush) + Tack. CPU 수보다 실제 mm cpumask 크기와 interrupt-off 구간이 tail latency를 지배한다.

평균값 외에 interrupt-off 구간, remote CPU 응답, firmware 호출과 retry 횟수를 분리해야 최악 지연의 원인을 찾을 수 있다.

07 · FAILURE

실패를 어떤 증거로 나눌 것인가

분류관찰되는 결과첫 확인값
arm64TLBI만 실행하고 completion barrier를 빼면 page free 뒤 remote walker가 old PA를 사용한다.TLBI operand의 ASID/VA, range scale, DSB domain과 mm cpumask를 확인한다.
x86-64CPU가 mm를 막 load하는 순간 대상 mask에서 빠지면 stale PTE를 무기한 사용한다.start/end, stride_shift, freed_tables, new_tlb_gen, INVPCID capability와 target cpumask를 본다.
RISC-VSBI/firmware가 잘못된 hart mask나 ASID를 받으면 일부 hart에만 stale translation이 남는다.start/end, page size, ASID, hart mask, SBI return과 remote completion을 확인한다.

08 · LAB

재현과 계측 절차

  1. tlb:tlb_flush와 IPI trace를 함께 수집해 initiator와 가장 늦은 remote CPU를 찾는다.
  2. CPU 하나에서 interrupt를 길게 막아 shootdown upper bound가 어떻게 늘어나는지 측정한다.
  3. 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
  4. 소스만 읽고 끝내지 않고 최종 vmlinuxobjdump -dr, readelf -SW 결과로 선택된 alternative와 section 배치를 확인한다.

09 · REFERENCES

원문 좌표

Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.