본문 바로가기

[운영체제] 04-1. Process Concepts

출처 서울대학교 홍성수 교수님, [K-MOOC] 운영체제의 기초, 4주차 Processes and Threads

 

1. 운영체제가 멀티 태스킹을 지원할 때 발생하는 두 가지 문제

OS의 가장 중요한 목적 중 하나는 멀티 태스킹 multi-tasking이다. OS를 사용하는 이유는 멀티 태스킹을 지원하기 위한 것이라고 해도 과언이 아니다.

멀티 태스킹이란 동시에 여러 개의 작업을 병렬적으로 수행하는 것이다.

 

(1) 스케줄링 scheduling

멀티 태스킹을 지원한다는 것은 하드웨어 자원들을 여러 job들에게 잘 배분한다는 뜻이다.

예를 들어 single processor system이 있고 지금 수행하고자 하는 job이 10개가 있다고 해보자. 어느 한 시점에는 하나의 job에게만 CPU를 줘야 한다. 그럼 나머지 9개의 job은 언제 CPU의 배분을 받을 수 있을까? 이러한 CPU의 배분은 효율적이어야 되고 공평해야 되는데, 이 스케줄링 scheduling 문제는 운영체제가 멀티 태스킹을 지원하기 때문에 발생하는 것이다. 싱글 태스킹 single-tasking을 한다면 이런 스케줄링 문제가 없을 것이다.

 

(2) 동기화 synchronization

task 또는 job들이 동시에 수행이 되고 있고 이들이 공유된 데이터에 접근한다면 여기에는 동기화 synchronization 문제라는 것이 발생한다. job들이 수행되는 과정에서 공유된 데이터에 잘못된 값이 들어가지 못하도록 OS가 적절한 조치를 해줘야 한다. 이를 두고 동기화 선점 synchronization preemptive를 제공한다고 이야기한다.

2.  프로세스

운영체제에서 가장 중요한 개념은 바로 프로세스 process이다.

프로그램과 프로세스


프로그램

앱을 다운받았다고 했을 때, 다운받아진 개체 entity는 수행 파일과 수행 파일을 배포/설치 deploy하는 데 필요한 메타 데이터이다. 이때 메타 데이터를 제외한 수행 파일이 프로그램 program이다. 수행 파일은 기기의 영구 저장 장치 permanent storage에 다운로드된다.
- 영구 저장 장치에 있는 실행 파일 executable file은 active하게 일을 할 수 있는 존재가 아니다. 디스크와 같은 저장 장치에 수동적으로 존재하는 sit there 데이터, binary bit sequence(stream)이다.

프로세스

프로그램을 수행하게 만들면 그 프로그램은 수행 중인, active한 프로그램이 된다. 이 active한 프로그램을 프로세스 process라고 한다. 프로세스는 시간에 따라서 일들을 처리한다.
- 수행 중인 프로그램은 스토리지에 수동적으로 존재하는 것이 아니라 메모리라는 자원을, CPU라는 자원을, 그리고 입출력 장치 I/O devie라는 자원을 사용한다.

 

프로세스의 정의

프로세스란 수행 중인 프로그램 Program in execution이다.

다시 말하면 특정 프로세스 상태의 컨텍스트에 있는 실행 스트림이다.

 

프로세스의 필요성

프로세스라는 개념은 왜 도입됐을까?

프로세스라는 개념이 없다면 컴퓨터 시스템 안에서 일어나는 일들은 혼돈에 빠지게 된다.

컴퓨터 시스템은 내부적으로 볼 때는 동기 시스템 synchronous system이지만, I/O 디바이스들로부터 hardware interrupt를 비동기적인 asynchronous 시점에 받는다. 내부적으로 동기적으로 synchronous 돌아가는 시스템이 asynchronous event인 hardware interrupt를 받아서 처리를 해야 한다면 내부적으로 동기적으로 동작하는 것과 별도로 그 이벤트를 받아서 처리해 줄 수 있는 개체 entity가 있어야 한다. 또한 만약에 외부에서 발생한 일이 뭔가 인과관계에 의해서 발생했다면 그것을 인과관계에서 처리해줄 수 있는 개체 entity가 있어야 한다. 그러한 개체 entity가 프로세스이다.

 

process state

프로그램이 수행을 할 때 프로세스에 영향을 줄 수 있는, 프로세스에 의해 영향을 받을 수 있는 모든 것들이다.

