프로세스가 어떻게 생성되고 복사되는지 알아 보자.
프로세스는 구조는 다음의 영역으로 나뉜다.
데이터 영역
힙 영역
스택 영역
프로세스는 프로그램을 실행할 때 새로 생성된다. 프로그램을 실행하면 프로그램을 메모리로 가져와 코드 영역에 넣고 PCB를 생성한다. 그리고 메모리에 데이터 영역과 스택 영역을 확보한 후, 프로세스를 실행한다.
실행 중인 프로세스로부터 새로운 프로세스를 복사하는 방법도 있다. fork() 함수가 그 역할을 하는데 커널에서 제공하는 이 함수는 프로세스를 복사하는 일종의 시스템 호출이다.
예로 새로운 워드프로세서 프로그램을 하나 더 실행하면, 운영체제는 새로운 워드프로세서 프로그램을 실행하지 않고, fork() 시스템 호출을 사용하여 기존의 워드 프로세서를 복사한다.
프로세스를 복사할 때, 기존의 프로세스는 부모 프로세스가되고 새로 생긴 프로세스는 자식 프로세스가 되어, 두 프로세스는 부모-자식관계로 연결된다.
프로세스 제어 블록을 포함한 부모 프로세스 영역의 대부분이 자식 프로세스에 복사되어 똑같은 프로세스가 만들어지는데 프로세서 제어 블록의 내용 중 일부가 변경된다.
프로세스를 복사하면 다음과 같은 장점이 있다.
기억해야 할 점은 fork() 문 이전에 파일을 여거나 변수를 선언하면 이것이 모두 자식 프로세스에 상속된다는 것이다.
기존의 프로세스를 새로운 프로세스로 전환하려면 어떻게 해야할까? exec() 함수의 시스템 호출을 하면된다.
fork() 가 새로운 프로세스를 복사하는 것이라면, exec() 는 프로세스는 그대로 둔 채 내용만 바꾸는 시스템 호출이다.
exec() 시스템 호출을 하면 현재의 프로세스가 완전히 다른 프로세스로 전환된다.
사용 목적은 프로세스의 구조체를 재활용하기 위함으로 이미 만들어진 프로세스 제어 블록, 메모리 영역, 부모-자식 관계를 그대로 사용할 수 있어 편리하다. 새로운 코드 영역만 가져오면 되기 떄문에 운영체제의 작업이 수월하다.
exec() 호출을 하면
코드 영역에 있는 기존의 내용을 지우고 새로운 코드로 바꾼다.
데이터 영역이 새로운 변수로 채워지고 스택 영역이 리셋된다.
프로세스 제어 블록의 내용 중 프로세스 구분자, 부모 프로세스 구분자, 자식 프로세스 구분자, 메모리 관련 사항 등은 변하지 않지만 프로그램 카운터 레지스터 값을 비롯한 각종 레지스터와 사용한 파일 정보가 모두 리셋된다.
즉 기존의 프로세스 구조를 그대로 둔 채 내용만 바꾸어 실행하는 것이다.
위의 프로세스의 복사와 전환은 프로세스의 계층구조를 이해하는데 큰 도움이 된다.
가령 유닉스의 프로세스 계층 구조를 보자.
유닉스에서 커널이 처음 메모리에 올라와 부팅이 되면 커널 관련 프로세스를 여러 개 만드는데 그중 init 프로세스가 전체 프로세스의 출발점이 된다.
init 프로세스는 일반 사용자 프로세스의 맨 위에 위치하여, fork()와 exec() 시스템 호출을 이용하여 자식 프로세스를 만든다.
이러한 구조는 동시에 여러 작업을 처리하고, 종료된 프로세스의 자원을 회수하는데 유용하다.
부모 프로세스는 자원을 회수하기 위해 자식 프로세스의 종료를 기다려야된다.
exit() 혹은 return() 문은 자식 프로세스가 작업이 끝났음을 부모 프로세스에 알리는 것이다.
부모 프로세스가 먼저 종료되거나 자식 프로세스가 비정상적으로 종료되면 부모 프로세스에 연락이 안 되는 경우도 있다. 이런 문제가 발생하면 자식 프로세스가 종료되지 않거나, 종료 되었는데도 사용하던 자원이 그대로 남게 된다.
이러한 프로세스를 고아 프로세스(orphan process) 라고 한다.
따라서 운영체제는 반환되지 못한 자원을 회수하는 자원 회수를 주기적으로 해야 한다.
자바의 경우는 모든 객체는 Object 객체의 자식이 되므로 객체를 다 사용하고 난 뒤에 자원이 쉽게 회수되게 한다.