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

v6.6 / kernel/exit.c

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

## exit_mm

```c

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

	exit_mm_release(current, mm);
	if (!mm)
		return;
	sync_mm_rss(mm);
	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->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();
}

```

### 535행

```c

static void exit_mm(void)

```

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

### 537행

```c

	struct mm_struct *mm = current->mm;

```

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

### 539행

```c

	exit_mm_release(current, mm);

```

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

### 540행

```c

	if (!mm)

```

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

### 541행

```c

		return;

```

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

### 542행

```c

	sync_mm_rss(mm);

```

태스크 쪽에 누적된 RSS 메모리 통계를 mm에 반영한 뒤 주소 공간 연결을 정리합니다.

### 543행

```c

	mmap_read_lock(mm);

```

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

### 544행

```c

	mmgrab_lazy_tlb(mm);

```

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

### 545행

```c

	BUG_ON(mm != current->active_mm);

```

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

### 547행

```c

	task_lock(current);

```

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

### 558행

```c

	smp_mb__after_spinlock();

```

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

### 559행

```c

	local_irq_disable();

```

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

### 560행

```c

	current->mm = NULL;

```

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

### 561행

```c

	membarrier_update_current_mm(NULL);

```

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

### 562행

```c

	enter_lazy_tlb(mm, current);

```

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

### 563행

```c

	local_irq_enable();

```

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

### 564행

```c

	task_unlock(current);

```

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

### 565행

```c

	mmap_read_unlock(mm);

```

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

### 566행

```c

	mm_update_next_owner(mm);

```

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

### 567행

```c

	mmput(mm);

```

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

### 568행

```c

	if (test_thread_flag(TIF_MEMDIE))

```

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

### 569행

```c

		exit_oom_victim();

```

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

