# 새 태스크의 주소 공간을 공유하거나 복제합니다

v6.18.37 / kernel/fork.c

태스크를 새로 만든다고 모든 자원을 항상 복사하는 것은 아닙니다. copy_process 내부에서 쓰이는 copy_mm을 통해 주소 공간 공유 여부를 살펴봅니다. CLONE_VM이면 같은 mm을 함께 사용하고, 그렇지 않으면 새 mm을 준비합니다. 새 mm을 만드는 것과 모든 물리 페이지를 즉시 복사하는 것도 구분해야 합니다.

## copy_mm

```c

static int copy_mm(u64 clone_flags, struct task_struct *tsk)
{
	struct mm_struct *mm, *oldmm;

	tsk->min_flt = tsk->maj_flt = 0;
	tsk->nvcsw = tsk->nivcsw = 0;
#ifdef CONFIG_DETECT_HUNG_TASK
	tsk->last_switch_count = tsk->nvcsw + tsk->nivcsw;
	tsk->last_switch_time = 0;
#endif

	tsk->mm = NULL;
	tsk->active_mm = NULL;

	/*
	 * Are we cloning a kernel thread?
	 *
	 * We need to steal a active VM for that..
	 */
	oldmm = current->mm;
	if (!oldmm)
		return 0;

	if (clone_flags & CLONE_VM) {
		mmget(oldmm);
		mm = oldmm;
	} else {
		mm = dup_mm(tsk, current->mm);
		if (!mm)
			return -ENOMEM;
	}

	tsk->mm = mm;
	tsk->active_mm = mm;
	sched_mm_cid_fork(tsk);
	return 0;
}

```

### 1515행

```c

static int copy_mm(u64 clone_flags, struct task_struct *tsk)

```

새 태스크 tsk가 쓸 사용자 주소 공간을 준비합니다. clone_flags의 CLONE_VM 여부로 기존 mm 공유와 별도 mm 복제를 나누며, int 결과로 성공 또는 오류를 보고합니다.

### 1517행

```c

	struct mm_struct *mm, *oldmm;

```

oldmm에는 부모 current의 사용자 주소 공간을, mm에는 자식에 연결할 결과를 담습니다. 공유 경로에서는 두 포인터가 같고 복제 경로에서는 새 mm이 됩니다. 포인터가 보관하는 것은 객체의 주소입니다. 이 선언만으로 대상 구조체나 문자열의 내용이 복사되지는 않습니다.

### 1519행

```c

	tsk->min_flt = tsk->maj_flt = 0;

```

자식의 경미한·중대한 페이지 폴트 통계를 0에서 시작합니다. 부모 통계를 그대로 물려주지 않습니다.

### 1520행

```c

	tsk->nvcsw = tsk->nivcsw = 0;

```

자식의 자발적·비자발적 문맥 전환 횟수를 초기화합니다.

### 1521행

```c

#ifdef CONFIG_DETECT_HUNG_TASK

```

인터럽트로 깨울 수 없는 D 상태에 태스크가 오래 머무는 문제를 진단하는 기능입니다. 켜진 경우 새 태스크의 문맥 전환 횟수·시각 기준을 초기화하여 이후 진행 여부를 비교합니다. 이 줄 자체가 태스크를 강제로 깨우지는 않습니다. 이 선택은 전처리 단계에서 이루어지며, CPU가 실행 중 이 줄의 조건을 검사하지 않습니다.

### 1522행

```c

	tsk->last_switch_count = tsk->nvcsw + tsk->nivcsw;

```

hung task 검사에서 진행 여부를 비교할 시작 문맥 전환 횟수를 기록합니다.

### 1523행

```c

	tsk->last_switch_time = 0;

```

아직 자식의 마지막 문맥 전환 시각 기록이 없음을 초기 값으로 표시합니다.

### 1524행

```c

#endif

```

새 태스크의 hung-task 진행 기준을 초기화하는 선택 기능 부분을 마칩니다. 이후 mm과 active_mm 초기화는 이 기능 유무와 관계없이 진행합니다.

### 1526행

```c

	tsk->mm = NULL;

```

사용자 mm 연결을 먼저 비워 초기화 실패나 mm 없는 태스크 경로에서 잘못된 포인터가 남지 않게 합니다.

### 1527행

```c

	tsk->active_mm = NULL;

```

CPU 주소 변환에 사용할 mm 관계도 아직 준비되지 않은 상태로 만듭니다.

### 1534행

```c

	oldmm = current->mm;

```

현재 태스크의 사용자 주소 공간 포인터를 읽습니다. 이 줄은 주소 공간을 복제하지 않습니다.

### 1535행

```c

	if (!oldmm)

```

복제할 사용자 mm이 없는 태스크인지 검사합니다. 이 경우 다음 줄에서 정상 종료합니다.

### 1536행

```c

		return 0;

```

이 함수가 담당하는 준비를 성공으로 보고합니다. 여기서 0은 NULL 포인터가 아니라 정수 반환 상태입니다.

### 1538행

```c

	if (clone_flags & CLONE_VM) {

```

호출자가 사용자 주소 공간 공유를 요청했는지 플래그 비트를 검사합니다.

### 1539행

```c

		mmget(oldmm);

```

같은 사용자 주소 공간을 공유할 참조를 하나 늘립니다. mm 객체의 전체 내용을 복사하지 않습니다.

### 1540행

```c

		mm = oldmm;

```

참조를 늘린 기존 mm 포인터를 자식도 사용하도록 선택합니다.

### 1541행

```c

	} else {

```

앞 조건이 성립하지 않은 경로를 선택합니다. 앞쪽 처리와 아래 처리를 모두 수행하는 것이 아닙니다.

### 1542행

```c

		mm = dup_mm(tsk, current->mm);

```

별도 주소 공간 관리 객체와 관련 매핑을 복제합니다. 모든 물리 페이지를 즉시 복사한다는 뜻은 아닙니다.

### 1543행

```c

		if (!mm)

```

필요한 mm이 없는지 검사합니다. 이 함수에서 이 위치가 초기 진입인지 할당 직후인지에 따라 조기 종료 또는 실패 처리가 이어집니다.

### 1544행

```c

			return -ENOMEM;

```

필요한 메모리 구조를 만들지 못해 메모리 부족 오류를 호출자에게 반환합니다. 미완성 객체를 정상 결과로 연결하지 않습니다.

### 1547행

```c

	tsk->mm = mm;

```

성공적으로 확보한 사용자 주소 공간을 자식 태스크에 연결합니다.

### 1548행

```c

	tsk->active_mm = mm;

```

자식의 초기 실행에 사용할 주소 변환 문맥도 같은 mm으로 설정합니다.

### 1549행

```c

	sched_mm_cid_fork(tsk);

```

스케줄러의 mm 관련 문맥 식별 상태를 새 태스크에 맞춰 초기화합니다.

### 1550행

```c

	return 0;

```

이 함수가 담당하는 준비를 성공으로 보고합니다. 여기서 0은 NULL 포인터가 아니라 정수 반환 상태입니다.

