← Architecture 비교DUJINLABS.COM

Linux 6.18.37 LTS · Architecture comparison 04/21

Address-space switch: TTBR, CR3/PCID와 SATP/ASID

task의 mm_struct가 바뀔 때 page-table root와 ASID/PCID를 재사용하고 generation wrap을 처리하는 방식을 비교합니다.

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

01 · QUESTION

무엇을 확인할 것인가

새 mm의 page-table root를 쓰면서도 매 context switch마다 전체 TLB를 비우지 않는 근거는 무엇인가?

각 mm에 hardware address-space tag와 generation을 배정한다. tag가 현재 generation에서 유효하면 root를 tag와 함께 설치해 다른 mm translation과 공존시키고, wrap이면 전체 invalidate 뒤 generation을 올린다.

mm cpumask publication, page-table root write와 speculation barrier 순서가 TLB shootdown의 대상 CPU 선택과 맞아야 한다.

지연 시간 관점fast path는 valid tag와 같은 root 재사용이고 slow path는 allocation, wrap flush, IBPB/L1D mitigation과 lazy TLB 처리다.

02 · CONTRACT

공통 계약과 architecture 구현

architecture핵심 mechanism실패 형태확인할 상태
arm64TTBR0_EL1에 ASID와 PGD PA를 조합ASID wrap flush가 누락되면 새 mm가 이전 mm의 동일 ASID translation을 사용한다.asid generation, TTBR0_EL1, CONTEXTIDR_EL1, active_asids와 reserved_ttbr0를 본다.
x86-64CR3에 PGD PA, PCID와 no-flush bit를 조합no-flush CR3를 stale PCID에 쓰면 다른 mm translation이 살아남는다.CR3, PCID, tlb_gen, loaded_mm, user/kernel PCID와 IBPB decision을 기록한다.
RISC-VSATP.MODE, ASID와 root PPN을 조합ASID version wrap 또는 icache stale bit 누락은 data translation이나 새 code 관찰을 깨뜨린다.SATP MODE/ASID/PPN, context id version, cpumask와 icache_stale_mask를 확인한다.

03 · DIAGRAMS

세 그림으로 먼저 읽기

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

arm64

mechanism
TTBR0_EL1에 ASID와 PGD PA를 조합
state
check_and_switch_context()는 mm context id generation을 검사하고 필요하면 새 ASID를 배정한다. CnP, PAN, EPD0와 reserved TTBR0 사용 여부가 실제 register write를 바꾼다.
checkpoint
asid generation, TTBR0_EL1, CONTEXTIDR_EL1, active_asids와 reserved_ttbr0를 본다.

x86-64

mechanism
CR3에 PGD PA, PCID와 no-flush bit를 조합
state
switch_mm_irqs_off()는 per-CPU loaded_mm, tlb_gen과 ASID slot을 비교한다. 같은 mm라도 generation이 뒤처지면 INVPCID 또는 CR3 reload가 필요하다.
checkpoint
CR3, PCID, tlb_gen, loaded_mm, user/kernel PCID와 IBPB decision을 기록한다.

RISC-V

