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

v6.18.37 / arch/arm64/mm/context.c

같은 가상 주소 0x4000도 프로세스 A와 B에서는 다른 메모리를 가리킬 수 있습니다. 문맥 전환 때는 레지스터뿐 아니라 이 주소 해석 기준도 바꿉니다. arm64는 TTBR와 ASID, x86은 CR3와 PCID, RISC-V는 SATP와 ASID를 사용합니다. 여기서는 arm64의 실제 TTBR 교체 함수를 한 줄씩 읽습니다.

## cpu_do_switch_mm

```c

void cpu_do_switch_mm(phys_addr_t pgd_phys, struct mm_struct *mm)
{
	unsigned long ttbr1 = read_sysreg(ttbr1_el1);
	unsigned long asid = ASID(mm);
	unsigned long ttbr0 = phys_to_ttbr(pgd_phys);

	/* Skip CNP for the reserved ASID */
	if (system_supports_cnp() && asid)
		ttbr0 |= TTBR_CNP_BIT;

	/* SW PAN needs a copy of the ASID in TTBR0 for entry */
	if (IS_ENABLED(CONFIG_ARM64_SW_TTBR0_PAN))
		ttbr0 |= FIELD_PREP(TTBR_ASID_MASK, asid);

	/* Set ASID in TTBR1 since TCR.A1 is set */
	ttbr1 &= ~TTBR_ASID_MASK;
	ttbr1 |= FIELD_PREP(TTBR_ASID_MASK, asid);

	cpu_set_reserved_ttbr0_nosync();
	write_sysreg(ttbr1, ttbr1_el1);
	write_sysreg(ttbr0, ttbr0_el1);
	isb();
	post_ttbr_update_workaround();
}

```

### 349행

```c

void cpu_do_switch_mm(phys_addr_t pgd_phys, struct mm_struct *mm)

```

다음 mm의 최상위 표 물리 주소와 mm 구조체를 받습니다. ASID 할당 자체는 이 함수보다 앞선 경로에서 준비됩니다.

### 351행

```c

	unsigned long ttbr1 = read_sysreg(ttbr1_el1);

```

TTBR1의 현재 설정을 읽습니다. 커널 표 주소 등 ASID 이외의 필드는 보존하면서 ASID만 교체하려는 목적입니다.

### 352행

```c

	unsigned long asid = ASID(mm);

```

ASID 매크로로 이 mm에 배정된 주소 공간 식별자를 얻습니다. 가상 주소를 변환하는 표의 주소와는 다른 값입니다.

### 353행

```c

	unsigned long ttbr0 = phys_to_ttbr(pgd_phys);

```

물리 루트 주소를 TTBR이 요구하는 비트 형식으로 인코딩합니다. C 포인터를 그대로 레지스터에 쓰지 않습니다.

### 356행

```c

	if (system_supports_cnp() && asid)

```

시스템이 CnP를 지원하고 예약용 ASID 0이 아닌 경우만 다음 속성을 사용합니다.

### 357행

```c

		ttbr0 |= TTBR_CNP_BIT;

```

같은 변환 표를 공유하는 처리 요소 사이의 최적화에 쓰는 CnP 비트를 설정합니다.

### 360행

```c

	if (IS_ENABLED(CONFIG_ARM64_SW_TTBR0_PAN))

```

TTBR0 전환으로 PAN을 구현하는 코드를 빌드했는지 확인합니다. PAN은 커널이 사용자 주소를 우발적으로 읽거나 쓰지 못하게 하는 장치이며, 이 경로는 사용자 접근 함수가 필요할 때만 유효한 TTBR0를 잠시 사용하도록 구분합니다.

### 361행

```c

		ttbr0 |= FIELD_PREP(TTBR_ASID_MASK, asid);

```

진입 경로가 사용할 ASID 사본을 TTBR0에도 넣습니다. FIELD_PREP는 값의 비트를 TTBR_ASID_MASK 위치에 배치합니다.

### 364행

```c

	ttbr1 &= ~TTBR_ASID_MASK;

```

TTBR1에 남은 이전 ASID 비트를 지웁니다. 다른 TTBR1 필드는 유지합니다.

### 365행

```c

	ttbr1 |= FIELD_PREP(TTBR_ASID_MASK, asid);

```

TCR.A1이 선택하는 TTBR1의 ASID 필드에 다음 mm의 식별자를 넣습니다.

### 367행

```c

	cpu_set_reserved_ttbr0_nosync();

```

중간에 이전 TTBR0와 새 ASID가 부적절하게 결합되지 않도록 TTBR0를 예약된 상태로 먼저 전환합니다. nosync는 이 helper가 최종 동기화를 맡지 않는다는 뜻입니다.

### 368행

```c

	write_sysreg(ttbr1, ttbr1_el1);

```

새 ASID를 포함한 TTBR1 설정을 실제 시스템 레지스터에 씁니다.

### 369행

```c

	write_sysreg(ttbr0, ttbr0_el1);

```

준비한 다음 사용자 페이지 표의 TTBR0를 설치합니다.

### 370행

```c

	isb();

```

ISB로 이후 명령 실행이 갱신된 주소 변환 설정과 맞도록 합니다.

### 371행

```c

	post_ttbr_update_workaround();

```

TTBR 갱신 후 필요한 CPU별 보완 처리를 수행합니다. 지원 CPU에 따른 내부 조건은 해당 helper에서 정합니다.