프로그래머가 부여한 수많은 변수의 이름과 특정 시점에서 그 변수가 가지고 있는 값의 쌍 pair을 집합으로 만들면 수행 중인 프로그램의 스냅샷 snap shot이 된다. 이 스냅샷을 그 프로그램의 state라고 한다. 다시 말해서 그 프로그램이 수행되는 중 어느 한 시점에 메모리 소자가 기억하고 있는 모든 값들을 모아서 state라고 한다.

  • 예시: code, data values, open files, etc

OS가 프로세스를 구현해서 커널 내에서 관리하려면 process state를 구체적인 자료 구조로 매핑하고, 실제로 그 자료 구조를 구현하여 계속 유지 관리해야 한다. 그렇기 때문에 process state는 굉장히 중요한 개념이다. 운영체제를 개발할 때 가장 먼저 해야 할 일은 process의 state를 구현하고 자료 구조로 표현하는 것이 되겠다.

 

context

context는 a collection of process states라고 이해해도 된다. 이 강의에서는 process state와 context를 동일한 의미로 사용하겠다.

process state는 그 하부를 들여다 봤을 때 3개의 context로 구성된다.

 

(1) memory context

  • 프로세스가 수행을 하기 위해서 메인 메모리에 꼭 가지고 있어야 하는 정보
  • OS는 프로세스에게 메모리 영역을 할당할 때 segment라고 하는 기본 단위를 사용한다.
  • Code segment(instruction), data segment(global variable), stack segment(local variable), heap(dynamic memory allocation)

(2) hardware context

  • 작은 메모리 소자들을 통틀어서 hardware context라고 한다.
  • CPU registeres, I/O registers

(3) system context

  • 위 두 개 외에 운영체제가 관리하는 여러 자료 구조
  • 커널이 특정 프로세스를 유지하기 위해 만들어서 관리하는 자료 구조
  • 프로세스가 할당 받아서 사용하는 리소스, 스케줄링 정보들, process id들
  • Process table, open file table, page table

멀티 프로그래밍과 멀티 프로세싱

멀티 프로그래밍이라는 단어는 관심 있는 리소스가 메모리이다.

멀티 프로세싱이라는 단어는 관심 있는 리소스가 CPU이다.

강의 슬라이드에서는 멀티 태스킹이라는 용어 대신에 멀티 프로세싱이라는 용어를 사용하고 있는데 멀티 태스킹이라는 용어를 더 일상적으로 사용하니 그 용어를 사용하겠다.

 

멀티 프로그래밍, 멀티 프로세싱 등을 4가지 조합으로 만들어보겠다.

 

Single-Tasking + Uni-Programming

초창기의 personal computer들은 메모리에 하나의 job만 올려놓았다.

CPU는 하나의 job을 시작하면 끝을 낼 때까지 그 job만 수행시켰다.

 

Single-Tasking + Multi-Programming

이것은 정의상 모순인 조합이다.

single-tasking은 한 번에 하나의 job만 수행시키고 다른 것은 수행시키지 않으니까 한 개 이상의 active한 job을 만들 수가 없다.

그런데 multi-programming이 되려면 메인 메모리에 active한 job들이 두 개 이상 올라가 있어야 된다. 모순이 된다.

 

Multi-Tasking + Uni-Programming

부자연스럽지만 불가능하지는 않다.

메인 메모리에는 하나의 process 올라가 있고 그 프로세스가 메모리를 차지하는 동안 CPU도 같이 쓴다.

그런데 프로세스가 hardware interrupt를 만나면 CPU를 내주게 되는데 CPU를 내줄 때 메인 메모리까지 같이 내준다. 어떤 프로세스가 메모리를 내준다는 것은 디스크 스토리지에 있는 곳으로 대피해 간다는 의미이다. 그래서 아예 컴퓨터 시스템에 있는 메모리와 CPU를 다 양보하고 나가는 그런 형국이 된다. 그러면 스토리지에 있던 다른 프로세스가 메인 메모리로 들어오고 CPU를 쓰게 된다.

이렇게 되면 성능이 나빠진다. 프로세스와 프로세스가 교체될 때 disk I/O가 유발되기 때문이다. 그렇지만 과거의 time sharing os가 등장할 때 이러한 구성을 사용했었다. 이때는 메인 메모리가 48kb, 64kb 이렇게밖에 되지 않았다. 그래서 여러 개의 프로세스를 메인 메모리에 올릴 수가 없었다. 그래서 한 프로세스를 메인 메모리에 적재해서 쓰게 하고 그 프로세스가 CPU를 양보해야 될 때는 디스크로 내보내는 연산을 했는데 이러한 연산을 스와핑 swapping이라고 했다. 조금 더 정확하게 이야기하면 swap out이라고 한다. swap out은 하드웨어 자원의 제약 때문에 사용했던 것이다. 그러나 스와핑이라는 것은 현대 운영체제에서 오히려 성능을 높여줄 수 있는 메커니즘으로 여전히 사용된다.

 

