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 선택과 맞아야 한다.
02 · CONTRACT
공통 계약과 architecture 구현
| architecture | 핵심 mechanism | 실패 형태 | 확인할 상태 |
|---|---|---|---|
| arm64 | TTBR0_EL1에 ASID와 PGD PA를 조합 | ASID wrap flush가 누락되면 새 mm가 이전 mm의 동일 ASID translation을 사용한다. | asid generation, TTBR0_EL1, CONTEXTIDR_EL1, active_asids와 reserved_ttbr0를 본다. |
| x86-64 | CR3에 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-V | SATP.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
세 그림으로 먼저 읽기
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를 확인한다.
flush_tlb_mm_range()가 switch 중 CPU를 놓치지 않게 ordering을 이룬다.local_flush_tlb_all()/ASID flush, instruction-cache synchronization이 membarrier 계약과 연결된다.flush_tlb_mm_range()가 switch 중 CPU를 놓치지 않게 ordering을 이룬다.→관찰: CR3, PCID, tlb_gen, loaded_mm, user/kernel PCID와 IBPB decision을 기록한다.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개 줄에 각각 설명을 붙였습니다.
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를 연결해 본다.
(blank)빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
set_asid:분기 label이다. 이 위치로 들어오는 모든 선행 경로가 같은 register, stack, lock과 interrupt 상태를 만족하는지 비교한다.
__set_bit(asid, asid_map);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
cur_idx = asid;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
return asid2ctxid(asid, generation);이 함수가 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
void check_and_switch_context(struct mm_struct *mm)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
unsigned long flags;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
unsigned int cpu;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
u64 asid, old_active_asid;이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Address-space switch: TTBR, CR3/PCID와 SATP/ASID의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (system_supports_cnp())이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
cpu_set_reserved_ttbr0();helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
asid = atomic64_read(&mm->context.id);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* The memory ordering here is subtle.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* If our active_asids is non-zero and the ASID matches the currentLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* generation, then we update the active_asids entry with a relaxedLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* cmpxchg. Racing with a concurrent rollover means that either:Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* - We get a zero back from the cmpxchg and end up waiting on theLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* lock. Taking the lock synchronises with the rollover and soLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* we are forced to see the updated generation.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* - We get a valid ASID back from the cmpxchg, which means theLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* relaxed xchg in flush_context will treat us as reservedLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* because atomic RmWs are totally ordered for a given location.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
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를 연결해 본다.
if (old_active_asid && asid_gen_match(asid) &&이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
atomic64_cmpxchg_relaxed(this_cpu_ptr(&active_asids),이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Address-space switch: TTBR, CR3/PCID와 SATP/ASID의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
old_active_asid, asid))이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Address-space switch: TTBR, CR3/PCID와 SATP/ASID의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
goto switch_mm_fastpath;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
(blank)빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
raw_spin_lock_irqsave(&cpu_asid_lock, flags);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
/* Check that our ASID belongs to the current generation. */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
asid = atomic64_read(&mm->context.id);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
if (!asid_gen_match(asid)) {이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
asid = new_context(mm);현재 generation에서 쓸 새 ASID를 할당하는 slow path다.
atomic64_set(&mm->context.id, asid);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
cpu = smp_processor_id();helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
if (cpumask_test_and_clear_cpu(cpu, &tlb_flush_pending))이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
local_flush_tlb_all();helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
atomic64_set(this_cpu_ptr(&active_asids), asid);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
raw_spin_unlock_irqrestore(&cpu_asid_lock, flags);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
switch_mm_fastpath:분기 label이다. 이 위치로 들어오는 모든 선행 경로가 같은 register, stack, lock과 interrupt 상태를 만족하는지 비교한다.
(blank)빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
arm64_apply_bp_hardening();helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Defer TTBR0_EL1 setting for user threads to uaccess_enable() whenLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* emulating PAN.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (!system_uses_ttbr0_pan())이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
cpu_switch_mm(mm->pgd, mm);PGD physical address와 mm context를 실제 TTBR state로 설치한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
unsigned long arm64_mm_context_get(struct mm_struct *mm)이 함수의 진입 계약이 시작된다. arm64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
unsigned long flags;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
u64 asid;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
(blank)빈 줄은 arm64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (!pinned_asid_map)이 조건이 arm64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
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개 줄에 각각 설명을 붙였습니다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 x86-64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* This optimizes when not actually switching mm's. Some architectures use theLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* 'unused' argument for this optimization, but x86 must useLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* 'cpu_tlbstate.loaded_mm' instead because it does not always keepLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* 'current->active_mm' up to date.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
void 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를 분리해 해석한다.
struct task_struct *tsk)이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Address-space switch: TTBR, CR3/PCID와 SATP/ASID의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
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를 연결해 본다.
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를 연결해 본다.
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를 연결해 본다.
unsigned cpu = smp_processor_id();helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
unsigned long new_lam;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
struct new_asid ns;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
u64 next_tlb_gen;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
(blank)빈 줄은 x86-64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
(blank)빈 줄은 x86-64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* We don't want flush_tlb_func() to run concurrently with us. */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (IS_ENABLED(CONFIG_PROVE_LOCKING))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
WARN_ON_ONCE(!irqs_disabled());불가능해야 하는 상태 또는 복구 가능한 오류를 외부에 드러내는 줄이다. 직전 register/object 값을 함께 남겨 재현 가능한 failure signature를 만든다.
(blank)빈 줄은 x86-64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Verify that CR3 is what we think it is. This will catchLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* hypothetical buggy code that directly switches to swapper_pg_dirLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* without going through leave_mm() / switch_mm_irqs_off() or thatLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* does something like write_cr3(read_cr3_pa()).Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Only do this check if CONFIG_DEBUG_VM=y because __read_cr3()Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* isn't free.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
#ifdef CONFIG_DEBUG_VMKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
if (WARN_ON_ONCE(__read_cr3() != build_cr3(prev->pgd, prev_asid,이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
tlbstate_lam_cr3_mask()))) {이 함수의 진입 계약이 시작된다. x86-64에서 caller context, argument ownership과 반환 시 보장할 architecture state를 먼저 적는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* If we were to BUG here, we'd be very likely to killLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* the system so hard that we don't see the call trace.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Try to recover instead by ignoring the error and doingLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* a global flush to minimize the chance of corruption.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* (This is far from being a fully correct recovery.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Architecturally, the CPU could prefetch somethingLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* back into an incorrect ASID slot and leave it thereLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* to cause trouble down the road. It's better thanLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* nothing, though.)Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
__flush_tlb_all();helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
if (was_lazy)이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
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를 연결해 본다.
(blank)빈 줄은 x86-64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* The membarrier system call requires a full memory barrier andLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* core serialization before returning to user-space, afterLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* storing to rq->curr, when changing mm. This is becauseLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* membarrier() sends IPIs to all CPUs that are in the target mmLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* to make them issue memory barriers. However, if another CPULinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* switches to/from the target mm concurrently withLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* membarrier(), it can cause that CPU not to receive an IPILinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* when it really should issue a memory barrier. Writing to CR3Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* provides that full memory barrier and core serializingLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* instruction.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (prev == next) {이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
/* Not actually switching mm's */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
VM_WARN_ON(is_dyn_asid(prev_asid) &&이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Address-space switch: TTBR, CR3/PCID와 SATP/ASID의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
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를 분리해 해석한다.
next->context.ctx_id);이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Address-space switch: TTBR, CR3/PCID와 SATP/ASID의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 x86-64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* If this races with another thread that enables lam, 'new_lam'Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* might not match tlbstate_lam_cr3_mask().Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
(blank)빈 줄은 x86-64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Even in lazy TLB mode, the CPU should stay set in theLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* mm_cpumask. The TLB shootdown code can figure out fromLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* cpu_tlbstate_shared.is_lazy whether or not to send an IPI.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (IS_ENABLED(CONFIG_DEBUG_VM) &&이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
WARN_ON_ONCE(prev != &init_mm && !is_notrack_mm(prev) &&불가능해야 하는 상태 또는 복구 가능한 오류를 외부에 드러내는 줄이다. 직전 register/object 값을 함께 남겨 재현 가능한 failure signature를 만든다.
!cpumask_test_cpu(cpu, mm_cpumask(next))))helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
cpumask_set_cpu(cpu, mm_cpumask(next));helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Check if the current mm is transitioning to a global ASID */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (mm_needs_global_asid(next, prev_asid)) {이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
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를 연결해 본다.
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개 줄에 각각 설명을 붙였습니다.
* If cache will be flushed in switch_to, no need to flush here.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (!(task && switch_to_should_flush_icache(task)))이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
local_flush_icache_all();helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
void 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를 분리해 해석한다.
struct task_struct *task)이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Address-space switch: TTBR, CR3/PCID와 SATP/ASID의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
unsigned int cpu;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
(blank)빈 줄은 RISC-V Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (unlikely(prev == next))이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return;이 함수가 Address-space switch: TTBR, CR3/PCID와 SATP/ASID 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 RISC-V Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
membarrier_arch_switch_mm(prev, next, task);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 RISC-V Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/*Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* Mark the current MM context as inactive, and the next asLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* active. This is at least used by the icache flushingLinux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
* routines in order to determine who should be flushed.Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*/Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
cpu = smp_processor_id();helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 RISC-V Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
set_mm(prev, next, cpu);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 RISC-V Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
flush_icache_deferred(next, cpu, task);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V Address-space switch: TTBR, CR3/PCID와 SATP/ASID 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
05 · WORKED EXAMPLE
숫자로 검산하기
16-bit tag 재사용과 generation wrap
hardware tag가 16bit라 65536개이고 현재 generation=7, next mm context=(generation 6, ASID 42)라고 가정한다.
- stale 판단mm generation 6은 현재 7과 다르므로 ASID 42를 그대로 사용할 수 없다.
- allocategeneration 7의 free tag 103을 배정하고 context를 (7,103)으로 갱신한다.
- root valuePGD PA/root PPN과 tag 103을 TTBR/CR3/SATP 형식에 맞게 조합한다.
- wrapfree tag가 없고 generation 8로 넘어가면 old generation translation을 모든 관련 CPU에서 invalidate한 뒤 재사용한다.
결론ASID/PCID 숫자만 로그에 남기면 재사용 충돌을 판별할 수 없다. generation과 root PA를 함께 기록한다.
06 · DEEP DIVE
경계별 상세 분석
공통 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 선택과 맞아야 한다.
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를 본다.
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을 기록한다.
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를 확인한다.
객체 수명과 소유권을 먼저 고정한다
ASID/PCID 숫자는 영구 identity가 아니다. generation과 한 쌍일 때만 mm를 식별하며 wrap 뒤 old TLB entry와 재사용 tag가 충돌하지 않게 해야 한다.
주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.
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
실패를 어떤 증거로 나눌 것인가
| 분류 | 관찰되는 결과 | 첫 확인값 |
|---|---|---|
| arm64 | ASID wrap flush가 누락되면 새 mm가 이전 mm의 동일 ASID translation을 사용한다. | asid generation, TTBR0_EL1, CONTEXTIDR_EL1, active_asids와 reserved_ttbr0를 본다. |
| x86-64 | no-flush CR3를 stale PCID에 쓰면 다른 mm translation이 살아남는다. | CR3, PCID, tlb_gen, loaded_mm, user/kernel PCID와 IBPB decision을 기록한다. |
| RISC-V | ASID version wrap 또는 icache stale bit 누락은 data translation이나 새 code 관찰을 깨뜨린다. | SATP MODE/ASID/PPN, context id version, cpumask와 icache_stale_mask를 확인한다. |
08 · LAB
재현과 계측 절차
- 두 process가 같은 ASID/PCID 숫자를 서로 다른 generation에서 받는 순간을 trace한다.
- address-space switch와 동시 TLB shootdown stress로 mm_cpumask race를 재현한다.
- 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
- 소스만 읽고 끝내지 않고 최종
vmlinux의objdump -dr,readelf -SW결과로 선택된 alternative와 section 배치를 확인한다.
09 · REFERENCES
원문 좌표
- arm64arch/arm64/mm/context.c:207-279
- x86-64arch/x86/mm/tlb.c:774-864
- RISC-Varch/riscv/mm/context.c:310-339
Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.