이 코드는 어떤 문제를 푸나요?
태스크를 새로 만든다고 모든 자원을 항상 복사하는 것은 아닙니다. copy_process 내부에서 쓰이는 copy_mm을 통해 주소 공간 공유 여부를 살펴봅니다. CLONE_VM이면 같은 mm을 함께 사용하고, 그렇지 않으면 새 mm을 준비합니다. 새 mm을 만드는 것과 모든 물리 페이지를 즉시 복사하는 것도 구분해야 합니다.
읽을 범위: v6.6 · kernel/fork.c · copy_mm 1708–1744행입니다. 아래에 이 범위의 원문과 각 줄의 설명을 실었습니다. 주제 전체의 흐름과 다른 경로는 기존 분석에서 함께 읽으실 수 있습니다.
먼저 알아둘 개념
mm_struct
프로세스의 사용자 주소 공간을 관리하는 객체입니다. task_struct 자체와는 다른 객체입니다.
참조 횟수
여러 태스크가 같은 주소 공간을 사용하면 한 태스크가 끝나도 나머지가 사용할 수 있어야 하므로 생존 수명을 셉니다.
처음 읽을 때
코드를 읽을 때 통계와 mm 포인터를 초기 상태로 만듭니다. 그다음 어느 상태가 바뀌는지 각 줄에서 확인하세요.
더 깊이 살펴볼 때
이 경로의 호출 문맥, 잠금·인터럽트 상태, 오류 시 남는 자원을 함께 추적해 보세요. 호출된 함수가 수행하는 작업과 현재 함수가 직접 보장하는 범위를 구분하는 것이 중요합니다.
그림으로 보는 변화

