본 내용들은 전공수업으로 배운 내용들을 스스로 리마인드하기 위한 목적으로 작성되었으며, 누군가에게 지식을 전달하기 위한 목적으로 작성한 것이 아닙니다. 대부분의 내용은 제가 수강했던 전공수업 및 Operating System Concepts, 10th Edition에 근거하고 있습니다.
1. 개념
- 프로세스는 실행 중인 프로그램을 의미한다. 즉 온 메모리 상태인 것. 프로그램은 secondary storage 내에 있는 실행가능한 코드를 의미한다.
- 물론 하나의 프로그램이 여러 개의 프로세스를 가질 수도 있다. 한 프로그램이 여러 번 실행되는 상황이라든지.
- 이런 프로세스는 다음과 같은 구성요소로 이뤄진다.
- Text Section: 실제 프로그램의 코드가 저장된 부분, instruction set.
- Data Section: 전역변수, static 변수 등 프로세스 시작 시점에 미리 할당되는 영역.
- Stack: 함수 호출과 함께 할당되고, 함수가 끝나면 소멸하는 영역. 지역 변수(주소), 매개 변수(주소), return address가 저장된다. 이름대로 실제 Stack 구조.
- Heap: 런타임에 동적으로 할당되는 영역으로, 유저 혹은 GC(garbage collector)에 의해 메모리 공간이 동적으로 할당 및 소멸한다. 따라서 객체나 배열 등의 실질적인 데이터는 모두 이 영역에 담긴다.
- 그리고 실제 CPU 내에서 프로세싱 중이라면 program count나 register 값들도 있다
- 위의 네 가지 영역은 메모리에 로드되는 부분들이고, 그 중 Text Section과 Data Section은 빌드 타임에 크기가 정해지고, Stack 및 Heap 영역은 런타임에 크기가 결정된다.
- 프로세스는 다양한 state를 가진다. New(생성 중인 프로세스), Running(명령어들이 실행 중인 상태), Waiting(I/O 완료 등 특정 이벤트를 기다리는 상태), Ready(프로세서에 할당되기를 기다리는 상태), Terminated(종료된 프로세스)
- PCB(Process Control Block): OS의 프로세스 관리 단위로, 각 프로세스에 관한 정보를 담고 있는 구조체다. Process state, Program counter 값, CPU register 값들을 비롯해, 스케줄링에 필요한 우선순위나 메모리 관리에 필요한 base/limit register, CPU 점유시간, I/O 상태 정보 등 많은 정보가 담겨있다.
- Thread: 스레드. 한 프로세스 내에서 여러 작업을 동시에 하기 위한 개념으로, 당연하게도 스레드가 늘면 PCB 내에 Program counter도 그만큼 많이 들고 있어야 한다.
2. 프로세스 스케줄링
- Process Scheduler: 현재 준비된 프로세스들 중에서 다음에 CPU 코어에서 실행될 프로세스를 선택한다.
- 프로세스 스케줄링은 CPU 사용을 극대화하기 위함이다.
- 프로세스들을 스케줄링 큐에 담아 관리하게 되는데, 일반적으로 Ready Queue와 Wait Queue로 나누어진다.
- 이름에서 짐작할 수 있듯이, Ready Queue에는 CPU 코어에 할당할 준비가 끝난 프로세스들이 대기하며, Wait Queue에는 I/O나 자식 프로세스의 종료, 인터럽트 대기 등 목적에 따라 장시간 이벤트를 기다려야 하는 프로세스들이 대기한다.
- 큐 내부에는 PCB들이 저장되며, 이들은 상태에 따라 다른 큐로 옮겨질 수 있다.
- Context Switch: CPU에서 실행 중인 프로세스가 변경될 때 발생하며, 현재까지 프로세싱하던 데이터 상태들(레지스터 값, PC 값 등)을 해당 프로세스의 PCB에 저장하고, 다음으로 실행될 프로세스의 PCB로부터 값들을 불러오는 작업이다. 이 Context Switch에 걸리는 시간은 순수한 오버헤드로, OS와 PCB가 복잡해질수록 이 오버헤드도 커진다.
- 이런 오버헤드는 하드웨어의 서포트에 따라서도 달라질 수 있는데, 각각의 CPU 코어 내에 여러 쌍의 레지스터들을 보유하는 경우 여러 개의 context들을 한번에 로드해두고 사용할 수 있기 때문이다. 이는 물리적 스레드를 의미한다.
3. 프로세스 작동(Operation)
- 시스템은 프로세스의 생성 및 종료를 위한 매커니즘을 제공한다.
- 보통 프로세스는 pid를 통해 식별 및 관리되며, 부모 프로세스가 자식 프로세스를 생성하는 형태로 만들어져 트리 구조를 이룬다.
- 이렇게 생성된 자식 프로세스가 종료될 때까지 부모 프로세스가 대기하는 경우도 있고, 부모와 자식 프로세스가 동시에 실행되는 경우도 있다.
- UNIX의 fork()와 exec() 시스템 콜이 위 기능의 대표적인 예시이다.
- 프로세스가 종료될 때는, exit() 시스템 콜을 사용해 OS에게 작업의 종료를 알리게 된다. 종료된 프로세스가 점유하고 있던 자원들은 OS에 의해 할당 해제된다.
- 종료된 프로세스의 상태에 따라 wait()하고 있던 부모 프로세스에게 상태 값을 전달한다.
- 비정상적인 경우에는 부모 프로세스가 abort() 시스템 콜을 통해 자식 프로세스를 직접 종료 요청하기도 하는데, 자식 프로세스가 할당된 자원을 초과하거나, 더 이상 자식 프로세스의 작업이 지속될 필요가 없거나, 부모 프로세스가 종료되는데 OS 정책으로 자식 프로세스가 독립적으로 실행되는 것을 허용하지 않는 경우 등이 있다.
- 일반적으로는, OS의 cascading termination을 통해 특정 프로세스의 자식과 그 후손 프로세스들은 함께 종료되게 된다.
- 이게 정상적으로 동작하지 않는 경우, 좀비 프로세스(zombie, 자식 프로세스가 종료되었으나 부모 프로세스에서 wait() 처리를 하지 않은 경우, 커널이 자식에 대한 최소한의 상태 정보를 계속 유지한다), 고아 프로세스(orphan, 부모 프로세스가 wait() 없이 먼저 종료되어 버리는 경우) 등이 발생한다.
4. 프로세스 간 통신(Inter-Process Communication, IPC)
- 정보 공유, 계산 효율, 모듈화, 편의성 등의 이유로 프로세스 간 통신이 필요할 때가 있다.
- 이런 IPC는 보통 두 가지 모델이 있다. 협력 프로세스가 공유하는 메모리 영역을 이용하는 Shared Memory 모델과 협력 프로세스 간 커널을 통해 메시지를 교환해 통신하는 Message Passing 모델이다.
- Shared Memory
- 생산자 프로세스와 소비자 프로세스가 있다고 가정하고 공유 메모리 공간을 버퍼로 이용한다.
- 물론 unbounded-buffer가 이상적이겠지만, 일반적으로는 bounded-buffer를 사용하게 된다.
- OS의 도움을 받지 않아도 되지만 Synchronization 문제가 발생할 수 있다.
- 원형 큐 형태의 버퍼를 사용하고, 생산자가 사용하는 포인터와 소비자가 사용하는 포인터만으로 버퍼의 생산 및 소비 가능 여부를 결정한다면, 구조적으로 버퍼 사이즈-1까지만 사용할 수 있다.
- 물론 별도의 카운터 값을 통해 버퍼의 크기를 공유한다면 모든 버퍼를 온전하게 사용할 수 있으나, Race Condition 문제가 발생하면 데이터 일관성을 보장할 수 없다.
- Message Passing
- 커널을 통해 메시지를 주고 받는다.
- 이 때 프로세스 사이를 연결하는 communication link가 중요한데, 이 링크를 어떻게 구현할 것인지, 3개 이상의 프로세스 간 연결을 지원할 것인지, 링크의 가용량은 어떻게 할 것인지, 메시지의 크기는 고정되어 있는지 유동적인지 등 구현에 있어 여러가지 이슈를 신경써야 한다.
- 직접적/간접적
- 커널을 통해 직접적으로 다른 프로세스에 메시지를 전달하는 경우(직접적): 링크는 자동으로 설정되며 한 쌍의 프로세스 사이에 구성된다. 각 쌍 사이에는 정확히 하나의 링크가 존재하고, 일반적으로 양방향이다. 프로세스를 직접 명시해야 하는 하드 코딩에 가까워 바람직하지 않다.
- 커널의 mailbox를 이용하는 경우(간접적): 일종의 port 역할을 하는 추상적 객체를 만들어 사용한다. 두 프로세스는 공유 mailbox가 있는 경우에만 링크가 설정되고, 링크는 여러 프로세스 사이에서도 구성될 수 있다. 각 프로세스 쌍은 여러 링크를 공유할 수도 있다. 하지만 이 때도, receiver가 여럿일 때 메시지를 어떻게 처리할 것인지 등에 대한 정책을 미리 설정해야 한다.
- 동기/비동기
- 메시지의 전송과 수신이 block되는 경우(동기)
- 메시지의 전송과 수신이 non-block인 경우(비동기)
- receiver와 sender 모두 block 하는 형태로 동작하는 경우, 랑데부 통신이 된다.
- 버퍼 크기
- 0으로 설정: 랑데부 통신이 된다.
- Bounded Capacity: link 버퍼가 꽉차면 sender는 block된다.
- Unbounded Capacity: 이상적인 형태로, sender는 항상 기다릴 필요가 없다.
CPU 스케줄링 로직이나 Thread, Synchronization 문제 등은 아예 한 챕터로 다루는 내용이라 자세한 설명은 작성하지 않았습니다.
'CS 리마인드 > 운영체제' 카테고리의 다른 글
[운영체제] 5. CPU 스케줄링 (0) | 2024.12.09 |
---|---|
[운영체제] 4. 스레드 (0) | 2024.12.03 |
[운영체제] 2. OS 서비스 (1) | 2024.03.29 |
[운영체제] 1. 서론 (1) | 2024.03.22 |