mechanism
SATP.MODE, ASID와 root PPN을 조합
state
switch_mm()는 cpu mask, icache stale mask와 ASID version을 확인한 뒤 SATP를 쓴다. ASID extension이 없으면 주소 공간 전환마다 더 넓은 sfence.vma가 필요하다.
checkpoint
SATP MODE/ASID/PPN, context id version, cpumask와 icache_stale_mask를 확인한다.
그림 2. 공통 kernel과 architecture hook의 소유권공통 정책이 hardware state를 직접 소유하지 않는 경계를 표시합니다.
Linux common contract각 mm에 hardware address-space tag와 generation을 배정한다. tag가 현재 generation에서 유효하면 root를 tag와 함께 설치해 다른 mm translation과 공존시키고, wrap이면 전체 invalidate 뒤 generation을 올린다.
arm64TTBR0_EL1에 ASID와 PGD PA를 조합ASID allocation lock과 local flush 뒤 TTBR write가 이어지며 contextidr와 speculation state도 task 경계와 맞춘다.
x86-64CR3에 PGD PA, PCID와 no-flush bit를 조합mm_cpumask와 loaded_mm publication은 concurrent flush_tlb_mm_range()가 switch 중 CPU를 놓치지 않게 ordering을 이룬다.
RISC-VSATP.MODE, ASID와 root PPN을 조합SATP write와 local_flush_tlb_all()/ASID flush, instruction-cache synchronization이 membarrier 계약과 연결된다.
lifetime boundaryASID/PCID 숫자는 영구 identity가 아니다. generation과 한 쌍일 때만 mm를 식별하며 wrap 뒤 old TLB entry와 재사용 tag가 충돌하지 않게 해야 한다.
그림 3. publication과 관찰 순서state를 준비한 뒤 architecture ordering을 거쳐 관찰 가능한 checkpoint가 됩니다.
arm64state 준비ASID allocation lock과 local flush 뒤 TTBR write가 이어지며 contextidr와 speculation state도 task 경계와 맞춘다.관찰: asid generation, TTBR0_EL1, CONTEXTIDR_EL1, active_asids와 reserved_ttbr0를 본다.
x86-64state 준비mm_cpumask와 loaded_mm publication은 concurrent flush_tlb_mm_range()가 switch 중 CPU를 놓치지 않게 ordering을 이룬다.관찰: CR3, PCID, tlb_gen, loaded_mm, user/kernel PCID와 IBPB decision을 기록한다.
RISC-Vstate 준비SATP write와 local_flush_tlb_all()/ASID flush, instruction-cache synchronization이 membarrier 계약과 연결된다.관찰: SATP MODE/ASID/PPN, context id version, cpumask와 icache_stale_mask를 확인한다.

04 · SOURCE

Linux 6.18.37 원본 코드와 줄별 설명

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

arm64 · Linux 6.18.37

TTBR0_EL1에 ASID와 PGD PA를 조합

check_and_switch_context()는 mm context id generation을 검사하고 필요하면 새 ASID를 배정한다. CnP, PAN, EPD0와 reserved TTBR0 사용 여부가 실제 register write를 바꾼다.

원본 코드: arch/arm64/mm/context.c:207-279