1. 자식 상태 초기화
통계와 mm 포인터를 초기 상태로 만듭니다.
그림은 copy_process 전체 중 mm 처리만 보여 줍니다.
2. 공유 여부 판단
CLONE_VM이면 참조를 늘리고 아니면 dup_mm을 호출합니다.
분기 화살표는 선택 경로이며 두 경로가 동시에 실행되지는 않습니다.
3. 자식에 연결
준비한 mm을 자식 태스크에 연결합니다.
포인터 연결이며 이 줄에서 전체 메모리를 복사하지 않습니다.
copy_mm를 한 줄씩 읽기
줄 번호는 v6.6 원문 기준입니다. 주석·빈 줄을 포함한 함수 전체를 먼저 보고, 그 아래에서 각 줄을 설명합니다.
static int copy_mm(unsigned long 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;
}static int copy_mm(unsigned long clone_flags, struct task_struct *tsk)새 태스크 tsk가 쓸 사용자 주소 공간을 준비합니다. clone_flags의 CLONE_VM 여부로 기존 mm 공유와 별도 mm 복제를 나누며, int 결과로 성공 또는 오류를 보고합니다.
struct mm_struct *mm, *oldmm;oldmm에는 부모 current의 사용자 주소 공간을, mm에는 자식에 연결할 결과를 담습니다. 공유 경로에서는 두 포인터가 같고 복제 경로에서는 새 mm이 됩니다. 포인터가 보관하는 것은 객체의 주소입니다. 이 선언만으로 대상 구조체나 문자열의 내용이 복사되지는 않습니다.
tsk->min_flt = tsk->maj_flt = 0;자식의 경미한·중대한 페이지 폴트 통계를 0에서 시작합니다. 부모 통계를 그대로 물려주지 않습니다.
tsk->nvcsw = tsk->nivcsw = 0;자식의 자발적·비자발적 문맥 전환 횟수를 초기화합니다.
#ifdef CONFIG_DETECT_HUNG_TASK인터럽트로 깨울 수 없는 D 상태에 태스크가 오래 머무는 문제를 진단하는 기능입니다. 켜진 경우 새 태스크의 문맥 전환 횟수·시각 기준을 초기화하여 이후 진행 여부를 비교합니다. 이 줄 자체가 태스크를 강제로 깨우지는 않습니다. 이 선택은 전처리 단계에서 이루어지며, CPU가 실행 중 이 줄의 조건을 검사하지 않습니다.
tsk->last_switch_count = tsk->nvcsw + tsk->nivcsw;hung task 검사에서 진행 여부를 비교할 시작 문맥 전환 횟수를 기록합니다.
tsk->last_switch_time = 0;아직 자식의 마지막 문맥 전환 시각 기록이 없음을 초기 값으로 표시합니다.
#endif새 태스크의 hung-task 진행 기준을 초기화하는 선택 기능 부분을 마칩니다. 이후 mm과 active_mm 초기화는 이 기능 유무와 관계없이 진행합니다.
tsk->mm = NULL;사용자 mm 연결을 먼저 비워 초기화 실패나 mm 없는 태스크 경로에서 잘못된 포인터가 남지 않게 합니다.
tsk->active_mm = NULL;CPU 주소 변환에 사용할 mm 관계도 아직 준비되지 않은 상태로 만듭니다.
oldmm = current->mm;현재 태스크의 사용자 주소 공간 포인터를 읽습니다. 이 줄은 주소 공간을 복제하지 않습니다.
if (!oldmm)복제할 사용자 mm이 없는 태스크인지 검사합니다. 이 경우 다음 줄에서 정상 종료합니다.
return 0;이 함수가 담당하는 준비를 성공으로 보고합니다. 여기서 0은 NULL 포인터가 아니라 정수 반환 상태입니다.
if (clone_flags & CLONE_VM) {호출자가 사용자 주소 공간 공유를 요청했는지 플래그 비트를 검사합니다.
mmget(oldmm);같은 사용자 주소 공간을 공유할 참조를 하나 늘립니다. mm 객체의 전체 내용을 복사하지 않습니다.
mm = oldmm;참조를 늘린 기존 mm 포인터를 자식도 사용하도록 선택합니다.
} else {앞 조건이 성립하지 않은 경로를 선택합니다. 앞쪽 처리와 아래 처리를 모두 수행하는 것이 아닙니다.
mm = dup_mm(tsk, current->mm);별도 주소 공간 관리 객체와 관련 매핑을 복제합니다. 모든 물리 페이지를 즉시 복사한다는 뜻은 아닙니다.
if (!mm)필요한 mm이 없는지 검사합니다. 이 함수에서 이 위치가 초기 진입인지 할당 직후인지에 따라 조기 종료 또는 실패 처리가 이어집니다.
return -ENOMEM;필요한 메모리 구조를 만들지 못해 메모리 부족 오류를 호출자에게 반환합니다. 미완성 객체를 정상 결과로 연결하지 않습니다.
tsk->mm = mm;성공적으로 확보한 사용자 주소 공간을 자식 태스크에 연결합니다.
tsk->active_mm = mm;자식의 초기 실행에 사용할 주소 변환 문맥도 같은 mm으로 설정합니다.
sched_mm_cid_fork(tsk);스케줄러의 mm 관련 문맥 식별 상태를 새 태스크에 맞춰 초기화합니다.
return 0;이 함수가 담당하는 준비를 성공으로 보고합니다. 여기서 0은 NULL 포인터가 아니라 정수 반환 상태입니다.
함께 생각해 볼 질문
CLONE_VM이면 mm_struct도 새로 만드나요?
이 경로는 기존 mm의 참조를 늘려 공유합니다.
부모 current->mm이 NULL이면 오류인가요?
사용자 mm이 없는 커널 태스크 경로일 수 있습니다. 여기서는 복제할 사용자 mm 없이 성공을 반환합니다.
dup_mm은 모든 데이터를 즉시 복사하나요?
주소 공간 관리 구조와 페이지 테이블 복제, COW 처리 등을 구분해야 합니다. 모든 사용자 물리 페이지를 일괄 복사한다는 뜻은 아닙니다.
출처와 읽은 범위
Linux stable v6.6 · kernel/fork.c
해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고
