프로세스(Process)는 운영체제의 핵심 개념 중 하나이다. 프로세스는 실행 중인 프로그램을 의미하며, 프로그램 자체는 디스크에 저장된 명령어와 데이터의 집합이다. 운영체제는 이 명령어와 데이터를 실행하여 프로그램을 작동시킨다.
사용자는 여러 프로그램을 동시에 실행하기를 원한다. 예를 들어, 웹 브라우저, 이메일 ,게임, 음악 플레이어 등을 동시에 실행하는 것이다. 운영체제는 실제로 한정된 CPU를 가지고 있음에도 불구하고, 여러 개의 프로세스가 동시에 실행되는 것처럼 만드는 기술, 즉 CPU 가상화를 통해 이를 가능케 한다.
이러한 환상을 만들기 위해, 운영체제는 시분할(time sharing) 방식을 사용하여 한 프로세스를 잠시 실행한 후 다른 프로세스로 전환하는 작업을 반복한다. 이 과정을 통해, 여러 프로세스가 동시에 실행되는 것처럼 보이게 한다. 하지만 이 방식은 프로세스마다 성능이 다소 저하될 수 있다.
운영체제가 이런 작업을 잘 처리하기 위해서는 저수준의 기술적 방법, 즉 메커니즘(mechanism)과, 어떤 프로세스를 언제 실행시킬지 결정하는 정책(policy)이 필요하다. 메커니즘은 운영체제가 필요한 기능을 구현하는 방법을 말하며, 정책은 운영체제가 어떤 결정을 내리기 위한 규칙이나 알고리즘이다. 예를 들어, 여러 프로그램이 실행 가능할 때 어느 것을 먼저 실행할지 결정하는 스케줄링 정책(scheduling policy)이 이에 해당한다.
이처럼 운영체제는 복잡한 작업을 처리하면서도 사용자에게 편리하고 효율적인 환경을 제공하기 위해 설계되었다.
시분할과 공간분할
시분할은 운영체제가 자원을 효율적으로 공유하는 중요한 방법 중 하나이다. 여러 사용자나 프로그램이 동시에 자원을 사용하는 것처럼 보이게 하기 위해, 운영체제는 짧은 시간 동안 한 사용자(또는 프로그램)에게 자원을 할당하고, 그 다음 사용자에게 순차적으로 할당하는 방식으로 자원(CPU나 네트워크 링크 등)을 관리한다. 이와 반대되는 개념으로 공간 분할이 있다. 공간 분할(space sharing)에서는 자원의 '공간'을 여러 사용자나 프로그램에게 나누어 준다. 디스크가 좋은 예인데, 디스크의 경우 특정 공간(블록)이 한 파일에 할당되면, 그 파일이 삭제되기 전까지 다른 파일이 그 공간을 사용할 가능성이 낮다.
프로세스의 개념
운영체제는 실행 중인 프로그램을 프로세스라는 개념을 제공한다. 프로세스를 간단하게 표현하면, 실행 중에 접근하거나 영향을 받은 자원의 목록이라고 할 수 있다.
프로세스를 이해하려면 하드웨어 상태(machine state)를 이해해야 한다. 프로그램은 실행 중에 하드웨어 상태를 읽거나 변경한다. 이떄 가장 중요한 하드웨어 구성 요소는 메모리이다. 메모리는 명령어와 데이터를 저장한다. 프로세스가 접근할 수 있는 메모리는 프로세스의 구성 요소이다.
레지스터도 프로세스의 하드웨어 상태를 구성하는 중요한 요소이다. 많은 명령어가 레지스터를 직접 읽거나 변경한다. 따라서 프로세스를 실행하려면 레지스터도 필요하다.
프로세스의 하드웨어 상태를 구성하는 레지스터 중 특별한 레지스터가 있다. 프로그램 카운터(PC)는 실행 중인 명령어를 알려준다. PC는 명령어 포인터(IP)라고도 불린다. 스택 포인터(SP)와 프레임 포인터(FP)는 함수의 변수와 리턴 주소를 저장하는 스택을 관리하는 데 사용된다.
핵심 내용:
- 프로세스는 실행 중인 프로그램의 개념.
- 프로세스는 메모리, 레지스터, 파일 등의 자원을 사용.
- 프로그램 카운터는 실행 중인 명령어를 알려준다.
- 스택 포인터와 프레임 포인터는 스택을 관리하는 데 사용.
- 프로세스는 영구 저장장치에 접근할 수 있다.
정책과 구현의 분리
- 핵심 개념:
- 고수준 정책: 운영체제가 무엇을 해야 하는지 결정하는 규칙
- 저수준 기법: 운영체제가 어떻게 작업을 수행하는지 구현하는 방법
- 설계 패러다임:
- 고수준 정책과 저수준 기법을 분리
- 이점:
- 정책 변경 용이: 정책 변경 시 기법 변경 필요 없음
- 모듈성 향상: 코드 재사용 및 유지 관리 용이
- 확장성 향상: 새로운 정책 및 기법 추가 용이
- 예시:
- 정책: 어느 프로세스를 실행할 것인가?
- 기법: 스케줄링 알고리즘 (예: 라운드 로빈, 우선 순위 기반)
- 결론:
고수준 정책과 저수준 기법 분리는 운영체제 설계의 중요한 패러다임이며, 이는 모듈성, 확장성, 유지 관리 용이성을 향상시킨다.
프로세스 API
운영체제가 반드시 API로 제공해야 하는 몇몇 기본 기능은 다음과 같다. 이 API 등은 형태는 다르지만 모든 현대 운영체제에서 제공된다.
- 생성(Create): 쉘에 명령어를 입력하거나, 응용 프로그램의 아이콘을 더블 클릭하여 프로그램을 실행시키면, 운영체제는 새로운 프로세스를 생성한다.
- 제거(Destroy): 많은 프로세스는 실행되고 할 일을 다하면 스스로 종료하지만 스스로 종료하지 않는 경우가 있기 때문에 운영체제는 불필요한 프로세스를 종료시키는 기능을 제공한다.
- 대기(Wait): 특정 프로세스의 작업이 끝날 때까지 기다려야 할 때가 있어 여러 종류의 대기 인터페이스가 제공된다. 이는 다른 프로세스와의 작업을 동기화하거나, 특정 조건이 충족될 때까지 기다리는 데 사용될 수 있다.
- 각종 제어(Miscellaneous Control): 프로세스를 일시정지 하거나 다시 시작하는 등의 여러 가지 제어 기능들이 제공된다. 이는 프로세스의 동작을 조절하거나 문제를 해결하는데 도움이 된다.
- 상태(Status): 프로세스의 현재 상태 정보를 얻어내는 인터페이스도 제공된다. 이는 프로세스가 얼마 동안 실행되었는지 또는 프로세스가 어떤 상태에 있는지 등이 포함된다.
프로세스 생성 : 좀 더 자세하게
프로그램을 실행하기 위해 운영체제가 가장 먼저 하는 일은 프로그램의 코드와 정적 데이터(예를 들어. 초기값을 가지는 변수)를 메모리 즉, 프로세스의 주소 공간으로 불러오는 것이다. 이를 '로딩(loading)' 이라고 한다. 프로그램은 디스크나 요즘에는 SSD에 실행 파일 형태로 저장되어 있는데, 운영체제는 이 파일에서 필요한 부분을 읽어 메모리에 적재한다.
초기 운영체제는 프로그램 실행 전에 코드와 데이터 전체를 메모리에 로딩했지만, 최근의 운영체제는 이를 지연시켜 필요할 때만 메모리에 적재하는 방식을 사용한다. 이를 정확히 이해하려면 페이징(paging)과 스와핑(swapping)에 대한 지식이 필요하다.
- 스택(stack) 메모리 할당: C 프로그램은 지역 변수, 함수 인자, 리턴 주소 등을 저장하기 위해 스택을 사용한다. 운영체제는 이를 위한 메모리를 할당하고, main() 함수의 인자(argc, argv)로 스택을 초기화한다.
- 힙(heap) 메모리 할당: C 프로그램에서 힙은 동적으로 할당되는 메모리 공간. malloc()으로 메모리를 요청하고 free()로 반환하는 식으로 사용되며, 주로 가변 크기의 자료구조(연결 리스트, 해시 테이블, 트리 등)를 위해 쓰인다.
- 입출력 초기화: 운영체제는 프로그램의 입출력을 위한 초기화 작업도 수행한다. 예를 들어 유닉스 시스템에서는 각 프로세스에 표준 입력(STDIN), 표준 출력(STDOUT), 표준 에러(STDERR)에 대한 파일 디스크립터를 제공
이러한 준비 과정이 모두 끝나면, 운영체제는 프로그램의 시작점(entry point), 즉 main() 함수로 제어를 이동시켜 프로그램 실행을 시작한다. 이를 통해 CPU의 제어권이 새로 생성된 프로세스로 넘어가고, 프로그램이 실행되기 시작하는 것이다.
프로세스 상태
프로세스 상태(state)를 단순화 하면 다음 세 상태 중 하나에 존재할 수 있다.
- 실행 (Running): 실행 상태에서 프로세스는 프로세서에서 실행 중이다. 즉, 프로세스는 명령어를 실행하고 있다.
- 준비 (Ready): 준비 상태에서 프로세스는 실행할 준비가 되어 있지만 운영체제가 다른 프로세스를 실행하고 있는 등의 이유로 대기 중이다.
- 대기 (Blocked): 프로세스가 다른 사건을 기다리는 동안 프로세스의 수행을 중단시키는 연산이다. 흔한 예 : 프로세스가 디스크에 대한 입출력 요청을 하였을 때 프로세스는 입출력이 완료될 때까지 대기 상태가 되고, 다른 프로세스가 실행 상태로 될 수 있다.
프로세스는 '준비' 상태와 '실행' 상태를 운영제제의 정책에 따라 이동한다. 프로세스는 운영체제의 스케줄링 정책에 따라 스케줄이 되면 '준비' 상태에서 '실행' 상태로 전이한다.
‘실행’ 상태에서 ‘준비’ 상태로의 전이는 프로세스가 나중에 다시 스케줄 될 수 있는 상태가 되었다는 것을 의미한다. 프로세스가 입출력 요청 등의 이유로 대기 상태가 되면 요청 완료 등의 이벤트가 발생할 때까지 대기 상태로 유지된다. 이벤트가 발생하면 프로세스는 다시 준비 상태로 전이되고 운영체제의 결정에 따라 바로 다시 실행될 수 있다.
두 개의 프로세스가 어떻게 전이될 수 있는지를 한번 알아보자. 먼저 실행 중인 두 프로세스가 있다고 했을 때, 각 프로세스가 오직 CPU만 사용하고 입출력을 행하지 않을 때의 프로세스 상태 추이를 나타내면 아래와 같다.
첫 번째 프로세스가 어느 정도 실행한 후에 입출력을 요청한다. 그 순간 프로세스는 대기 상태가 되고 다른 프로세스에게 실행 기회를 준다.
- Process0은 입출력을 요청하고 요청한 작업이 완료되기를 기다린다
- 프로세스는 디스크를 읽거나 네트워크로부터 패킷을 기다릴 때 대기 상태로 전이한다.
- 운영체제는 Process0이 CPU를 사용하지 않는다는 것을 감지하고, Process1을 실행시킨다.
- Process1이 실행되는 동안 입출력이 완료되고 Process0은 준비 상태로 다시 전이된다.
- Process1은 종료되고, Process0이 실행되어 종료된다.
이처럼 간단한 예에서조차 운영체제가 내려햐 할 결정은 매우 많다. 우선 시스템이 Process0이 입출력을 요청할 때 Process1의 실행 여부를 결정해야 한다. Process1을 실행키로 한 결정은 CPU를 계속 동작시키므로 자원 이용률을 높인다. 또한, 시스템은 Process0이 요청한 입출력이 완료되었을 때, Process0을 바로 실행하지 않고 실행 중이던 Process1을 계속 실행하였다. 이게 좋은 결정이었는지는 확실하지 않다.
자료구조
운영체제 역시 일종의 프로그램이기에, 다른 프로그램들처럼 여러 정보를 저장하고 관리하기 위한 자료구조를 갖고 있다.
예를 들어,운영체제는 프로세스의 상태를 파악하기 위해 프로세스 리스트라는 자료구조를 유지하는데, 이 리스트에는 실행 대기 중인 프로세스들의 정보가 담겨 있다. 또한 현재 실행 중인 프로세스가 무엇인지 알기 위한 별도의 자료구조도 관리한다.
아울러 운영체제는 입출력 작업 등으로 인해 대기(blocked) 상태에 있는 프로세스도 추적해야 한다. 해당 입출력이 완료되면, 운영체제는 이 정보를 토대로 대기 중이던 프로세스를 깨워 실행 가능한 상태(ready)로 만들어 줄 수 있어야 한다.
프로세스가 실행을 멈출 때(예: 인터럽트 등으로 인해), 운영체제는 레지스터 문맥(register context)이라는 자료구조에 해당 프로세스의 레지스터 값들을 저장한다. 나중에 이 프로세스를 다시 실행할 때는 저장된 레지스터 값들을 복원함으로써 중단된 지점부터 실행을 재개할 수 있게 된다. 이를 문맥 교환(context switch)이라고 하며, 추후 더 자세히 다룬다.
프로세스의 상태로는 실행(running), 준비(ready), 대기(blocked) 외에도 몇 가지가 더 있다.
어떤 시스템에서는 프로세스가 생성 중일 때의 초기(initial) 상태를 별도로 둔다. 또 프로세스가 종료되었지만 메모리 상에 아직 남아있는 종료(final) 상태가 있는데, 유닉스 계열 시스템에서는 이를 좀비(zombie) 상태라고 부른다.
종료 상태는 해당 프로세스가 성공적으로 실행을 마쳤는지 등을 다른 프로세스(주로 부모 프로세스)가 검사하는 데 활용된다. 부모 프로세스는 자식 프로세스의 종료를 기다리는 시스템 콜(예: wait())을 호출하는데, 이를 통해 운영체제는 종료된 프로세스의 자원을 정리할 수 있게 된다.
'Deep Dive > OS' 카테고리의 다른 글
[OSTEP] 스터디 2주차 - 가상화의 세계 part.2 (0) | 2025.09.08 |
---|---|
[OSTEP] 스터디 1주차 - 아주 쉬운 세가지 이야기 (1) | 2025.09.01 |