207	asid = find_next_zero_bit(asid_map, NUM_USER_ASIDS, 1);
208 
209set_asid:
210	__set_bit(asid, asid_map);
211	cur_idx = asid;
212	return asid2ctxid(asid, generation);
213}
214 
215void check_and_switch_context(struct mm_struct *mm)
216{
217	unsigned long flags;
218	unsigned int cpu;
219	u64 asid, old_active_asid;
220 
221	if (system_supports_cnp())
222		cpu_set_reserved_ttbr0();
223 
224	asid = atomic64_read(&mm->context.id);
225 
226	/*
227	 * The memory ordering here is subtle.
228	 * If our active_asids is non-zero and the ASID matches the current
229	 * generation, then we update the active_asids entry with a relaxed
230	 * cmpxchg. Racing with a concurrent rollover means that either:
231	 *
232	 * - We get a zero back from the cmpxchg and end up waiting on the
233	 *   lock. Taking the lock synchronises with the rollover and so
234	 *   we are forced to see the updated generation.
235	 *
236	 * - We get a valid ASID back from the cmpxchg, which means the
237	 *   relaxed xchg in flush_context will treat us as reserved
238	 *   because atomic RmWs are totally ordered for a given location.
239	 */
240	old_active_asid = atomic64_read(this_cpu_ptr(&active_asids));
241	if (old_active_asid && asid_gen_match(asid) &&
242	    atomic64_cmpxchg_relaxed(this_cpu_ptr(&active_asids),
243				     old_active_asid, asid))
244		goto switch_mm_fastpath;
245 
246	raw_spin_lock_irqsave(&cpu_asid_lock, flags);
247	/* Check that our ASID belongs to the current generation. */
248	asid = atomic64_read(&mm->context.id);
249	if (!asid_gen_match(asid)) {
250		asid = new_context(mm);
251		atomic64_set(&mm->context.id, asid);
252	}
253 
254	cpu = smp_processor_id();
255	if (cpumask_test_and_clear_cpu(cpu, &tlb_flush_pending))
256		local_flush_tlb_all();
257 
258	atomic64_set(this_cpu_ptr(&active_asids), asid);
259	raw_spin_unlock_irqrestore(&cpu_asid_lock, flags);
260 
261switch_mm_fastpath:
262 
263	arm64_apply_bp_hardening();
264 
265	/*
266	 * Defer TTBR0_EL1 setting for user threads to uaccess_enable() when
267	 * emulating PAN.
268	 */
269	if (!system_uses_ttbr0_pan())
270		cpu_switch_mm(mm->pgd, mm);
271}
272 
273unsigned long arm64_mm_context_get(struct mm_struct *mm)
274{
275	unsigned long flags;
276	u64 asid;
277 
278	if (!pinned_asid_map)
279		return 0;

라인 바이 라인 주석

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

L207 asid = find_next_zero_bit(asid_map, NUM_USER_ASIDS, 1);

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

L208(blank)

빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L209set_asid:

분기 label이다. 이 위치로 들어오는 모든 선행 경로가 같은 register, stack, lock과 interrupt 상태를 만족하는지 비교한다.

L210 __set_bit(asid, asid_map);

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

L211 cur_idx = asid;

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

L212 return asid2ctxid(asid, generation);

이 함수가 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L213}

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

L214(blank)

빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L215void check_and_switch_context(struct mm_struct *mm)

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

L216{

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

L217 unsigned long flags;

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

L218 unsigned int cpu;

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

L219 u64 asid, old_active_asid;

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Address-space switch: TTBR, CR3/PCID와 SATP/ASID의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L220(blank)

빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L221 if (system_supports_cnp())

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

L222 cpu_set_reserved_ttbr0();

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

L223(blank)

빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L224 asid = atomic64_read(&mm->context.id);

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

L225(blank)

빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L226 /*

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

L227 * The memory ordering here is subtle.

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

L228 * If our active_asids is non-zero and the ASID matches the current

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

L229 * generation, then we update the active_asids entry with a relaxed

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

L230 * cmpxchg. Racing with a concurrent rollover means that either:

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

L231 *

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

L232 * - We get a zero back from the cmpxchg and end up waiting on the

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

L233 * lock. Taking the lock synchronises with the rollover and so

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

L234 * we are forced to see the updated generation.

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

L235 *

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

L236 * - We get a valid ASID back from the cmpxchg, which means the

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

L237 * relaxed xchg in flush_context will treat us as reserved

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

L238 * because atomic RmWs are totally ordered for a given location.

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

L239 */

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

L240 old_active_asid = atomic64_read(this_cpu_ptr(&active_asids));

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

L241 if (old_active_asid && asid_gen_match(asid) &&

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

L242 atomic64_cmpxchg_relaxed(this_cpu_ptr(&active_asids),

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Address-space switch: TTBR, CR3/PCID와 SATP/ASID의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L243 old_active_asid, asid))

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Address-space switch: TTBR, CR3/PCID와 SATP/ASID의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L244 goto switch_mm_fastpath;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L245(blank)

빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L246 raw_spin_lock_irqsave(&cpu_asid_lock, flags);

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

L247 /* Check that our ASID belongs to the current generation. */

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

L248 asid = atomic64_read(&mm->context.id);

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

L249 if (!asid_gen_match(asid)) {

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

L250 asid = new_context(mm);

현재 generation에서 쓸 새 ASID를 할당하는 slow path다.

L251 atomic64_set(&mm->context.id, asid);

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

L252 }

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

L253(blank)

빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L254 cpu = smp_processor_id();

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

L255 if (cpumask_test_and_clear_cpu(cpu, &tlb_flush_pending))

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

L256 local_flush_tlb_all();

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

L257(blank)

빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L258 atomic64_set(this_cpu_ptr(&active_asids), asid);

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

L259 raw_spin_unlock_irqrestore(&cpu_asid_lock, flags);

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

L260(blank)

빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L261switch_mm_fastpath:

분기 label이다. 이 위치로 들어오는 모든 선행 경로가 같은 register, stack, lock과 interrupt 상태를 만족하는지 비교한다.

L262(blank)

빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L263 arm64_apply_bp_hardening();

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

L264(blank)

빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L265 /*

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

L266 * Defer TTBR0_EL1 setting for user threads to uaccess_enable() when

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

L267 * emulating PAN.

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

L268 */

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

L269 if (!system_uses_ttbr0_pan())

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

L270 cpu_switch_mm(mm->pgd, mm);

PGD physical address와 mm context를 실제 TTBR state로 설치한다.

L271}

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

