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

종료 태스크에서 주소 공간 연결을 해제합니다

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

태스크가 끝날 때 사용자 주소 공간과의 관계도 정리해야 합니다. 연결을 NULL로 만드는 것만으로 모든 참조가 사라지는 것은 아닙니다. 다른 태스크의 공유, TLB와 메모리 장벽, mm의 최종 수명을 고려하며 단계를 나누어 처리합니다.

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

먼저 알아둘 개념

mm과 active_mm

사용자 주소 공간 소유 관계와 현재 CPU의 주소 변환 문맥 관계를 구분하기 위해 별도 필드가 있습니다.

mmput

사용자 mm 참조를 내려놓습니다. 마지막 사용자인지에 따라 실제 정리 범위가 달라집니다.

처음 읽을 때

코드를 읽을 때 대상 mm을 찾고 필요한 참조와 잠금을 확보합니다. 그다음 어느 상태가 바뀌는지 각 줄에서 확인하세요.

더 깊이 살펴볼 때

exit_mm 주석의 membarrier 규칙을 따라가 보세요. 이전 사용자 메모리 접근, current->mm 제거와 CPU의 membarrier 상태 변경 순서가 핵심입니다. mm 사용자 참조와 lazy TLB 수명 참조도 별도로 추적하셔야 합니다.

그림으로 보는 변화

종료 태스크에서 주소 공간 연결을 해제합니다의 단계별 개념 그림
각 단계에 화살표 의미와 생략 범위를 표시했습니다. 주소·숫자 예제는 실제 장치 값을 뜻하지 않습니다.
1단계 설명

1단계 고정

GIF 원본 열기

1. 종료 통지와 보호

대상 mm을 찾고 필요한 참조와 잠금을 확보합니다.

화살표는 정리 순서입니다.

2. 태스크 연결 해제

장벽과 IRQ 제어 아래 current->mm을 NULL로 바꿉니다.

단순 포인터 변경에도 관찰 순서가 중요합니다.

3. 참조 해제

잠금을 풀고 소유자 정보와 mm 참조를 정리합니다.

태스크 종료가 곧바로 모든 공유 페이지 해제를 뜻하지 않습니다.

exit_mm를 한 줄씩 읽기

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

static void exit_mm(void)
{
	struct mm_struct *mm = current->mm;

	exit_mm_release(current, mm);
	if (!mm)
		return;
	mmap_read_lock(mm);
	mmgrab_lazy_tlb(mm);
	BUG_ON(mm != current->active_mm);
	/* more a memory barrier than a real lock */
	task_lock(current);
	/*
	 * When a thread stops operating on an address space, the loop
	 * in membarrier_private_expedited() may not observe that
	 * tsk->mm, and the loop in membarrier_global_expedited() may
	 * not observe a MEMBARRIER_STATE_GLOBAL_EXPEDITED
	 * rq->membarrier_state, so those would not issue an IPI.
	 * Membarrier requires a memory barrier after accessing
	 * user-space memory, before clearing tsk->mm or the
	 * rq->membarrier_state.
	 */
	smp_mb__after_spinlock();
	local_irq_disable();
	current->user_dumpable = (get_dumpable(mm) == SUID_DUMP_USER);
	current->mm = NULL;
	membarrier_update_current_mm(NULL);
	enter_lazy_tlb(mm, current);
	local_irq_enable();
	task_unlock(current);
	mmap_read_unlock(mm);
	mm_update_next_owner(mm);
	mmput(mm);
	if (test_thread_flag(TIF_MEMDIE))
		exit_oom_victim();
}
static void exit_mm(void)

종료 중인 current가 가진 사용자 mm 연결과 참조를 정리합니다. 다른 태스크가 같은 mm을 공유할 수 있으므로 연결 해제와 실제 메모리 회수의 시점을 나누어 처리합니다.

	struct mm_struct *mm = current->mm;

종료 중인 현재 태스크가 사용하는 사용자 mm을 지역 포인터에 보관합니다. 뒤에서 current->mm을 끊은 후에도 정리를 이어 갈 대상입니다.

	exit_mm_release(current, mm);

주소 공간을 놓기 전에 태스크 종료와 관련된 통지·해제 처리를 수행합니다.

	if (!mm)

