Process
Process의 정의
Process
는 메모리 상에서 실행중인 작업을 지칭하는 말이다.
즉, 실행중인 Program을 Process라고한다.
Program 생성 과정
우리가 작업한 소스코드가 Program이 되는 과정은 다음과 같다.
1. c언어로 코드를 작성한다고 할 때, `.c` 파일은 `gcc 컴파일러`에 의해 `.s` 확장자를 가진 어셈블리 파일로 변환된다.
2. 변환된 어셈블리 파일은 어셈블러에 의해 `.o` 확장자를 가지는 기계어 파일로 변환된다.
3. 변환된 `.o` 파일은 linker에 의해 연결되여 하나의 executable 파일로 변하는데 이를 Program이라고 한다.
linker가 여러개의 .o 파일을 연결하는 것은 우리가 서로 다른 소스코드에 모듈을 정의하고 import 하여 사용했던 기억을 떠올려보면 된다.
위 일련의 과정을 `빌드(Build)` 라고 부른다.
Process의 주소 공간(address space)
프로세스는 각각 별도의 주소 공간을 할당받는데,
`Code`, `Data`, `Heap`, `Stack`의 4가지 영역으로 이루어져 있으며 다음과 같은 구조로 되어있다.
실제로는 Program Code 영역도 instruction을 담는 `code 영역`과 global variable을 담는 `data 영역`으로 나뉘어진다.
이는 instruction을 담는 영역을 read-only로 설정하여 임의로 내용을 변경할 수 없게 하기 위함이다.
한 번에 여러 프로세스가 실행되고 있는 경우, 구조는 아래와 같다.
각각의 Process A, B, C 는 이전에 살펴보았던 address space 구조를 가지고 위와 같이 메모리 공간에 위치하게 된다.
Process의 Context
특정 시점에서 process의 상태를 파악하기 위한 여러가지 정보들을 `process context`라고 한다.
이러한 context 정보는 크게 hardware context, process address space, kernel data structure 3가지로 나뉜다.
1. hardware context
실행중인 instruction의 위치를 나타내는 `Program Counter`와 다양한 `register` 정보들에 해당한다.
2. process address space
위에서 살펴본 code, data, stack 등에 해당한다.
3. kernel data structure
Kernel이 process를 관리하기 위한 정보를 담고 있는 자료구조인 `PCB`가 이에 해당한다.
또한, Kernel에서는 kernel에게 요청을 위임한 프로세스 마다 별도의 stack을 가지고 있는데, 이를 `Kernel stack`이라고 하며 이 또한 Process의 상태를 파악하기 위한 context에 해당한다.
Process의 Context Switching
위에서 살펴본 process context는 context switching을 위해 필요한 정보이다.
CPU에서 여러 Process를 동시에 실행시키는 경우 코어가 1개라면 실제로는 2개 이상의 Process가 동시에 실행되는 것이 아니라, OS에 의해 빠르게 전환되며 실행된다.
이를 Concurrency, 즉 동시성이라고 하며 Parallelism(병렬성)은 실제로 여러 작업이 동시에 실행되는 것을 의미한다.
병렬성은 CPU의 코어가 여러개인 경우, 실제로 각 코어별로 Process를 동작시킬 수 있기 때문에 실현될 수 있다.
Concurrency하게 process를 실행하기 위해 CPU는 process에 대한 정보들(register 값, process state) 등을 저장하고 다른 process에 대한 작업을 수행하며, 이를 반복한다.
이 과정을 Process의 Context Switcing 이라고 한다.
이 Context Switching 과정에서 Process context가 제대로 저장되지 않고 switching이 일어난다면 실행중이던 정보가 날아가는 등의 복구가 불가능한 치명적인 에러가 발생할 수 있다. 따라서 앞서 살펴본 다양한 process context를 안전하게 저장하고 switching을 수행해야 한다.
이러한 Process Context 정보는 PCB 혹은 커널 메모리에 저장된다.
Context Switiching 과정을 살펴보면 다음과 같다.
Context Switching 과정은 당연히 그 자체로 cost가 발생한다.
이러한 cost에는 direct cost와 opportunity cost가 존재하는데, 말 그대로 직접적으로 발생하는 cost와 기회비용처럼 발생하는 cost가 존재한다.
direct cost에 경우 그림에 나와있는 PCB 저장 -> 복구 과정에 해당하며
opportunity cost의 경우 Cache와 관련된 cost이다.
Process 동작 과정에서 실행 속도를 빠르게 하기 위해 cache를 사용하는데, context switching 시에는 cache를 flush, 즉 모두 비우는 작업을 수행해야한다.
실행 속도를 빠르게 하기 위해 애써 채워놓았던 cache의 내용을 모두 버려야하기 때문에, 이는 매우 큰 cost로 작용하게된다.
Process의 State
Process는 ready, running, block의 세 가지 상태를 가지며, transition diagram으로 표현하면 아래와 같다.
`ready` state는 프로세스가 생성된 후, CPU에 의해 실행되기 위해 대기하고 있는 상태를 말한다. 메모리 로드 등 실행에 필요한 모든 조건을 만족한 상태로, CPU 자원만 얻으면 즉시 실행될 수 있는 상태를 말한다.
`running` state는 프로세스가 자원을 얻고, CPU에 의해 실행되고 있는 상태를 말한다.
`block` state는 CPU 자원을 할당해도 당장 instruction을 실행할 수 없는 상황을 말한다.
아직 실행해야 하는 instruction이 메모리에 로드되지 않았거나, I/O등의 interrupt에 의해 실행이 중단되고 다시 자원을 얻을 때 까지 대기하는 상태 등이 이에 해당한다.
다시 자원을 얻으면 ready 상태가 되어 자신의 순서를 기다린다.
PCB(Process Control Block)
운영체제가 각 프로세스를 관리하기 위해 필요한 정보를 가지고 있는 데이터구조이다.
위 그림에서 각 파트가 담당하는 정보는 다음과 같다.
(1) OS가 프로세스를 관리하기 위해 사용하는 정보로, Process state, Process ID, scheduling information, priority 등의 정보가 포함된다.
(2) CPU가 프로세스를 수행하는 것과 관련된 하드웨어의 정보로, Program Counter, register 정보등을 포함한다.
(3) 메모리와 관련된 정보로, Code, Data, Stack의 위치 정보를 포함한다.
(4) 파일과 관련된 정보로, Open File Descriptor 등의 정보를 포함한다.
Process의 생성과 종료
프로세스의 생성은 일반적으로 Parent Process가 Children Process를 생성하는 방식으로 이루어진다.
유닉스 시스템에서는 `fork()` system call을 사용하여 프로세스를 생성할 수 있다.
해당 함수의 호출 시 자식 프로세스를 생성하게 되며, 자식은 부모의 주소공간과 PCB 등의 OS data를 복사하여 가지게 된다.
`fork()` call 이후에 `exec()` system call을 사용하면 새로운 프로그램을 메모리에 올리면서, 완전히 다른 작업을 수행하는 프로세스를 생성할 수 있다.
프로세스가 마지막 명령을 수행하면 `exit()` system call을 사용하여 자식이 부모에게 output data를 보내고 각종 자원들을 OS에게 반납하면서 프로세스가 종료된다.
만약 프로세스를 중간에 중단시키고 싶다면, abort() system call을 사용하여 자식 프로세스를 종료시킬 수 있다.
이는 보통 자식이 할당 자원의 한계치를 넘어서거나, 자식 프로세스가 진행 중인 태스크를 더 이상 진행시킬 필요가 없는 경우에 수행한다.
만약 부모 프로세스가 exit하는 경우, 반드시 자식 프로세스가 모두 종료된 상태여야 하기 때문에, 해당 상황 발생 시 자식 프로세스를 먼저 순차적으로 exit한 후에 부모 프로세스의 process가 exit된다.
Copy On Write
자식 프로세스가 생성되는 경우, 즉시 모든 데이터 공간을 복사하지 않는 방식을 `Copy On Write`라고 한다.
처음 자식 프로세스가 생성되었을 때에는 모든 데이터 공간이 부모 프로세스와 완전히 동일하다.
따라서 이 경우, 실제로 메모리 공간에 자식 프로세스의 자원을 할당해주는 것이 아니라, 부모 프로세스의 메모리 공간을 참조하도록한다.
이후 Write 등의 작업으로 자식 프로세스의 내용에 변화가 발생하는 경우, 해당 공간만 새롭게 할당하며 자식 프로세스가 가지도록 한다.
Process 생성 System Call
fork() System Call
int pid = fork( );
if (pid == 0) {
printf(“I am process #%d\n”, getpid( ));
return 0;
} else {
printf(“I am parent of process #%d\n”, pid);
return 1;
}
fork()로 자식 프로세스가 생성되면, fork() 함수 콜 이후부터 실행이 되기 시작한다.
이는 자식 프로세스가 생성될 때, 부모의 모든 정보를 그대로 복사하는 과정에서 Program Counter까지 복사해오기 때문이다.
fork() system call의 리턴 값은 부모 프로세스와 자식 프로세스에게 다르게 전달되는데,
부모 프로세스의 경우 새롭게 생성한 자식 프로세스의 PID 값을, 자식 프로세스는 0이라는 값을 받게 된다.
따라서 이 값을 사용해서 자식 프로세스와 부모 프로세스가 다른 동작을 하게 구현할 수 있는데, 위 코드와 같이 if문을 통해 분기한 것이 그 예시이다.
exec() System Call
char *prog, **args;
int child_pid,status;
while (readAndParseCmdLine(&prog, &args)) {
child_pid = fork( );
if (child_pid == 0) {
exec(prog, args);
/* NOT reached here !! */
}
else {
wait(&status);
return 0;
}
}
exec system call를 사용하면, 부모 프로세스와 동일한 형태인 자식 프로세스를 완전히 다른 동작과 상태를 가진 프로세스로 만들 수 있다.
exec call 수행 시, 매개변수로 넘겨준 prog을 args 매개변수를 활용하여 수행하기 시작한다.
따라서 exec 이후의 코드는 실행되지 않으며, prog라는 새로운 프로그램에 해당하는 코드의 첫 줄 부터 순차적으로 수행되기 시작한다.
wait() System Call
int pid = fork( );
if (pid == 0) {
//Doing Something
} else {
wait();
}
일반적으로 wait() 시스템 콜은 자식 프로세스를 만든 후 사용된다.
wait() 시스템 콜을 사용하면 프로세스는 sleep 상태로 빠지며(block 상태) 자식 프로세스의 종료까지 대기하게된다.
exit() System Call
프로세스를 종료할 때 사용하는 시스템콜이다.
명시적으로 넣어주어서 종료시킬수도 있고, main 함수 리턴 위치에 컴파일러가 자동으로 넣어주는 방식으로 종료시킬수도 있다.
Process Communication
프로세스는 원칙적으로 개별적으로 수행되나, 서로 정보를 주고받으며 협력하는 것이 성능을 향상시키는 상황이 존재할 수 있다.
따라서 이러한 상황에서 프로세스가 서로의 정보를 주고받을 수 있는 방법을 정의하는데, 이를 IPC(Interprocess Communication)이라고 한다.
프로세스는 다른 프로세스와 협력하기 위해 메세지를 주고받는 형식을 사용할 수도 있고, Shared Variable을 사용하는 방식으로도 협력할 수 있다.
1. Message Passing
프로세스는 원칙적으로 다른 프로세스의 메모리 공간을 참조할 수 없기 때문에, 커널의 힘을 빌려서 메세지를 전달한다.
2. Shared Memory
서로 다른 프로세스 간에도 일부 주소 공간을 공유하게 하는 방식이다.
Thread
Thread에 대해서는 https://simple-coding-place.tistory.com/22
[OS] Thread
Thread Thread의 정의 Thread란 process 내부에서 실행되는 여러 흐름의 단위를 말한다. Thread의 주소 공간 Thread는 Process와 달리 Stack만을 고유하게 가지며, Code, Data, Heap 등을 공유한다. Thread의 Context Switch
simple-coding-place.tistory.com
에서 자세히 다루고 있으나, 간단하게 그 개념에 대해서만 언급하려고 한다.
앞서 살펴본 것 처럼, Process는 각자의 메모리 공간을 가지며 한 번에 여러 프로세스가 메모리 공간에 올라갈 수 있다.
이러한 멀티 프로세스는 task 처리 속도를 향상시키는 매우 중요한 메커니즘이다.
유사한 동작을 하는 여러개의 프로세스가 있는 상황을 떠올려보자. 각 프로세스는 각자의 메모리 공간을 차지하면서 실행되고 있을 것이다.
만약 이 프로세스들이 완전히 동일하거나, 유사한 동작을 한다고 하면, 하나의 메모리 공간을 공유하면서 각자의 logic을 수행할 수 있다면, 더 효율적이지 않을까?
이 아이디어를 적용한 것이 Thread이다.
즉 Thread는 특정한 메모리 공간(주로 Stack에 해당한다)만을 고유하게 가지고 나머지 영역을 공유하는 프로세스 내부의 동작 흐름을 말한다.
PCB에는 이제 Thread 당 PC, register의 정보를 저장해야한다.
'OS' 카테고리의 다른 글
[OS] 1. 운영체제란? + 기본적인 컴퓨터의 구조 (0) | 2023.10.26 |
---|---|
[OS] 9. File System (0) | 2023.10.08 |
[OS] 8. Virtual Memory (0) | 2023.10.03 |
[OS] 7. Address Translation (0) | 2023.10.03 |
[OS] 4. Process Scheduling (0) | 2023.10.02 |