Multi-Tasking + Multi-Programming

현대의 OS가 이 조합을 사용한다. 메인 메모리에 active한, 아직 수행을 끝내지 않은 프로세스들이 여러 개 올라와 있다.

CPU는 프로세스들끼리 서로 주고받으면서 multiplexing을 하게 된다. 그러니까 multi-tasking과 multi-programming이 잘 일어나는 것이다.

 

Process Control Block

멀티 프로세싱을 할 때 운영체제는 프로세스들을 계속 추적해야 한다.

각각의 프로세스에 대하여, PCB는 다음과 같은 정보를 들고 있다.

  • Execution status (Saved registers, etc.)
  • Scheduling information (priority)
  • Accounting and other misc. information (open files)

과거에 아주 초창기 time sharing OS 시절에는 프로세스가 생성이 될 때마다 PCB를 할당하기 위해서 array라는 단순한 자료 구조를 사용했다. array는 사이즈가 고정된 자료 구조이다. 그러다 보니 프로세스가 많아지면 결국 array의 사이즈를 초과하게 된다. 프로세스가 새로 생성이 될 때 process control block을 할당해주지 못하는 그런 문제가 생길 수 있다. 그래서 그 이후에는 process control block을 관리하기 위해서 array를 사용하지는 않고 linked list처럼 동적으로 할당 가능한 그런 자료 구조를 사용한다. 그렇지만 우리가 개념적으로 시스템에 존재하는 프로세스들의 자료 구조, PCB들을 쭉 sequential한 자료 구조로 생각하고, 그걸 array라고 생각할 수 있다.

process id는 해당 프로세스에 pcb에 대한 인덱스라고 생각할 수 있다. 시스템 와이드하게 유니크한 아이디를 부여할 수 있다. 그런데 이것은 오래된 제한적인 os의 모습이고 오늘날에는 다양한 자료 구조를 사용한다.

 

Process State Transition

지금까지는 정적인, 자료 구조의 관점에서 프로세스를 살펴봤다. 이제는 프로세스의 라이프 사이클을 살펴보겠다.

상태 변화 state transition 다이어그램이 있다. 이때의 state는 process의 state에서의 state가 아니다. 프로세스가 실행되면 프로세스는 상태를 바꾼다. process state는 그 프로세스가 메모리 소자에서 어떤 값을 가지는가에 중점을 두는 것이고 여기에서 state는 프로세스가 어떤 상태에 놓여 있는가를 얘기하는 것이다.

 

New

프로세스가 생성됨

 

Ready

프로세스가 CPU를 배정받기를 기다림

  • 여러 개의 프로세스가 있을 수 있음. 일종의 waiting queue이다. 리눅스에서는 run queue라는 이름을 쓰고 있다. run queue/ready list에 프로세스를 넣는다고 하면 어떤 자료구조를 넣으면 될까? PCB 자료구조를 ready queue에 넣는다고 생각하면 된다.
  • Dispatched
    • Ready 상태에서 Running 상태가 되는 것이다. CPU를 배정받는 것이다.

Running

instruction이 실행됨

  • 모든 자원과 함께 CPU도 배정받음
  • CPU가 한 개이면 running에는 instruction이 1개만 올 수 있음
  • Waiting for I/O or Event
    • Running 상태에서 Waiting 상태가 되는 것이다.
    • 어떤 프로세스가 running을 하고 있는데 이 프로세스가 갑자기 synchronous I/O를 하게 됐다. DMA를 하게 되었다. 큰 disk block을 write해달라고 했다. 이 프로세스는 CPU를 스스로 양보하게 된다. 양보한 프로세스가 running state를 떠나고 새로운 state로 가게 되는데 그걸 우리가 waiting state라고 이야기한다.
  • Interrupted
    • Running 상태에서 Ready 상태가 되는 것이다.
    • 한 프로세스가 CPU를 점유했을 때 간섭 없이 내버려두면 너무 오래 CPU를 사용하게 된다. 그러면 ready 상태에 있는 다른 프로세스들이 CPU들을 못 쓴다는 문제가 있다. 그러면 os가 타이머를 설정한다. 타이머가 expire할 때까지 CPU를 쓰고 있다면 타이머로부터 interrupt를 받게 된다. 그래서 OS에 핸들러가 뜨게 되고 os의 핸들러가 CPU를 너무 오래 사용하는 프로세스를 쫓아낸다. 근데 이 프로세스는 무슨 waiting 때문에 쫓아내는 프로세스가 아니다. 프로세스는 돌 수 있는데 CPU 자원을 양보해야만 했기 때문에 나가는 것이다. 이때 프로세스는 ready 상태로 가게 된다.

