프로세스의 작동 과정

남우진·2025년 2월 4일

프로세스

목록 보기
3/6

Operating System Concepts, 10th Edition을 읽고 정리하기 위해 작성하는 글입니다.


프로세스 생성


실행 과정에서 프로세스는 여러 개의 새로운 프로세스를 생성할 수 있다. 이때, 생성하는 프로세스부모 프로세스라고 하며, 새로운 프로세스는 해당 프로세스의 자식이다.

대부분의 운영 체제(UNIX, Linux, Windows)는 프로세스 식별자나 pid에 따라 프로세스를 식별한다. pid는 프로세스의 고유한 값이며, 커널 내에서 프로세스의 다양한 속성에 액세스하는 인덱스로 사용할 수 있다.

위의 그림은 리눅스 시스템의 일반적인 프로세스 트리를 나타낸다.

  • systemd 프로세스는 모든 사용자 프로세스의 루트 부모 프로세스 역할을 하며, 부팅시 생성되는 첫 번째 사용자 프로세스이다.
  • logind 프로세스는 시스템에 로그인하는 클라이언트를 관리하는 역할을 수행한다.
  • sshd 프로세스ssh(secure shell)을 사용하여 시스템에 연결하는 클라이언트를 관리하는 역할을 수행한다.
  • bash 프로세스는 bash 커맨드라인 인터페이스를 사용하여 vim 에디터와 ps 프로세스를 생성한다.

UNIX 및 Linux 시스템에서 ps 명령을 사용하여 프로세스 목록을 얻을 수 있다.

  • ex) ps -el
    • 현재 시스템에 활성화된 모든 프로세스의 전체 정보를 나열한다.

또, pstree 명령을 통해 모든 프로세스 트리를 표시할 수 있다.


프로세스가 자식 프로세스를 생성할 때, 자식 프로세스는 작업을 완료하기 위해 특정 리소스가 필요하다. 리소스는 운영 체제에 직접 얻거나, 부모 프로세스의 리소스로 제한될 수 있다. 자식 프로세스를 부모 프로세스의 리소스로 제한하면 특정 프로세스가 너무 많은 자식 프로세스를 만드는 것을 방지할 수 있다.

부모 프로세스는 자식 프로세스에 리소스를 전달하는 것 말고 초기화 데이터를 전달할 수 있다.

  • ex) 터미널에 파일(hw1.c)의 내용을 표시하는 기능을 가진 프로세스는 프로세스가 생성되면 부모 프로세스로 부터 hw1.c 파일의 이름을 입력으로 받아 파일을 열고 내용을 쓴다.

프로세스가 새로운 프로세스를 생성할 때 실행과 주소 공간에는 두 가지 가능성이 존재한다.

먼저 실행에서 부모 프로세스자식 프로세스와 동시에 계속 실행되거나 자식 프로세스 중 일부 또는 전부 종료될 때까지 기다린다. 그다음 주소 공간에서 자식 프로세스는 부모 프로세스의 복제본이거나 새로운 프로그램이 로드될 수 있다.


UNIX 운영 체제에서는 새로운 프로세스는 fork() 시스템 콜을 통해 생성되고, 기존 프로세스의 주소 공간 사본으로 구성된다. 이를 통해 부모 자식은 쉽게 통신할 수 있다. 자식 프로세스의 반환 코드는 0이지만, 부모 프로세스는 자식의 프로세스 식별자를 반환한다.

fork() 시스템 콜을 사용한뒤 부모 자식 프로세스중 하나exec() 시스템 콜을 사용하여 프로세스의 메모리 공간을 새 프로그램으로 대체한다.

  • exec() 시스템 콜바이너리 파일을 메모리에 로드하고 실행한다.

그런 다음 부모 프로세스는 더 많은 자식 프로세스를 만들거나 wait() 시스템 콜을 실행하여 자식이 종료될 때까지 기다릴 수 있다.

위의 그림은 fork() 시스템 콜을 사용하여 프로세스를 생성하는 과정을 나타낸다.

  • 자식 프로세스가 exit() 시스템 콜을 사용하여 완료되면 부모 프로세스는 다시 시작한다.

UNIX fork() 시스템 콜을 사용하여 프로세스를 생성하는 코드


#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>

int main()
{
	pid_t pid;
	/* 자식 프로세스를 fork */
	pid = fork();
	if (pid < 0) { /* 에러 발생 */
		fprintf(stderr, "Fork Failed");
		return 1;
	}
	else if (pid == 0) { /* 자식 프로세스 */
		execlp("/bin/ls","ls",NULL);
	}
	else { /* 부모 프로세스 */
		/* 자식이 완료될 때 까지 기다린다. */
		wait(NULL);
		printf("Child Complete");
	}
	return 0;
}
  • 위의 코드에서 같은 프로그램의 복사본을 실행하는 두 개의 프로세스가 존재한다.
  • 자식 프로세스는 execlp() 시스템 콜(exec 시스템 콜의 특정 버전)을 사용하여 /bin/ls 명령을 주소 공간에 로드한다. (부모 프로세스로 부터 복사한 프로그램을 교체한다는 의미)
  • 부모 프로세스는 wait() 시스템 콜을 사용하여 자식 프로세스가 완료될 때까지 기다린다.

Windows에서 프로세스를 생성하는 코드


#include <stdio.h>
#include <windows.h>