L272(blank)

빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L273unsigned long arm64_mm_context_get(struct mm_struct *mm)

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

L274{

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

L275 unsigned long flags;

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

L276 u64 asid;

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

L277(blank)

빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L278 if (!pinned_asid_map)

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

L279 return 0;

이 함수가 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

x86-64 · Linux 6.18.37

CR3에 PGD PA, PCID와 no-flush bit를 조합

switch_mm_irqs_off()는 per-CPU loaded_mm, tlb_gen과 ASID slot을 비교한다. 같은 mm라도 generation이 뒤처지면 INVPCID 또는 CR3 reload가 필요하다.

원본 코드: arch/x86/mm/tlb.c:774-864

774#endif
775 
776/*
777 * This optimizes when not actually switching mm's.  Some architectures use the
778 * 'unused' argument for this optimization, but x86 must use
779 * 'cpu_tlbstate.loaded_mm' instead because it does not always keep
780 * 'current->active_mm' up to date.
781 */
782void switch_mm_irqs_off(struct mm_struct *unused, struct mm_struct *next,
783			struct task_struct *tsk)
784{
785	struct mm_struct *prev = this_cpu_read(cpu_tlbstate.loaded_mm);
786	u16 prev_asid = this_cpu_read(cpu_tlbstate.loaded_mm_asid);
787	bool was_lazy = this_cpu_read(cpu_tlbstate_shared.is_lazy);
788	unsigned cpu = smp_processor_id();
789	unsigned long new_lam;
790	struct new_asid ns;
791	u64 next_tlb_gen;
792 
793 
794	/* We don't want flush_tlb_func() to run concurrently with us. */
795	if (IS_ENABLED(CONFIG_PROVE_LOCKING))
796		WARN_ON_ONCE(!irqs_disabled());
797 
798	/*
799	 * Verify that CR3 is what we think it is.  This will catch
800	 * hypothetical buggy code that directly switches to swapper_pg_dir
801	 * without going through leave_mm() / switch_mm_irqs_off() or that
802	 * does something like write_cr3(read_cr3_pa()).
803	 *
804	 * Only do this check if CONFIG_DEBUG_VM=y because __read_cr3()
805	 * isn't free.
806	 */
807#ifdef CONFIG_DEBUG_VM
808	if (WARN_ON_ONCE(__read_cr3() != build_cr3(prev->pgd, prev_asid,
809						   tlbstate_lam_cr3_mask()))) {
810		/*
811		 * If we were to BUG here, we'd be very likely to kill
812		 * the system so hard that we don't see the call trace.
813		 * Try to recover instead by ignoring the error and doing
814		 * a global flush to minimize the chance of corruption.
815		 *
816		 * (This is far from being a fully correct recovery.
817		 *  Architecturally, the CPU could prefetch something
818		 *  back into an incorrect ASID slot and leave it there
819		 *  to cause trouble down the road.  It's better than
820		 *  nothing, though.)
821		 */
822		__flush_tlb_all();
823	}
824#endif
825	if (was_lazy)
826		this_cpu_write(cpu_tlbstate_shared.is_lazy, false);
827 
828	/*
829	 * The membarrier system call requires a full memory barrier and
830	 * core serialization before returning to user-space, after
831	 * storing to rq->curr, when changing mm.  This is because
832	 * membarrier() sends IPIs to all CPUs that are in the target mm
833	 * to make them issue memory barriers.  However, if another CPU
834	 * switches to/from the target mm concurrently with
835	 * membarrier(), it can cause that CPU not to receive an IPI
836	 * when it really should issue a memory barrier.  Writing to CR3
837	 * provides that full memory barrier and core serializing
838	 * instruction.
839	 */
840	if (prev == next) {
841		/* Not actually switching mm's */
842		VM_WARN_ON(is_dyn_asid(prev_asid) &&
843			   this_cpu_read(cpu_tlbstate.ctxs[prev_asid].ctx_id) !=
844			   next->context.ctx_id);
845 
846		/*
847		 * If this races with another thread that enables lam, 'new_lam'
848		 * might not match tlbstate_lam_cr3_mask().
849		 */
850 
851		/*
852		 * Even in lazy TLB mode, the CPU should stay set in the
853		 * mm_cpumask. The TLB shootdown code can figure out from
854		 * cpu_tlbstate_shared.is_lazy whether or not to send an IPI.
855		 */
856		if (IS_ENABLED(CONFIG_DEBUG_VM) &&
857		    WARN_ON_ONCE(prev != &init_mm && !is_notrack_mm(prev) &&
858				 !cpumask_test_cpu(cpu, mm_cpumask(next))))
859			cpumask_set_cpu(cpu, mm_cpumask(next));
860 
861		/* Check if the current mm is transitioning to a global ASID */
862		if (mm_needs_global_asid(next, prev_asid)) {
863			next_tlb_gen = atomic64_read(&next->context.tlb_gen);
864			ns = choose_new_asid(next, next_tlb_gen);

라인 바이 라인 주석

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

L774#endif

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

L775(blank)

빈 줄은 x86-64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L776/*

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

L777 * This optimizes when not actually switching mm's. Some architectures use the

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

L778 * 'unused' argument for this optimization, but x86 must use

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

L779 * 'cpu_tlbstate.loaded_mm' instead because it does not always keep

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

L780 * 'current->active_mm' up to date.

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

L781 */

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

L782void switch_mm_irqs_off(struct mm_struct *unused, struct mm_struct *next,

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Address-space switch: TTBR, CR3/PCID와 SATP/ASID의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L783 struct task_struct *tsk)

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Address-space switch: TTBR, CR3/PCID와 SATP/ASID의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L784{

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

L785 struct mm_struct *prev = this_cpu_read(cpu_tlbstate.loaded_mm);

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

L786 u16 prev_asid = this_cpu_read(cpu_tlbstate.loaded_mm_asid);

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

L787 bool was_lazy = this_cpu_read(cpu_tlbstate_shared.is_lazy);

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

L788 unsigned cpu = smp_processor_id();

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

L789 unsigned long new_lam;

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

L790 struct new_asid ns;

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

L791 u64 next_tlb_gen;

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

L792(blank)

빈 줄은 x86-64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L793(blank)

빈 줄은 x86-64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L794 /* We don't want flush_tlb_func() to run concurrently with us. */

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

L795 if (IS_ENABLED(CONFIG_PROVE_LOCKING))

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

L796 WARN_ON_ONCE(!irqs_disabled());

불가능해야 하는 상태 또는 복구 가능한 오류를 외부에 드러내는 줄이다. 직전 register/object 값을 함께 남겨 재현 가능한 failure signature를 만든다.

L797(blank)

빈 줄은 x86-64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L798 /*

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

L799 * Verify that CR3 is what we think it is. This will catch

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

L800 * hypothetical buggy code that directly switches to swapper_pg_dir

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

L801 * without going through leave_mm() / switch_mm_irqs_off() or that

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

L802 * does something like write_cr3(read_cr3_pa()).

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

L803 *

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

L804 * Only do this check if CONFIG_DEBUG_VM=y because __read_cr3()

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

L805 * isn't free.

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

L806 */

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

L807#ifdef CONFIG_DEBUG_VM

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

L808 if (WARN_ON_ONCE(__read_cr3() != build_cr3(prev->pgd, prev_asid,

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

L809 tlbstate_lam_cr3_mask()))) {

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

L810 /*

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

L811 * If we were to BUG here, we'd be very likely to kill

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

L812 * the system so hard that we don't see the call trace.

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

L813 * Try to recover instead by ignoring the error and doing

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

L814 * a global flush to minimize the chance of corruption.

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

L815 *

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

L816 * (This is far from being a fully correct recovery.

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

L817 * Architecturally, the CPU could prefetch something

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

L818 * back into an incorrect ASID slot and leave it there

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

L819 * to cause trouble down the road. It's better than

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

L820 * nothing, though.)

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

L821 */

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

L822 __flush_tlb_all();

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

L823 }

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

L824#endif

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

L825 if (was_lazy)

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

L826 this_cpu_write(cpu_tlbstate_shared.is_lazy, false);

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

L827(blank)

빈 줄은 x86-64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L828 /*

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

L829 * The membarrier system call requires a full memory barrier and

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

L830 * core serialization before returning to user-space, after

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

L831 * storing to rq->curr, when changing mm. This is because

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

L832 * membarrier() sends IPIs to all CPUs that are in the target mm

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

L833 * to make them issue memory barriers. However, if another CPU

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

L834 * switches to/from the target mm concurrently with

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

L835 * membarrier(), it can cause that CPU not to receive an IPI

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

L836 * when it really should issue a memory barrier. Writing to CR3

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

L837 * provides that full memory barrier and core serializing

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

L838 * instruction.

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

L839 */

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

L840 if (prev == next) {

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

L841 /* Not actually switching mm's */

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

L842 VM_WARN_ON(is_dyn_asid(prev_asid) &&

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Address-space switch: TTBR, CR3/PCID와 SATP/ASID의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L843 this_cpu_read(cpu_tlbstate.ctxs[prev_asid].ctx_id) !=

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Address-space switch: TTBR, CR3/PCID와 SATP/ASID의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L844 next->context.ctx_id);

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Address-space switch: TTBR, CR3/PCID와 SATP/ASID의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L845(blank)

빈 줄은 x86-64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L846 /*

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

L847 * If this races with another thread that enables lam, 'new_lam'

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

L848 * might not match tlbstate_lam_cr3_mask().

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

L849 */

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

L850(blank)

빈 줄은 x86-64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L851 /*

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

L852 * Even in lazy TLB mode, the CPU should stay set in the

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

L853 * mm_cpumask. The TLB shootdown code can figure out from

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

L854 * cpu_tlbstate_shared.is_lazy whether or not to send an IPI.

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

L855 */

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

L856 if (IS_ENABLED(CONFIG_DEBUG_VM) &&

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

L857 WARN_ON_ONCE(prev != &init_mm && !is_notrack_mm(prev) &&

불가능해야 하는 상태 또는 복구 가능한 오류를 외부에 드러내는 줄이다. 직전 register/object 값을 함께 남겨 재현 가능한 failure signature를 만든다.

L858 !cpumask_test_cpu(cpu, mm_cpumask(next))))

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

L859 cpumask_set_cpu(cpu, mm_cpumask(next));

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

L860(blank)

빈 줄은 x86-64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L861 /* Check if the current mm is transitioning to a global ASID */

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

L862 if (mm_needs_global_asid(next, prev_asid)) {

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

L863 next_tlb_gen = atomic64_read(&next->context.tlb_gen);

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

L864 ns = choose_new_asid(next, next_tlb_gen);

per-CPU ASID slot과 flush 필요 여부를 선택한다.

RISC-V · Linux 6.18.37

SATP.MODE, ASID와 root PPN을 조합

switch_mm()는 cpu mask, icache stale mask와 ASID version을 확인한 뒤 SATP를 쓴다. ASID extension이 없으면 주소 공간 전환마다 더 넓은 sfence.vma가 필요하다.

원본 코드: arch/riscv/mm/context.c:310-339

310		 * If cache will be flushed in switch_to, no need to flush here.
311		 */
312		if (!(task && switch_to_should_flush_icache(task)))
313			local_flush_icache_all();
314	}
315#endif
316}
317 
318void switch_mm(struct mm_struct *prev, struct mm_struct *next,
319	struct task_struct *task)
320{
321	unsigned int cpu;
322 
323	if (unlikely(prev == next))
324		return;
325 
326	membarrier_arch_switch_mm(prev, next, task);
327 
328	/*
329	 * Mark the current MM context as inactive, and the next as
330	 * active.  This is at least used by the icache flushing
331	 * routines in order to determine who should be flushed.
332	 */
333	cpu = smp_processor_id();
334 
335	set_mm(prev, next, cpu);
336 
337	flush_icache_deferred(next, cpu, task);
338}
339 

라인 바이 라인 주석

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

L310 * If cache will be flushed in switch_to, no need to flush here.

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

L311 */

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

L312 if (!(task && switch_to_should_flush_icache(task)))

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

L313 local_flush_icache_all();

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

L314 }

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

L315#endif

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

L316}

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

L317(blank)

빈 줄은 RISC-V Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L318void switch_mm(struct mm_struct *prev, struct mm_struct *next,

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Address-space switch: TTBR, CR3/PCID와 SATP/ASID의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L319 struct task_struct *task)

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Address-space switch: TTBR, CR3/PCID와 SATP/ASID의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L320{

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

L321 unsigned int cpu;

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

L322(blank)

빈 줄은 RISC-V Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L323 if (unlikely(prev == next))

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

L324 return;

이 함수가 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L325(blank)

빈 줄은 RISC-V Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L326 membarrier_arch_switch_mm(prev, next, task);

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

L327(blank)

빈 줄은 RISC-V Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L328 /*

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

L329 * Mark the current MM context as inactive, and the next as

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

L330 * active. This is at least used by the icache flushing

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

L331 * routines in order to determine who should be flushed.

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

L332 */

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

L333 cpu = smp_processor_id();

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

L334(blank)

빈 줄은 RISC-V Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L335 set_mm(prev, next, cpu);

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

L336(blank)

빈 줄은 RISC-V Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L337 flush_icache_deferred(next, cpu, task);

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

L338}

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

L339(blank)

빈 줄은 RISC-V Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

05 · WORKED EXAMPLE

숫자로 검산하기

01

16-bit tag 재사용과 generation wrap

hardware tag가 16bit라 65536개이고 현재 generation=7, next mm context=(generation 6, ASID 42)라고 가정한다.

  1. stale 판단mm generation 6은 현재 7과 다르므로 ASID 42를 그대로 사용할 수 없다.
  2. allocategeneration 7의 free tag 103을 배정하고 context를 (7,103)으로 갱신한다.
  3. root valuePGD PA/root PPN과 tag 103을 TTBR/CR3/SATP 형식에 맞게 조합한다.
  4. wrapfree tag가 없고 generation 8로 넘어가면 old generation translation을 모든 관련 CPU에서 invalidate한 뒤 재사용한다.

결론ASID/PCID 숫자만 로그에 남기면 재사용 충돌을 판별할 수 없다. generation과 root PA를 함께 기록한다.

06 · DEEP DIVE

경계별 상세 분석

01

공통 kernel core와 architecture hook의 경계

각 mm에 hardware address-space tag와 generation을 배정한다. tag가 현재 generation에서 유효하면 root를 tag와 함께 설치해 다른 mm translation과 공존시키고, wrap이면 전체 invalidate 뒤 generation을 올린다.

mm cpumask publication, page-table root write와 speculation barrier 순서가 TLB shootdown의 대상 CPU 선택과 맞아야 한다.

02

arm64: TTBR0_EL1에 ASID와 PGD PA를 조합

check_and_switch_context()는 mm context id generation을 검사하고 필요하면 새 ASID를 배정한다. CnP, PAN, EPD0와 reserved TTBR0 사용 여부가 실제 register write를 바꾼다.

ASID allocation lock과 local flush 뒤 TTBR write가 이어지며 contextidr와 speculation state도 task 경계와 맞춘다. 디버깅할 때는 asid generation, TTBR0_EL1, CONTEXTIDR_EL1, active_asids와 reserved_ttbr0를 본다.

03

x86-64: CR3에 PGD PA, PCID와 no-flush bit를 조합

switch_mm_irqs_off()는 per-CPU loaded_mm, tlb_gen과 ASID slot을 비교한다. 같은 mm라도 generation이 뒤처지면 INVPCID 또는 CR3 reload가 필요하다.

mm_cpumask와 loaded_mm publication은 concurrent flush_tlb_mm_range()가 switch 중 CPU를 놓치지 않게 ordering을 이룬다. 디버깅할 때는 CR3, PCID, tlb_gen, loaded_mm, user/kernel PCID와 IBPB decision을 기록한다.

04

RISC-V: SATP.MODE, ASID와 root PPN을 조합

switch_mm()는 cpu mask, icache stale mask와 ASID version을 확인한 뒤 SATP를 쓴다. ASID extension이 없으면 주소 공간 전환마다 더 넓은 sfence.vma가 필요하다.

SATP write와 local_flush_tlb_all()/ASID flush, instruction-cache synchronization이 membarrier 계약과 연결된다. 디버깅할 때는 SATP MODE/ASID/PPN, context id version, cpumask와 icache_stale_mask를 확인한다.

05

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

ASID/PCID 숫자는 영구 identity가 아니다. generation과 한 쌍일 때만 mm를 식별하며 wrap 뒤 old TLB entry와 재사용 tag가 충돌하지 않게 해야 한다.

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

06

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

fast path는 valid tag와 같은 root 재사용이고 slow path는 allocation, wrap flush, IBPB/L1D mitigation과 lazy TLB 처리다.

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

07 · FAILURE

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

분류관찰되는 결과첫 확인값
arm64ASID wrap flush가 누락되면 새 mm가 이전 mm의 동일 ASID translation을 사용한다.asid generation, TTBR0_EL1, CONTEXTIDR_EL1, active_asids와 reserved_ttbr0를 본다.
x86-64no-flush CR3를 stale PCID에 쓰면 다른 mm translation이 살아남는다.CR3, PCID, tlb_gen, loaded_mm, user/kernel PCID와 IBPB decision을 기록한다.
RISC-VASID version wrap 또는 icache stale bit 누락은 data translation이나 새 code 관찰을 깨뜨린다.SATP MODE/ASID/PPN, context id version, cpumask와 icache_stale_mask를 확인한다.

08 · LAB

재현과 계측 절차

  1. 두 process가 같은 ASID/PCID 숫자를 서로 다른 generation에서 받는 순간을 trace한다.
  2. address-space switch와 동시 TLB shootdown stress로 mm_cpumask race를 재현한다.
  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 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.