시스템 호출 예를 들어 fork, vfork, clone 혹은 커널 스레드(kernel_thread)를 실행할 때는 커널의 _do_fork 함수가 호출됩니다. 이 함수가 수행하는 주요 작업을 살펴보겠습니다(리눅스-4.19 버전 기준).
/* clone_flags: 프로세스 복제 중的一些 속성 제어를 위한 플래그 집합
CSIGNAL(최소 바이트)는 자식 프로세스가 종료될 때 부모 프로세스에게 보내는
시그널 마스크를 지정합니다.
stack_start: 사용자 모드 스택의 시작 주소
stack_size: 사용자 모드 스택의 크기
parent_tidptr, child_tidptr는 각각 부모/자식 프로세스의 PID를 가리키는
사용자 공간 포인터입니다;*/
long _do_fork(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr,
unsigned long tls)
특별히 fork 시스템 호출은 clone_flags 매개변수에 SIGCHLD(아rm 아키텍처에서 17로 정의됨)를 전달합니다. 이는 자식 프로세스가 종료될 때 SIGCHLD 시그널을 부모 프로세스에게 보내는 것을 의미합니다.
SYSCALL_DEFINE0(fork)
{
#ifdef CONFIG_MMU
return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
#else
/* nommu 모드에서는 지원하지 않습니다 */
return -EINVAL;
#endif
}
또한 fork는 쓰기 시 복제 기술을 사용하며, 최초에는 부모와 자식 프로세스가 같은 스택 주소를 공유합니다. 스택 주소에 데이터를 쓰는 작업이 일어날 때만 새로운 스택 복본이 创建됩니다.
_do_fork 함수의 작업 흐름
_do_fork()
|
|--->copy_process: 새로운 프로세스를 실제로 创建하는 작업
|
|--->get_task_pid & pid_vnr: PID 획득
|
|--->CLONE_VFORK 플래그가 설정된 경우, vfork 완료를 처리하기 위해 completion 初始化
|
|--->wake_up_new_task: 새로 创建된 자식 프로세스를 깨우기
|
|--->CLONE_VFORK 플래그가 설정된 경우, vfork 완료를 기다림
copy_process 함수
copy_process
|
|--->플래그 확인
|
|--->시그널 중지 및 fork 이후에만 시그널 수신 지연
|
|--->dup_task_struct: task_struct 및 thread_info 인스턴스를 복제
|
|--->리소스 제한 확인
|
|--->새로운 프로세스 task_struct 초기화
|
|--->sched_fork: 스케쥴러 관련 설정
|
|--->프로세스의 여러 부분 복제/공유
|
|--->copy_semundo, copy_files, copy_fs, copy_sighand, copy_signal,
copy_mm, copy_namespaces, copy_io, copy_thread_tls
플래그 확인
// 다른 네임스페이스와의 프로세스와의 공유를 금지합니다
if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
return ERR_PTR(-EINVAL);
// CLONE_THREAD로 스레드를 만들 때는 반드시 CLONE_SIGHAND를 활성화해야 합니다
if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND))
return ERR_PTR(-EINVAL);
// CLONE_SIGHAND가 설정된 경우는 CLONE_VM 플래그가 설정되어야 합니다
if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM))
return ERR_PTR(-EINVAL);
// 전역 init 프로세스의 형제는 종료 후 still zombie 상태로 남습니다
if ((clone_flags & CLONE_PARENT) &&
current->signal->flags & SIGNAL_UNKILLABLE)
return ERR_PTR(-EINVAL);
// PID나 유저 네임스페이스가 다른 프로세스와 함께 작업 중일 때는
// 스레드 그룹 공유를 방지합니다
if (clone_flags & CLONE_THREAD) {
if ((clone_flags & (CLONE_NEWUSER | CLONE_NEWPID)) ||
(task_active_pid_ns(current) !=
current->nsproxy->pid_ns_for_children))
return ERR_PTR(-EINVAL);
}
dup_task_struct
부모 프로세스의 복제본을 만들기 위해 arch_dup_task_struct와 setup_thread_stack를 사용합니다.
리소스 확인
if (atomic_read(&p->real_cred->user->processes) >=
task_rlimit(p, RLIMIT_NPROC)) {
if (p->real_cred->user != INIT_USER &&
!capable(CAP_SYS_RESOURCE) && !capable(CAP_SYS_ADMIN))
goto bad_fork_free;
}
특정 유저가保有하는 프로세스 수를 user_struct->processes에서 확인하며, 해당 유저가 root 유저거나 CAP_SYS_RESOURCE/CAP_SYS_ADMIN 특수 권한을 보유하지 않을 경우 새로운 프로세스 创建를 거부합니다.
sched_fork
스케쥴러와 관련된 설정을 수행하고 해당 작업을 CPU에 할당합니다.
copy_xxx
- CLONE_SYSVSEM 플래그가 설정된 경우: 부모 프로세스의 System V 시그널량을 복제
- CLONE_FILES 플래그가 설정된 경우: 부모 프로세스의 파일 디스크립터를 사용
- CLONE_FS 플래그가 설정된 경우: 부모 프로세스의 파일 시스템 컨텍스트 사용
- CLONE_SIGHAND 플래그가 설정된 경우: 부모 프로세스의 시그널 핸들러 사용
- CLONE_THREAD 플래그가 설정된 경우: 부모 프로세스와 공유된 시그널 처리 중 특정 핸들러와 무관한 부분 사용
- CLONE_VM 플래그가 설정된 경우: 부모와 자식 프로세스가 같은 주소 공간 사용
- CLONE_NEWxyz 플래그가 설정되지 않은 경우: 부모와 동일한 네임스페이스 사용
- CLONE_IO 플래그가 설정된 경우: 부모와 동일한 I/O 컨텍스트 사용
커널 스레드
커널 스레드는 커널이 직접 시작하는 프로세스입니다. 커널 스레드란 커널 함수를 독립적인 프로세스로 委托하는 것을 말하며, 시스템의 다른 프로세스와 함께 실행됩니다(커널 스레드는 커널 보살핌 프로세스라고도 합니다). 커널 스레드에는 두 가지 유형이 있습니다:
- 생성된 후 특정 작업 요청까지 기다리는 스레드
- 주기적으로 특정 자원 사용량을 모니터링하고, 사용량이閏임계값보다 크거나 작을 때 특정 작업을 수행하는 스레드. 커널은 이 유형의 스레드를 연속 모니터링 작업에 사용합니다.
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)