Linux v6.18.37 · 개념과 코드 읽기

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

이 코드는 어떤 문제를 푸나요?

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

읽을 범위: v6.18.37 · arch/arm64/mm/context.c · cpu_do_switch_mm 349–372행입니다. 아래에 이 범위의 원문과 각 줄의 설명을 실었습니다. 주제 전체의 흐름과 다른 경로는 기존 분석에서 함께 읽으실 수 있습니다.

먼저 알아둘 개념

페이지 테이블 루트

주소 변환을 시작하는 표의 위치입니다. CPU가 C 포인터를 그대로 받는 것이 아니라 해당 레지스터의 형식으로 물리 주소를 인코딩합니다.

주소 공간 식별자

TLB에 어느 프로세스의 번역인지 구분할 표식을 붙입니다. 식별자를 재사용할 때에는 옛 번역이 남지 않도록 세대와 무효화를 관리합니다.

TCR.A1

이 arm64 경로는 ASID를 TTBR1에서 선택하도록 설정합니다. 따라서 사용자 페이지 테이블이 TTBR0에 있다는 사실만으로 ASID도 TTBR0에서 읽는다고 판단하면 안 됩니다.

처음 읽을 때

A와 B가 같은 주소를 읽는 그림을 그린 뒤, 루트 주소와 ASID가 바뀌는 순서를 따라가시면 됩니다.

더 깊이 살펴볼 때

ASID 재사용, reserved TTBR0를 거치는 이유, CnP와 소프트웨어 PAN의 조합을 함께 검토해 보십시오.

그림으로 보는 변화

프로세스 B의 ASID가 TTBR1에, 페이지 표 물리 루트가 TTBR0에 들어가는 관계와 VA 0x4000이 예시 PA 0x90004000으로 변환되는 그림. 실제 갱신 순서는 예약 TTBR0, TTBR1, 새 TTBR0, ISB입니다.
다음 mm의 ASID는 TCR.A1 설정에 따라 TTBR1에 반영되고, 사용자 표의 루트는 TTBR0에 반영됩니다. 아래 주소는 설명용입니다.
Address-space switch: TTBR, CR3/PCID와 SATP/ASID의 단계별 개념 그림
각 단계에 화살표 의미와 생략 범위를 표시했습니다. 주소·숫자 예제는 실제 장치 값을 뜻하지 않습니다.
1단계 설명

1단계 고정

GIF 원본 열기

1. 기존 주소 공간

현재 TTBR1 값을 읽고 다음 mm의 ASID와 루트 주소를 준비합니다.

다음 단계 화살표는 레지스터에 쓸 값을 계산하는 순서입니다.

2. 중간 상태 보호

TTBR0를 예약된 값으로 바꾼 뒤 TTBR1의 ASID를 갱신합니다.

옛 루트와 새 ASID의 부적절한 조합을 피하는 전환입니다.

3. 새 루트 사용

새 TTBR0를 쓰고 ISB 및 CPU별 보완 처리를 수행합니다.

이후 명령이 새 변환 설정을 사용하는 시점을 나타냅니다.

cpu_do_switch_mm를 한 줄씩 읽기

줄 번호는 v6.18.37 원문 기준입니다. 주석·빈 줄을 포함한 함수 전체를 먼저 보고, 그 아래에서 각 줄을 설명합니다.

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();
}
void cpu_do_switch_mm(phys_addr_t pgd_phys, struct mm_struct *mm)

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

	unsigned long ttbr1 = read_sysreg(ttbr1_el1);

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

	unsigned long asid = ASID(mm);

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

	unsigned long ttbr0 = phys_to_ttbr(pgd_phys);

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

	if (system_supports_cnp() && asid)

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

		ttbr0 |= TTBR_CNP_BIT;

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

	if (IS_ENABLED(CONFIG_ARM64_SW_TTBR0_PAN))

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

		ttbr0 |= FIELD_PREP(TTBR_ASID_MASK, asid);

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

	ttbr1 &= ~TTBR_ASID_MASK;

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

	ttbr1 |= FIELD_PREP(TTBR_ASID_MASK, asid);

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

	cpu_set_reserved_ttbr0_nosync();

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

	write_sysreg(ttbr1, ttbr1_el1);

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

	write_sysreg(ttbr0, ttbr0_el1);

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

	isb();

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

	post_ttbr_update_workaround();

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

함께 생각해 볼 질문

루트 주소만 바꾸면 TLB는 자동으로 비워집니까?

항상 그렇지 않습니다. ASID나 PCID로 번역을 구분할 수 있으며 재사용과 무효화 정책을 별도로 따라야 합니다.

커널 스레드도 항상 새 사용자 표를 가집니까?

아닙니다. active_mm을 빌리는 경우가 있으므로 모든 스케줄 전환을 사용자 mm 교체로 계산하면 안 됩니다.

왜 ASID를 TTBR1에도 씁니까?

이 커널 경로의 TCR.A1 설정이 ASID의 출처를 TTBR1으로 선택하기 때문입니다.

출처와 읽은 범위

Linux stable v6.18.37 · arch/arm64/mm/context.c

해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고

맨 위로 ↑