Waiting

프로세스가 새로운 이벤트가 발생하기를 기다림

  • waiting 상태에 프로세스는 몇 개가 있을 수 있는가? 여러 개가 있을 수 있다.
  • DMA에 의해서, 키보드 input을 기다림 등
  • 여러 개의 프로세스 PCB를 묶은 자료 구조가 필요하다. ready와는 다르다. 내가 왜 waiting하고 있는지 이유에 따라 분류가 된다.
    - DMA를 기다리는 큐, 다른 건 다른 큐. waiting reason별로 큐를 따로 만든다.
  • I/O Completion or Event Occurrence
    • Waiting 상태에서 Ready 상태가 되는 것이다.
    • DMA 컨트롤러가 부여받은 미션을 다 수행해서 DMA를 종료하면 interrupt mechanism을 이용해서 CPU에게 interrupt를 건다. CPU가 interrupt를 받으면 os에 핸들러가 뜨게 된다. os에 핸들러가 떠서 보면 나를 DMA 시켜준 프로세스가 지금 waiting queue에 지금 대기하고 있다는 걸 알게 된다. 걔가 다음에 수행할 수 있도록 해줘야 되겠다고 생각하며 waiting state에 있는 프로세스를 ready state로 보낸다.
    • 왜 waiting state에 있는 프로세스를 running state로 보내지 않고 ready 상태로 보낼까? 왜냐하면 지금 running 상태에 있는 프로세스가 waiting에서 깨어난 프로세스보다 더 중요한 프로세스일 수도 있다. 그러니까 이 프로세스를 반드시 ready 상태로 보내서 우선순위에 의한 스케줄링을 받도록 하고 그 프로세스가 자격이 되면 running으로 가게 되는 그런 절차를 밟아야 한다.

Terminated

프로세스가 실행을 끝냄

 

스케줄링에서 중요한 두 가지 개념

스케줄링 동작이란 프로세스를 한 state에서 다른 state로 옮겨주는 것이다.

어떤 프로세스가 running 상태에 있다가 synchronous I/O 때문에 자발적으로 CPU를 양보하는 waiting 상태로 가는 경우가 있고, 또 다른 경우는 running 상태에 있는 프로세스가 자기는 돌고 싶은데 시스템에서 강제로 CPU를 빼앗기게 되는 그런 transition이 있다.

 

non-preemptive scheduling

어떤 프로세스가 running을 하다가 자발적으로 CPU를 양보하는 것을 non-preemptive scheduling이라고 얘기한다. running에서 waiting 상태로 가는 것은 synchronous interrupt 또는 system call 같은 것으로 구현이 된다.

OS라고 하는 것은 PCB라고 하는 프로세스를 구현하는 자료 구조를 가지고 시스템 안에서 발생되는 여러 가지 이벤트에 따라서 그 PCB를 이 큐에서 저 큐로 옮겨주는 이런 연산을 하는 개체 entity다. 아주 기계적으로 해석하면 그렇다.

 

preemptive scheduling

그런데 running 상태에 있는 프로세스가 자기 의지와 무관하게 OS의 강제 명령에 의해서 CPU를 빼앗겨 가지고 ready 상태로 가는 것을 우리는 preemptive scheduling이라고 이야기한다.

preemptive scheduling을 하려면 반드시 OS의 개입이 필요하다. 왜냐하면 OS의 코드가 돌아서 나를 ready 상태로 옮겨줘야 하기 때문이다.

 

dual mode operation

dual mode operation에서 user code는 user mode에서 돌고 os system code는 kernel mode에서 돈다. 어떤 프로세스가 지금 수행하고 있다고 하면 마이크로 프로세스의 모드는 user mode가 된다. 그런데 그 프로세스가 CPU를 빼앗겨서 preemptive scheduling을 당해서 ready queue로 가려면 os의 코드 특히 scheduler code가 돌아야 한다. 그러려면 모드 체인지가 필요하다. 유저 모드에서 커널 모드로 모드 체인지가 일어나고 거기서 scheduler code가 돌아야 한다.

유저 모드에서 커널 모드로 모드 체인지가 일어나려면 어떤 Hardware mechanism이 필요할까? 바로 interrupt mechanism이 필요하다. running 상태에 있는 프로세스가 ready 상태로 넘어가려면 timmer interrupt 등이 개입이 되었다. 그래서 preemptive scheduling은 반드시 HW interrupt가 필요하다. 왜냐하면 asynchronous 비동기적이기 때문이다.