프로세스 (Process)
프로세스는 현재 실행중인 프로그램을 뜻한다. 정확히는 메모리에 올라와 있는 상태에서 실행 중인 프로그램이다. Process 외에도 Task, Job 등의 단어로도 쓰인다. 응용 프로그램 밑에서 여러 프로세스가 동작하기 때문에, 둘의 의미는 다르다.
하나의 PC에서는 정말 많은 프로세스가 실행되고 있다. 우리 눈에는 모든게 동시에 돌아가는 것처럼 보이지만, 사실 이 프로세스들은 하나하나가 순서를 따져가면서 CPU의 연산을 기다리고 있다. 이렇게 프로세스의 작동 순서를 결정하는 것을 프로세스 스케줄링(Process Scheduling)이라 한다.
프로세스의 구조
프로세스는 text(code), data, stack, heap의 네 가지로 구성된다.
text(code): 프로세스가 작업을 실행할 코드를 의미한다.
data: 변수나 초기화된 데이터들을 의미한다.
stack: 임시 데이터를 의미한다. 함수를 호출하거나, 함수 내의 로컬 변수등이 저장된다.
heap: 코드에서 동적으로 만든(new class() 같은) 데이터를 의미한다. 동적으로 만들어진 데이터는 프로세스가 살아있는 동안 계속해서 저장된다. 다만 계속해서 만들면 프로그램이 죽을 수도 있기 때문에, 메모리를 직접적으로 건드리지 않는 컴퓨터 언어(자바 같은)를 쓰는 프로그램은 자체적으로 GC(Garbage Collector)를 실행시켜 쓰이지 않는 데이터를 비운다.
프로세스 스케줄링
프로세스가 너무 많아져버리면 흔히 아는 랙이 발생하게 된다. 화면이 버벅거리고, 입력이 반영되는데 오랜 시간이 걸리는 등 PC도 열이 받고 사용자도 열이 받는 것. 이런 문제를 완화하기 위해서는 프로세스의 연산을 잘 분배하는 것이 중요한데, 이에 프로세스 스케줄링이 필요하다.
배치 처리(Batch System)
단순하다. 여러 프로세스를 그냥 먼저 온 순서대로 실행시키게 만드는 방법이다. 큐의 구조를 띈 메모리에 프로세스가 들어오고, 그렇게 필요한 연산을 할당하게 된다. 그렇기 때문에 아주 당연한 문제가 하나 있는데, 처리량이 많은 프로세스가 오더라도 다른 프로세스들이 기다려야 한다는 것.
이러한 불균형을 해소하기 위해 시분할 처리가 등장한다.
시분할 처리(Time Sharing System)
하나의 PC를 많은 사람들이 사용할 땐 어떻게 해야할까? 예를 들어 많은 사람들이 하나의 PC에 원격으로 접속해 작업을 하는 것이다. 하지만 배치 처리 시스템 하에서는 A라는 사람이 연산량 5000의 프로세스를 등록해버리면, 같이 있던 B나 C는 연산량 100의 프로세스를 실행시키고 싶어도 대기열에 막히고 만다(이걸 가지고 호위 상태(Convoy Effect)라고 한다). 이런 일을 극복하기 위해 나온 것이 시분할 처리이다.
시분할 처리는 프로세스의 작업량을 시간 단위로 쪼개, 돌아가면서 조금씩 조금씩 처리해주는 방법이다. 이걸 이용하면 B와 C는 오래 기다리지 않고 맡겨놓은 프로세스를 실행시킬 수 있게 된다.
이렇게 작업량을 잘개 쪼개서 실행시키면 프로세스가 마치 동시에 실행되는 것처럼 보이게 되는데(e.g. 유튜브를 틀어놓고 게임을 한다던지), 이것을 멀티 태스킹(Multi-Tasking)이라고 말한다. 실은 단일 CPU 아래에서 사용자가 눈치채지 못할만큼 빠른 속도로 프로세스를 옮겨가며 실행하고 있는 것이기는 하다...
멀티 프로그래밍(Multi-Programming)
레지스터, L1~3 캐시는 느려도 40 사이클이면 동작한다. SRAM, DRAM은 2~70 나노초가, 플래시 드라이브(NVME 같은 거)는 10마이크로초, 하드 디스크까지 나아가면 밀리초 단위까지 올라간다.
갑자기 이런 말을 왜 하냐면, 프로세스가 파일을 읽을 때에는 위와 같은 시간이 필요해 연산량을 할당받아도 딱히 할 일이 없다는 것을 말하기 위함이다. 예를 들어 어떤 텍스트 파일을 불러오기 위해 10ms를 대기해야하는데, 이 구간에서는 스케줄링 알고리즘에 의해 연산량을 할당받아도 아무 일도 못하게 된다. 차라리 다른 프로세스의 일을 하는 게 낫다는 것.
그렇기에 이런 특수한 상황에서는 프로세스가 연산량을 할당받지 않도록 하는 것이 필요한데, 이를 대기 상태로 표현한다. 대기 상태에 들어간 프로세스는 상태가 끝날 때까지 다음 작업에 할당되지 않고, 대신 그 구간에 다른 프로세스들이 들어가 작업량을 할당받음으로써 좀 더 효율적으로 CPU를 써먹을 수 있게 된다. 이렇게 CPU의 성능을 최대한 높히는 작업을 멀티 프로그래밍이라고 한다.
FIFO(First In, First Out) 스케줄러
배치 처리 시스템을 이용하는 가장 간단한 스케줄러다. FCFS(First Come, First Serve) 스케줄러라고도 한다. 위의 배치 처리 스케줄러와 거의 똑같이 작동한다.
FIFO답게 큐 자료구조를 사용해 구현할 수 있다.
SJF(Shortest Job First) 스케줄러
프로세스의 작업량이 적은 것부터 먼저 처리하는 스케줄러다. 얼핏보면 좋은 것 같지만, 이렇게 되면 작업량이 많은 프로세스는 연산량을 할당받지 못한 채 뒤에서 무한히 기다리게 되는 좋지 못한 상황(이를 기아 현상이라고 한다)이 발생할 수 있다.
우선순위 큐와 힙 자료구조를 사용하면 구현할 수 있다.
RealTime OS(RTOS), General Purpose OS(GPOS)
RTOS는 실시간으로 최적의 성능을 보장하는 것이 목표인 OS이다. 매우 정확하게 프로그램의 시작과 완료 시간을 보장한다. 다만 위에 보듯이 시분할 시스템으로 프로세스를 배치한다고 해서 완벽한 성능을 보장할 수 있는 것은 아니다. 되려 시분할 시스템보다는 멀티 프로그래밍을 사용하는 것이 더 낫다. 이런 시스템은 아주 정밀한 시간을 요구하는(항공기나 병원처럼) 상황에 사용된다.
GPOS는 RTOS와는 달리 좀 더 널널하게 프로세스를 할당하는 OS다. 윈도우나 리눅스가 여기에 해당된다.
Priority 스케줄러
SJF 스케줄러와 비슷하다. 이 경우 작업량이 적은 순서가 아니라, 프로세스가 가진 우선 순위를 기반으로 순위를 매겨 실행한다. 그렇기에 우선 순위 기반 스케줄러라고도 한다.
RR(Round Robin) 스케줄러
시분할 시스템과 거의 같다. 프로세스의 연산량을 제한하고, 연산이 다 끝나지 못하면 맨 뒤로 돌아가서 대기시키는 스케줄러다.
프로세스의 상태
이건 중요하니까 뇌에 문신으로 새기던가 하자.
프로세스는 5개의 상태를 가진다.
생성(Create, New): 프로세스가 생성되는 순간이다. OS에 의해 프로세스는 초기화된다.
준비(Ready): 동작할 준비를 마치고 기다리는 상태다.
실행(Running): CPU 자원을 할당받아 신나게 돌아가는 상태다.
대기(Waiting, Blocked): 특정 이벤트나, I/O Devices의 입력 등을 기다리는 상태다.
종료(Terminated, Exit): 프로세스가 자의(실행 끝)로든 타의(오류 등)로든 끝나는 순간이다. OS에 의해 프로세스에 할당된 메모리등이 정리된다.
생성과 실행, 종료는 곧바로 수행되지만, 준비와 대기 상태는 줄을 서고 오래 기다리는 구간이라 큐 형태의 대기열을 가진다.
컨텍스트 스위칭(Context Switching, 문맥 교환)
위의 스케줄러에 의해, CPU는 하나의 프로세스 뿐만이 아니라 여러가지 프로세스를 실행시킬 수 있다. 여기서 CPU가 연산을 수행할 프로세스를 교체하는 작업을 컨텍스트 스위칭이라고 한다.
프로세스의 전반적인 상태는 PC(Program Counter)와 SP(Stack Pointer)를 사용해 저장된다. 이렇게 프로세스 상태가 저장되면, CPU는 다른 프로세스의 연산을 수행하고 돌아와서도 기존에 하던 부분에서 이어서 할 수 있게 된다. 그렇다면 PC와 SP를 저장할 곳이 필요하게 되는데, 이것을 PCB(Process Control Block)라는 구조체가 담당하게 된다.
PCB에는 '내가 어떤 프로세스를 가리키고 있는지'를 알려주는 PID(Process ID)를 포함해, 프로세스가 가진 여러가지 상태가 저장된다. 위에 말한 PC와 SP(위 이미지의 Pointer)도 담겨있으며, 컨텍스트 스위칭은 이 PCB를 CPU 내 레지스터에 교체하는 것을 말하는 것이다.
이러한 컨텍스트 스위칭도 하나의 작업이므로, 당연히 연산량을 잡아먹는다. 이것 때문에 시분할 시스템이 데우스 엑스 마키나가 되지 못한다. 시분할을 통해 프로세스 연산을 쪼개는 건 좋지만, 너무 많이 쪼개서 프로세스를 계속 전환하게 되면, 그만큼 컨텍스트 스위칭도 많이 발생해 전체적으로 CPU 효율의 하락을 불러오기 때문.
프로세스 간 교신(IPC, InterProcess Communication)
일반적으로 프로세스는 다른 프로세스의 공간에 접근할 수 없다. 엑셀로 문서를 편집하다가, 참고할 자료가 있어 인터넷 브라우저를 열었더니 갑자기 엑셀의 문서값이 자기 멋대로 바뀌어버린다고 생각해보자. 적지 않은 혈압약이 필요해지게 된다. 그렇기에 프로세스들의 데이터 부분은 보호되며, 다른 프로세스에서는 접근할 수 없도록 되어 있다.
하지만 극한의 효율을 위해서는 여러 프로세스를 병렬적으로 돌릴 필요가 있다. 예를 들어 파일이 저장되자마자 다른 프로그램에 반영된다던지 하는 것이 필요한 경우가 많다는 것이다. 그러던 중에 알게 된 것이, 프로세스는 프로세스 자체의 공간은 폐쇄적으로 운용하지만, 커널이 가진 공간은 공통적으로 사용한다는 것이었다(프로세스는 결국 커널의 기능(시스템 콜 같은)을 사용한다는 것을 기억하자). 이 커널 공간을 사용하면 프로세스끼리도 데이터를 주고 받을 수 있게 되는데, 이를 IPC 기법이라고 한다.
IPC 기법은 다음과 같은 것이 있다.
1. 파일을 저장하고, 다른데서 불러오기 (이 경우 당연하지만! 커널 공간을 쓰지 않는다)
2. Message Queue: 프로세스 내에 메시지를 받을 수 있는 큐를 하나 만들어놓는다. 다른 프로세스에서 특정 메시지가 들어오면, 그것을 적절히 가공해서 사용한다. 일종의 모스 부호 통신 같은 느낌.
3. Shared Memory: 말 그대로 공유 자원을 같이 쓴다. 동기화의 문제가 있다.
4. Pipe: 파이프라는 자료를 사용해 단방향으로 자료를 보낸다. '단방향'이므로, 상하관계를 가진 프로세스 간 통신에 유용하다.
5. Signal
6. Semaphore
7. Socket
1번도 많이 쓰이지만, 대표적으로는 메시지 큐, 공유 메모리, 파이프가 자주 쓰인다.
'프로그래밍 > 개념원리' 카테고리의 다른 글
가상 메모리(임시) (0) | 2024.03.24 |
---|---|
스레드(임시) (0) | 2024.03.24 |
커널(임시) (0) | 2024.03.23 |
레지스터, 파이프라인(임시) (0) | 2024.03.23 |
메모리 (0) | 2024.03.17 |