사용자는 더블클릭으로 프로그램을 실행한다.
실행중인 프로그램을 닫기 버튼으로 종료한다.
CPU가 1개인 PC에서 여러개의 프로그램을 실행시킬때,
마치 동시에 여러 프로그램을 실행시키는 느낌이 든다.
이는 모두 운영체제가 프로세스를 관리해주기 때문이다.
그렇다면 어떻게 운영체제가 프로세스를 관리하는지 알아보자.
먼저 프로세스란 실행중인 프로그램을 의미한다.
프로세스는 3가지로 구성되어 있다.
사용자가 프로그램을 실행시키면 운영체제에 의해 메모리(RAM)에 프로세스가 올라간다.

위 그림에서 보면 커널이란 것도 메모리에 올라가 있다.
커널은 운영체제의 핵심기능이다. 좁은 의미의 운영체제다.
운영체제 또한 프로그램이며 부팅시에 메모리에 올라간다.
항상 메모리에 상주하며 운영체제의 핵심적인 기능들을 수행하는 프로세스를
커널이라고 생각하면 된다.
커널 또한 프로세스이기 때문에 stack, data, code로 구성되어 있다.
그렇다면 운영체제는 어떻게 프로세스를 생성할까?
그에 앞서 System call이라는 개념을 알아야 한다.
System call이란 커널함수이다.
즉, System call을 사용하면 운영체제의 기능을 실행시킬 수 있다.
프로세스와 관련된 System call은 다음과 같다.
프로세스는 위의 System call을 사용하여 커널에 의해 프로세스를 생성할 수 있다.
아래 예를 보자.
#include <unistd.h>
#include <stdio.h>
#include <wait.h>
int main()
{ // fork()의 리턴값은 복제된 프로세스의 id.
0 // 자식 프로세스는 fork() 이후 부터 실행되며, fork()의 리턴값으로 0을 받는다.
1 pid_t pid = fork();
2
3 if (pid == 0){
4 printf("Child Process pid : %d\n", pid);
5 execlp("echo", "echo", "Hello World", NULL);
6 printf("이 부분은 출력이 되지 않음.");
7 }
8
9 if (pid > 0){
10 printf("Parent Process pid : %d\n", pid);
11 }
12
13 wait(NULL);
14 printf("Parent Process Done\n");
15
16 return 0;
}
위의 코드를 컴파일 후 실행하면 프로세스다.
이 프로세스는 1번줄에서 프로세스를 복제한다.
1번줄을 실행한 후에는 같은 일을 하는 프로세스가 2개가 되었다.
부모프로세스는 9번줄로 이동한 후 13번 줄에서 자식프로세스가 종료될 때까지 기다린다.
자식프로세스는 3번줄로 이동한 후 5번줄에서 새로운 프로그램으로 덮어씌워 진다.
(echo는 뒤에 문자열을 터미널에 출력해주는 프로그램이다.)
자식프로세스는 5번줄 이후로는 새로운 프로그램으로 덮어씌워지고, 따라서 6번줄은 출력되지 않는다.
이 프로세스의 실행결과는 아래와 같다.

