프로세스
비공식적으로, 프로세스는 실행 중인 프로그램이다. 프로그램이 명령어 리스트를 저장한 파일(실행 파일)과 같은 수동적인 존재(passive entity)라면 프로세스는 프로그램 카운터 및 관련 자원을 가진 능동적인 존재(active entity)이다.
프로세스는 아래의 자원들을 가진다.
- 레지스터 (PC, IR 등)
- cpu time
- 메모리
- 파일
- I/O 디바이스
메모리 배치
프로세스의 메모리 배치는 일반적으로 여러 섹션으로 구분되며 아래의 섹션을 포함한다.
- 텍스트 섹션 - 실행 코드
- 데이터 섹션 - 전역 변수
- 힙 섹션 - 프로그램 실행 중 동적으로 할당되는 메모리
- 스택 섹션 - 함수를 호출 시 임시 데이터 저장 장소(함수 매개변수, 복귀 주소, 지역 변수)
텍스트와 데이터 섹션은 크기가 고정되어 있으며 스택과 힙 섹션은 서로의 방향으로 커진다. 아래는 C 프로그램의 메모리 배치이다. 위의 메모리 배치와 거의 비슷하지만 argc, agrv 매개변수가 저장되는 별도의 섹션이 있으며 데이터 섹션은 초기화된 데이터와 초기화되지 않은 데이터를 저장하는 서로 다른 섹션으로 나뉜다.
프로세스 상태
프로세스는 상태(state)를 가지고 있으며 실행되면서 상태가 변한다. 프로세스는 아래의 상태 중 하나에 있게 된다.
- new : 프로세스가 생성 중이다.
- running : 명령어들이 실행되고 있다.
- waiting : 프로세스가 어떤 이벤트(입출력 완료 또는 신호의 수신 같은)가 일어나기를 기다린다.
- ready : 프로세스가 프로세서에 할당되기를 기다린다.
- terminated : 프로세스의 실행이 종료되었다.
프로세스 제어 블록
OS는 프로세스를 PCB(Process Control Block)로 표현한다. 한 프로세스의 모든 정보를 저장한 구조체가 PCB이다.
프로세스 스케줄링
멀티 프로그래밍, 멀티 태스킹의 목표는 여러 프로세스를 동시에(concurrently) 실행해 cpu의 사용률을 높이는 것이다. 동시라는 말이 parallel이 아님에 주의하자. time sharing(시분할)을 통해 프로세스를 동시에 실행 한다. 즉 cpu 코어가 매우 빈번하게 작업(프로세스)을 전환하며 실행해 각 프로세스가 동시에 실행되는 것처럼 보이게 한다. 작업을 전환할 때 context switch가 발생한다.
cpu는 멀티 코어로 구성되어 있으므로 코어 개수만큼 프로세스를 병렬로(parallel) 실행하며 각 cpu 코어는 한번에 하나의 프로세스를 실행해 프로세스를 동시에(concurrently) 실행한다. 단일 코어 시스템에서는 한번에 하나의 프로세스를 실행할 수 있지만 멀티 코어 시스템에서는 한번에 코어 개수만큼 프로세스를 실행할 수 있다.
fork()와 같은 시스템 호출로 프로세스를 생성하면 프로세스는 new 상태가 된다. 생성된 프로세스가 실행될 준비를 마치면 준비 큐(ready queue)에 들어가 ready 상태가 되고 ready 상태의 프로세스에 cpu 코어가 할당되어 실행되면 running 상태가 된다. running 상태의 프로세스는 다시 준비 큐에 들어가 ready 상태가 되거나 대기 큐에 들어가 wait 상태가 되거나 종료되어 terminated 상태가 된다. 프로세스는 종료될 때까지 이 주기를 반복하며 종료 시점에 PCB 및 모든 자원이 반환된다.
위 그림과 같이 running 상태의 프로세스는 다음의 이벤트가 발생해 상태가 변한다.
- I/O 요청 발생 → I/O 대기 큐에서 대기, I/O 작업이 끝나면 준비 큐로 들어감
- 타임 슬라이스 만료 → 준비 큐로 들어감
- 자식 프로세스 생성 → 자식 프로세스를 만든 다음 자식의 종료를 기다리는 동안 자식 종료 대기 큐로 들어감
- 인터럽트 발생 → 인터럽트 대기 큐에서 대기, 인터럽트 처리가 완료되면 준비 큐로 들어감
준비 큐와 여러 대기 큐는 PCB의 연결리스트로 구현된다.
context switch
어떤 프로세스를 실행하다가 나중에 다시 그 프로세스를 실행하려면 어떻게 해야될까? 프로세스의 context(문맥)을 저장해 두었다가 다시 불러오면 다시 실행할 수 있다. 프로세스의 context는 PCB에 표현된다. context는 CPU 레지스터의 값, 프로세스 상태, 메모리 관리 정보 등을 포함한다. cpu 코어가 프로세스를 전환할 때 context switch(문맥 교환)가 발생한다. context switch란 이전의 프로세스 context를 보관하고 새로운 프로세스 context를 복구하는 작업을 말한다. context switch 동안 cpu 코어는 아무런 일도 하지 못하기에 context switch는 오버헤드이다.
프로세스 생성
OS는 프로세스 생성, 제거할 수 있는 서비스를 제공하며 시스템 콜로 이 서비스를 이용할 수 있다. 프로세스에서 여러 개의 새로운 프로세스를 생성할 수 있다. 이때 새로운 프로세스를 생성하는 프로세스를 부모(parent) 프로세스라 하며 새로운 프로세스는 자식(child) 프로세스라 한다. 자식 프로세스들도 각각 자식 프로세스들을 생성할 수 있으므로 프로세스 트리가 형성된다.
아래는 리눅스의 전형적인 프로세스 트리를 보여준다. pid가 1인 systemd 프로세스가 모든 사용자 프로세스의 루트 부모 프로세스 역할을 수행하고 시스템이 부트될 때 생성되는 첫 번째 사용자 프로세스이다. 시스템이 부팅되면 systemd 프로세스는 다양한 사용자 프로세스를 생성한다. systemd 프로세스가 로그인 하는 사용자 관리를 담당하는 logind 프로세스를 생성하고 logind 프로세스는 bash 셸 프로세스를 생성하고 bash 셸 프로세스는 ps와 vim 프로세스를 생성하는 것을 볼 수 있다.
프로세스가 새로운 프로세스를 생성할 때, 두 프로세스를 실행시키는 두 가지 가능성이 존재한다.
- 부모는 자식과 함께(concurrently) 실행을 계속한다.
- 부모는 일부 또는 모든 자식의 실행을 종료할 때까지 기다린다.
새로운 프로세스들의 주소 공간 측면에서 볼 때 두 가지 가능성이 존재한다.
- 자식 프로세스는 부모 프로세스의 복사본이다. (자식 프로세스는 부모와 똑같은 프로그램과 데이터를 가진다.)
- 자식 프로세스가 자신에게 적재될 새로운 프로그램을 가지고 있다.
위 가능성을 UNIX OS에서 확인해보자. fork() 시스템 콜을 호출하면 자식 프로세스가 생성되고 자식 프로세스는 부모 프로세스의 복사본이 된다. 즉 텍스트 섹션만 공유할 뿐 나머지 섹션(스택, 힙, 데이터)은 복사해 별개의 메모리 영역을 가진다. 그 후 부모와 자식은 fork()의 다음 명령문부터 함께 실행을 계속한다. fork()는 자식 프로세스에게는 0을 부모 프로세스에게는 0이 아닌 pid(자식 프로세스의 pid)를 리턴한다.
그 후 자식이 실행되는 동안 부모가 아무것도 하지 않을 경우 부모 프로세스에서 wait() 시스템 콜을 호출한다. wait() 시스템 콜이 호출되면 자식 종료 대기 큐로 이동하고 자식 프로세스가 종료될 때까지 대기한다. 보통 프로세스를 새로 만드는 목적은 자신의 복제 프로세스를 만드는 것이 아닌 다른 프로그램을 가진 프로세스를 만드는 것에 있다. 자식 프로세스에서 exec() 시스템 콜을 호출하면 새 프로그램을 메모리에 적재하고 자신의 원래 메모리는 반환한다. 이후 자식 프로세스는 로드된 프로그램을 수행한다.
프로세스 종료
프로세스는 exit() 시스템 콜로 자신의 종료를 OS에게 요청한다. 보통 소스 코드 마지막에 위치한다. 프로세스가 종료되면 프로세스의 모든 자원이 반환된다. 또는 부모 프로세스에서 자식 프로세스를 임의로 중단(kill)시킬 수도 있다. 몇몇 시스템에서는 부모 프로세스가 종료한 이후에 자식 프로세스가 존재 할 수 없기에 이러한 시스템의 OS는 부모 프로세스가 종료되면 자식 프로세스도 같이 종료시켜 준다.
스레드
예전의 프로세스는 단일 실행 스레드(single thread of execution)을 가졌다면 지금의 프로세스는 여러 개의 스레드를 가져 여러 개의 실행 흐름을 가지게 되었다. 예전에는 프로세스가 여러 개의 실행 흐름을 가지기 위해 새 프로세스를 생성했다. 하지만 프로세스를 생성하는 건 많은 시간과 자원이 요구되는 작업이며 같은 일을 하는 프로세스를 여러 개 두는건 매우 비효율적이다. 때문에 경량화된(light-weight) 프로세스인 스레드가 등장했다. 스레드는 원래 실타래, 프로그램 실행 흐름을 의미하는 용어였다.
멀티스레딩 시스템에서 cpu를 점유하는 단위는 프로세스가 아닌 스레드가 된다. 즉 cpu 코어 개수만큼 스레드를 병렬로(parallel) 실행하며 각 cpu 코어는 매우 빈번하게 작업(스레드)을 전환하며 실행해 스레드를 동시에(concurrently) 실행한다.
'Computer Science > OS' 카테고리의 다른 글
[OS - 5] 스레드 (0) | 2022.11.29 |
---|---|
[OS - 4] IPC (0) | 2022.11.21 |
[OS - 2] 운영체제 개요 (2) (0) | 2022.11.09 |
[OS - 1] 운영체제 개요 (1) (0) | 2022.11.02 |
컴파일 VS 인터프릿 (0) | 2022.08.11 |