프로세스
프로세스의 개념
프로그램과 프로세스는 분명한 차이가 있다. 프로세스는 실행 중인 프로그램을 의미하며, 이는 곧 메모리에 올라가 있는 프로그램을 의미한다. 프로세스는 자신의 작업을 하기 위해 CPU time이나 메모리, 입출력 장치 등의 리소스를 필요로 하는데 운영체제는 이러한 리소스를 잘 관리할 수 있어야 한다.
이러한 프로세스들을 자세히 살펴보려면 메모리에서 프로세스가 어떻게 구성되어 있는지 살펴보는 것이 중요하다. 크게 보면 아래 그림과 같이 나타낼 수 있다.
하나 씩 살펴보면 코드 영역(텍스트 영역)은 코드가 저장되는 부분으로 CPU는 코드 영역에서 명령어를 가져가 수행한다. 데이터 영역은 전역 변수나 정적 변수가 저장되는 영역이다(전역 변수와 정적 변수의 차이도 생각해보면 좋겠다). 스택 영역은 함수 호출과 관련된 부분으로 지역 변수나 매개변수가 저장된다. 마지막으로 힙 영역은 사용자가 동적으로 할당하는 공간으로 자바에서 new를 사용할 때 할당된다. 스택은 함수 안에서만 지역적으로 사용할 수 있고, 동적으로 크기 조절이 불가능하지만 속도가 빠르다. 반면 힙은 전역적으로 사용이 가능하고 동적으로 사이즈를 조절할 수도 있다. 대신 접근이 느리다.
자바에서 원시형 변수 int, float, double과 같은 변수는 모두 스택에 저장된다. 반면 배열, 문자열과 같은 참조형 변수는 힙 영역에 저장되고 스택에는 힙 영역의 주소가 저장되게 된다.
프로세스가 실행되면 그것의 상태가 바뀌게 되는데 크게 5가지의 상태로 나눌 수 있다.
new는 프로세스가 새로 생성된 상태로 여러 초기화 과정을 거치면 queue로 들어가 ready 상태로 대기하게 된다. 그리고 큐에서 대기 상태 중이던 프로세스가 CPU를 획득하면 running 상태로 바뀌어 실행되게 된다. 이를 스케줄러 디스패치라고 부른다. 실행 중이던 프로세스가 입출력 장치와 같은 이벤트를 갖게 되면 중지되어 waiting 상태가 되게 되고, 입출력이 끝나게 되면 다시 큐로 들어가 ready 상태가 된다. 그리고 마지막으로 프로세스 실행이 모두 끝나면 terminated 되어 자원을 회수하게 된다.
이러한 여러가지 상태를 관리하기 위해서 PCB(Process Control Block)을 사용한다. PCB에는 프로세스에 대한 다양한 정보가 들어있어 이를 통해 상태를 관리할 수 있게 된다.
프로세스 스케줄링
멀티프로그래밍의 목적은 CPU 효율을 높이는 것이고, 시분할의 목적은 각 프로그램이 동시에 돈다고 느끼게끔 하는 것이다. 그리고 시분할을 하기 위해서 프로세스 스케줄링이 필요한 것이다. 스케줄링은 앞서 살펴보았다.
프로세스가 끊임없이 교체될 때 프로세스가 어디까지 실행됐고, 이제 어디서부터 실행됐는지를 저장하고 있어야 다시 실행을 이어나갈 수 있을 것이다. 이러한 과정을 Context Switch 즉 문맥 교환이라고 한다. 문맥 교환이란 CPU 코어를 다른 프로세스에게 넘겨주는 것으로, 현재 프로세스의 상태를 저장하고 다른 프로세스의 상태를 복원하는 것을 의미한다.
프로세스에 대한 작업
프로세스는 생성과 종료 두 가지 과정을 꼭 거쳐야 한다. 프로세스가 여러 새로운 프로세스를 생성할 수도 있는데, 이를 parent process, child process로 구분할 수 있다. 여기에는 또 여러가지 가능성이 있다. 우선 실행에서 부모와 자식이 동시에 실행될 수 있고 자식이 끝날 때까지 부모가 기다리는 경우가 있다. 또한 부모의 주소 영역을 자식이 그대로 복사할 수도 있고 자식이 부모와 다른 프로세스를 가질 수도 있다.
이러한 부모-자식 프로세스 관계 때문에 다양한 상태가 생길 수 있는데, 자식 프로세스가 끝났지만 부모가 자식의 종료를 회수하지 않았을 때의 자식 프로세스의 상태를 좀비 프로세스라고 하고 부모 프로세스가 자식 프로세스보다 먼저 종료되는 상태를 고아 프로세스라고 한다.
유닉스와 같은 운영체제에서는 fork()를 통해 새로운 프로세스를 만들어낼 수 있다. 새로 생긴 프로세스는 fork() 이후부터 실행되고, 자식 프로세스의 pid는 0으로 리턴된다(부모 프로세스는 0이 아닌 값이다). 몇가지 코드를 통해 이를 분석해보자.
int value = 5;
int main() {
pid_t pid;
pid = fork();
if (pid == 0) { // child process
value += 15;
return 0 ;
}
else if (pid > 0 ) { // parent process
wait(NULL) ;
printf("Parent: value = %d\n", value); // LINE A
}
}
이 코드의 결과는 어떻게 될까? 아마 대부분 20이라고 생각할 것이다. 하지만 자세히 살펴보면 자식 프로세스가 생성되고 15를 더할 때 부모 프로세스는 wait을 하고 있었다. 자식과 부모는 각각 다른 메모리 영역을 가지고 있기 때문에 자식의 변화는 부모에게 영향을 주지 못하고 따라서 5가 그대로 출력된다. 다른 코드도 한 번 살펴보자.
int main() {
fork(); // fork a child process
fork(); // fork another child process
fork(); // and fork another
return 0;
}
이 코드는 몇 개의 프로세스를 생성할까? 정답은 8개이다. 그림으로 그려보면 이해가 쉽다. 같은 색은 같이 생성된 프로세스를 의미한다.
'CS 지식 > 운영체제' 카테고리의 다른 글
운영체제 Ch6 - 프로세서 동기화(1) (0) | 2021.12.12 |
---|---|
운영체제 Ch5 - CPU 스케줄링 (0) | 2021.12.08 |
운영체제 Ch4 - 스레드 (0) | 2021.12.08 |
운영체제 Ch3 - 프로세스(2) (0) | 2021.12.08 |
운영체제 Ch1, 2 - Introduction & OS Structures (0) | 2021.12.01 |