그렇다면 위의 예제코드로 만든 프로세스는 누가 생성해준 것일까?
리눅스 터미널에는 bash shell이라는 프로그램이 실행되고 있다.
따라서 우리가 실행명령어를 입력하면 bash shell 프로세스에
인자로 전달되어 우리의 프로세스를 생성해 주는 것이다.
이렇게 운영체제는 태초의 프로세스로부터 복제, 생성을 반복하여 프로세스를 생성하게 된다.
내가 실습에 사용한 컴퓨터는 CPU가 1개(core 1개)이다.
그런데 방금 예로든 프로그램을 실행하면
최소 bash shell, parent process, child precess만 해도 3개다.
이 3개가 빠르게 실행되었다.
프로세스는 어떻게 교체되는지 알아보자.
프로세스에는 Context란 개념이 있다.
Context는 현 시점 프로세스의 상태다.
프로그램 내부에는 변수가 존재하고 이 변수에는 시간에 따라 여러 값이 할당된다.
특정 시간에 변수의 값과 같은 것들이 현재 프로세스의 context라고 볼 수 있다.
이러한 context는 PCB(process controll block)라 불리는 자료구조에 저장된다.
이 PCB는 프로세스별로 존재하며, 커널의 데이터 영역에 존재한다.
프로세스A가 실행중에 프로세스B로 교체를 하게되면
프로세스A의 정보를 프로세스A의 PCB에 저장하고,
프로세스B의 정보를 프로세스B의 PCB에서 가져와 CPU에 로드한다.
이렇게 하면 프로세스B는 이전에 멈췄던 작업부터 다시 재개할 수 있다.
이를 Context Switching이라 한다.
CPU는 경주마와 같다. 명령어 1줄 실행하고,
다음 명령어 실행하고...
다음 줄만 보며 달린다.
이런 CPU를 잠시 멈추는 것을 Interrupt라고 한다.
CPU는 실행중인 프로세스의 명령어를 실행하고, Interrupt확인을 한다.
이 작업을 반복하면서 Interrupt가 없으면 계속 하던 일을 진행한다.
그리고 만약 Interrupt가 있다면, CPU는 실행중인 프로세스를 PCB에 저장한 후,
커널의 ISR(Interrupt Service Routine)부분의 코드를 실행하여
각 Interrupt에 해당하는 Routine을 실행한다.
이후 대기큐의 있는 프로세스에 해당하는 PCB를 불러와 로드한 후 실행한다.
Interrupt의 예로는 System call, CPU할당시간 초과, 입출력필요와 같은 것들이 있다.
아래는 CPU할당시간이 초과되어 프로세스가 교체되는 과정을 보여준다.

이렇게 운영체제는 Interrupt에 의한 Context Switching으로 프로세스의 교체를 구현한다.
앞에서 본 프로세스가 생성되고 교체되는 과정에서
프로세스를 몇가지 상태로 표현할 수 있다.
프로세스는 생성되어 메모리에 올라간다.
프로세스는 메모리의 일부를 자원으로 할당받는다.
프로세스가 생성되어 자원을 할당받고 있는 상태를 'New'라고 표현한다.
프로세스가 자원을 할당받아 메모리에 올라가 있는 상태다.
언제든지 CPU가 작업할 수 있는 상황이다.
CPU가 해당 프로세스를 실행하고 있는 상태다.
프로세스가 다른 작업(입출력)이 완료될 때까지 기다리는 상태다.
사용자의 입력을 받을때까지 대기해야하는 상황을 표현한다.
다른 작업이 완료되면 Ready상태로 넘어간다.
운영체제에 의해 메모리가 아닌 HDD(SSD)로 쫓겨난 상태다.
운영체제는 메모리에 너무 많은 프로세스가 올라오게 되면,
작업을 처리하기 어렵기 때문에 몇몇 프로세스들을 내쫓는다.
운영체제에서 이러한 부분을 담당하는 곳을
중기스케쥴러(Medium-Term Scheduler)라고 부른다.
운영체제는 각 프로세스를 상태별로 적절하게 큐에 넣어 스케쥴링한다.

아이콘 출처
Cpu 타워 아이콘 제작자: Good Ware - Flaticon
Ram icons created by Freepik - Flaticon
Computer icons created by xnimrodx - Flaticon
Keyboard icons created by Freepik - Flaticon
Printer icons created by bqlqn - Flaticon
Hdd icons created by Hilmy Abiyyu A. - Flaticon
Queue icons created by Freepik - Flaticon
Operating system icons created by Freepik - Flaticon
Traffic icons created by fjstudio - Flaticon
참고자료
http://www.kocw.net/home/cview.do?cid=3646706b4347ef09