int main()
{
	/* 프로세스의 속성 지정 */
	STARTUPINFO si;
	/* 프로세스 및 스레드 식별자 */
	PROCESS_INFORMATION pi;
	/* 메모리 할당 */
	ZeroMemory(&si, sizeof(si));
	si.cb = sizeof(si);
	ZeroMemory(&pi, sizeof(pi));
	
	/* 자식 프로세스 생성 */
	if (!CreateProcess(NULL, "C:∖∖WINDOWS∖∖system32∖∖mspaint.exe",
		NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
	{
		fprintf(stderr, "Create Process Failed");
		return -1;
	}
	/* 부모 프로세스는 자식 프로세스가 완료될 때까지 기다린다. */
	WaitForSingleObject(pi.hProcess, INFINITE);
	printf("Child Complete");
	/* 핸들 종료 */
	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);
}
  • 프로세스는 CreateProcess()를 사용하여 Windows API에서 생성되고, fork()와 유사하다. fork()와 차이점은 프로세스 생성 시 지정된 프로그램자식 프로세스의 주소 공간에 로드해야한다는 점이다.
  • CreateProcess()는 애플리케이션 이름, 명령줄, 프로세스, 스레드 핸들 상속, 생성 플래그, 부모 환경 블록, 디렉토리, STARTUPINFO 및 PROCESS_INFORMATION 포인터를 매개 변수로 사용한다.
  • WaitForSingleObject()는 자식 프로세서의 핸들인 pi.hProcess가 전달되며 이 프로세스가 완료될때 까지 기다리는 점wait() 시스템 콜과 유사하다.



프로세스 종료


자식 프로세스가 exit() 시스템 콜을 사용하여 종료되면 부모에게 프로세스 상태 값을 반환한다. 프로세스의 모든 리소스는 운영 체제에 의해 할당 해제 및 회수된다.

또, 프로세스는 적절한 시스템 콜을 통해 다른 프로세스를 종료시킬 수 있다.

  • ex) Windows의 TerminateProcess()

일반적으로 이러한 시스템 콜은 종료될 프로세스의 부모에서만 호출할 수 있고, 자식 프로세스를 종료하려면 자식의 ID를 알아야한다. 따라서 한 프로세스가 새 프로세스를 만들면 새로 만든 프로세스의 ID가 부모에게 전달된다.

부모는 자식이 할당된 리소스의 사용량을 초과하는 경우, 자식에 할당된 작업이 더 이상 필요하지 않는 경우에 자식을 종료할 수 있다. 또, 부모가 종료되면 운영 체제가 자식이 계속 진행하는 것을 허용하지 않는다.


프로세스가 종료되면 해당 리소스는 운영 체제에 의해 할당 해제되지만 프로세스 테이블에 있는 해당 프로세스의 항목은 부모가 wait()을 호출할 때 까지 그대로 유지되어야 한다. 왜냐하면 프로세스 테이블에는 프로세스의 종료 상태가 포함되어 있기 때문이다.

종료되었지만 부모가 아직 wait()을 호출하지 않은 프로세스좀비 프로세스라고 부르고 프로세스가 종료될 때 잠깐만 좀비상태로 존재한다. 부모가 wait()을 호출하면 좀비 프로세스의 프로세스 식별자와 프로세스 테이블의 항목이 해제된다.

만약 부모가 wait()을 호출하지 않고 대신 종료하여 자식 프로세스를 고아로 남겨놓으면 UNIX 시스템은 init 프로세스를 고아 프로세스의 새 부모로 지정하여 해결한다. 또, Linux 시스템은 systemd 이외의 프로세스고아 프로세스를 상속하고 해당 프로세스의 종료를 관리하는 것은 허용한다.



안드로이드 프로세스 계층


제한된 메모리와 같은 리소스 제약으로 인해 모바일 운영 체제는 제한된 시스템 리소스를 회수하기 위해 기존 프로세스를 종료해야 할 수 있다.

Android는 임의의 프로세스를 종료하는 대신 프로세스의 중요도 계층을 식별하여 중요도가 증가하는 순서로 프로세스를 종료한다.

  • 포그라운드 프로세스(Foreground process)
    • 현재 화면에 표시되는 프로세스로 사용자가 현재 상호 작용하고 있는 애플리케이션을 나타낸다.
  • 표시 가능한 프로세스(Visible process)
    • 포그라운드에서 직접 표시되지 않지만 포그라운드 프로세스가 참조하는 활동을 수행하는 프로세스(즉, 포그라운드 프로세스에 상태가 표시되는 활동을 수행하는 프로세스)
  • 서비스 프로세스(Service process)
    • 백그라운드 프로세스와 유사하지만 사용자에게 명확하게 보이는 활동(예: 음악 스트리밍)을 수행하는 프로세스
  • 백그라운드 프로세스(Background process)
    • 활동을 수행 중이지만 사용자에게 명확하게 보이지 않는 프로세스
  • 빈 프로세스(Empty process)
    • 어떠한 애플리케이션과 연관된 활성 구성 요소도 보유하지 않는 프로세스

시스템 리소스를 회수해야 하는 경우 먼저 빈 프로세스를 종료한 다음 백그라운드 프로세스를 종료하는 식으로 진행한다.

또, Android는 개발 관행으로 프로세스 수명 주기의 지침을 따르는 것을 제안하고, 이를 통해 프로세스의 상태가 종료되기 전에 저장되며 사용자가 애플리케이션으로 돌아가면 저장된 상태에서 재기된다.

0개의 댓글