커널 스레드처럼 내려놓을 사용자 mm이 없는 경우인지 확인합니다. 이 함수의 할당 실패 검사가 아닙니다.

		return;

사용자 mm이 없으므로 주소 공간 잠금·분리·mmput을 진행하지 않고 반환합니다.

	mmap_read_lock(mm);

주소 공간 매핑에 대한 읽기 잠금을 잡아 필요한 상태를 안정적으로 읽습니다.

	mmgrab_lazy_tlb(mm);

이후 사용자 mm 연결을 끊어도 lazy TLB 문맥에서 mm을 유지할 수 있도록 수명 처리를 미리 합니다. 실제 mm_count 증가 여부는 lazy TLB 참조 계산 구성에 따라 달라집니다.

	BUG_ON(mm != current->active_mm);

사용하던 사용자 mm과 CPU 문맥의 active_mm이 같아야 한다는 조건을 검사합니다. 다르면 종료 경로가 전제한 상태가 깨진 것으로 처리합니다.

	task_lock(current);

현재 태스크의 mm 등 필드를 바꾸기 전에 태스크 잠금을 잡습니다. 뒤의 추가 장벽과 함께 다른 CPU의 관찰 순서를 맞춥니다.

	smp_mb__after_spinlock();

앞서 수행한 사용자 메모리 접근이 mm 연결과 membarrier 상태를 지우기 전에 관찰되도록 장벽을 보강합니다. 다른 CPU가 mm=NULL을 보고 IPI 대상을 생략해도 membarrier의 순서 보장이 깨지지 않게 합니다.

	local_irq_disable();

현재 CPU의 IRQ를 잠시 차단하여 mm 연결, membarrier 상태와 lazy TLB 전환을 도중에 인터럽트 처리와 엇갈리지 않게 갱신합니다.

	current->user_dumpable = (get_dumpable(mm) == SUID_DUMP_USER);

mm 연결을 끊기 전에 사용자 코어 덤프 가능 상태를 태스크 쪽에 남깁니다.

	current->mm = NULL;

종료 중인 태스크가 더 이상 사용자 mm을 소유하지 않도록 연결을 끊습니다.

	membarrier_update_current_mm(NULL);

현재 태스크가 사용자 mm을 내려놓았음을 해당 CPU의 membarrier 상태에 반영합니다. current->mm 포인터 변경과 함께 수행할 상태 전환입니다.

	enter_lazy_tlb(mm, current);

사용자 mm을 직접 사용하는 태스크에서 lazy TLB 상태로 넘어가도록 아키텍처 경로를 호출합니다. 이 줄을 페이지 테이블 전체 해제나 무조건적인 TTBR 교체라고 읽지 않습니다.

	local_irq_enable();

mm 관련 상태 전환을 마쳤으므로 현재 CPU의 IRQ를 다시 허용합니다. 태스크 잠금과 mmap 읽기 잠금은 뒤의 두 호출에서 해제합니다.

	task_unlock(current);

변경한 태스크 상태를 보호하던 잠금을 풉니다.

	mmap_read_unlock(mm);

주소 공간 매핑의 읽기 잠금을 해제합니다.

	mm_update_next_owner(mm);

공유 mm의 소유자로 기록할 다음 태스크를 갱신합니다.

	mmput(mm);

사용자 mm 참조를 하나 내려놓습니다. 마지막 사용자 여부에 따라 후속 회수가 달라집니다.

	if (test_thread_flag(TIF_MEMDIE))

현재 태스크가 OOM 희생자로 표시되어 있는지 검사합니다. 해당하는 경우에만 OOM 희생자 상태 정리를 수행합니다.

		exit_oom_victim();

OOM 희생 태스크에 부여된 상태를 종료 시 정리합니다.

함께 생각해 볼 질문

current->mm=NULL이면 메모리가 즉시 없어지나요?

아닙니다. 참조 수와 다른 사용자, 후속 정리 코드가 실제 수명을 결정합니다.

장벽은 잠금과 같은가요?

잠금은 동시 접근을 조정하고 장벽은 메모리 접근의 관찰 순서를 제한합니다.

커널 태스크처럼 mm이 없으면 어떻게 하나요?

필요한 초기 종료 처리를 한 뒤 이 경로에서 반환합니다.

출처와 읽은 범위

Linux stable v6.18.37 · kernel/exit.c

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

맨 위로 ↑