[운영체제] 프로세스 관리

2022. 8. 5. 15:55CS/운영체제(OS)

1. 프로세스란?

  • 프로세스 : 실행 중인 프로그램, 디스크에 실행파일 형태로 존재하던 프로그램이 메모리에 올라가 실행되기 시작하면 비로소 프로세스가 된다.
  • 프로세스의 문맥(context) : 프로세스가 현재 어떤 상태에서 수행되고 있는지 정확히 규명하기 위해 필요한 정보, 그 프로세스의 주소 공간(코드, 데이터, 스택)과 레지스터에 어떤 값이 있었는지, 시스템 콜을 통해 커널에서 수행한 일의 상태, 커널이 프로세스에 관해 관리하는 각종 정보 포함

① 하드웨어 문맥 : CPU의 수행 상태를 나타내는 것, 프로그램 카운터 값과 각종 레지스터에 저장하고 있는 값

② 프로세스의 주소 공간 : 코드, 데이터, 스택에 저장되고 있는 값

③ 커널상의 문맥 : OS는 프로그램이 프로세스가 되었을 때 프로세스를 관리하기 위한 자료구조를 관리한다. 여기에 해당되는 것이 PCB커널스택이다.


2. 프로세스의 상태

  • 프로세스의 상태는 실행(running), 준비(ready), 봉쇄(blocked, wait, sleep)의 세 가지로 구분할 수 있다.

  • Running : 프로세스가 CPU를 보유하고, 기계어 명령을 실행하고 있는 상태
  • Ready : 프로세스가 CPU만 보유하면 당장 명령을 실행할 수 있으나, CPU를 할당받지 못한 상태
  • Blocked, Waiting : CPU를 할당받더라도 당장 명령을 수행할 수 없는 상태
  • 컨텍스트 스위칭 : OS에서 실행시킬 프로세스를 변경하기 위해 원래 수행중이던 프로세스의 문맥을 저장하고 새로운 프로세스의 문맥을 세팅하는 과정
  • CPU 디스패치 : 준비 상태에 있는 프로세스 중 CPU를 할당받을 프로세스를 선택한 후 CPU의 제어권을 넘겨받는 과정

※ 디스크 입출력을 요청한 프로세스는??? (예시)

  1. 디스크 입출력을 기다리는 큐에 줄 서 있는다.
  2. 자기 차례가 되어 디스크 컨트롤러에서 서비스를 받고(마그네틱 매체에서 원하는 데이터를 로컬 버퍼로 읽어옴) 디스크 컨트롤러가 CPU에 입출력이 완료되었다는 인터럽트 발생시킴
  3. CPU는 다른 프로세스 명령을 수행하다가 인터럽트가 발생한 것을 확인하고 이에 맞는 루틴 수행
  4. 이 루틴이 실행되는 동안 CPU에서 수행되던 프로세스의 상태는 사용자모드 실행 상태에서 커널모드 실행 상태로 바뀐다.
  5. 입출력이 완료된 프로세스의 상태를 blocked에서 ready로 바꾸고 장치의 로컬 버퍼에 있는 데이터를 메모리로 이동시키는 일련의 업무를 수행한다.
  6. 이후에는 CPU 스케줄링 방법에 따라 어떤 프로세스에게 CPU를 이양할지 결정된다.

3. 프로세스 제어블록(PCB)

  • 프로세스 제어블록 : OS가 시스템 내의 프로세스들을 관리하기 위해 프로세스마다 유지하는 정보들을 담는 커널 내의 자료구조

※ PCB를 구성하는 요소

  • 프로세스의 상태 : CPU를 할당해도 되는 지 여부를 결정하기 위해 필요
  • 프로그램 카운터값 : 다음에 수행할 명령의 위치
  • CPU 레지스터값 : CPU 연산을 위해 현 시점에 레지스터에 어떤 값을 저장하고 있는지 나타냄
  • CPU 스케줄링 정보, 메모리 관리 정보 : CPU 스케줄링과 메모리 할당을 위해 필요한 정보
  • 자원 사용 정보 : 사용자에게 자원 사용 cost를 계산해 청구하는 용도
  • 입출력 상태 정보 : 프로세스의 입출력 관련 상태 정보

4. 문맥교환(Context Switching)

  • 문맥교환 : 하나의 사용자 프로세스로부터 다른 사용자 프로세스로 CPU 제어권이 이양되는 과정
  • 사용자 프로세스가 CPU를 할당받고 실행되던 도중에 타이머 인터럽트가 발생하면 CPU의 제어권은 운영체제로 넘어간다. 그러면 운영체제는 타이머 인터럽트 처리루틴으로 가서 수행 중이던 프로세스의 문맥을 저장하고 새롭게 실행시킬 프로세스에 CPU를 이양한다.
  • 이 때, 문맥이라고 함은 각 프로세스의 PCB에 저장된다.
  • 문맥교환에 소요되는 시간은 시스템 입장에서 볼 때 일종의 오버헤드라고 할 수 있다. 타이머에 CPU 할당 시간을 아주 작게 설정하면 프로세스 간 문맥교환이 너무 빈번하게 일어나 오버헤드가 상당히 커질 수 있고, 크게 설정하면 시분한 시스템의 의미가 퇴색되므로 적절한 CPU 할당시간을 정하는 것이 중요하다.

5. 프로세스를 스케줄링하기 위한 큐

  • OS는 준비 상태의 프로세스들을 줄 세우기 위해 준비 큐를 두고 준비 큐의 제일 앞의 프로세스에 제일 먼저 CPU를 할당한다. 준비 큐에 줄 세우는 방식은 CPU 스케줄링에 따른다.
  • CPU를 기다리는 큐 이외에도 특정 디바이스를 기다리는 큐도 존재한다. 디스크 컨트롤러는 디스크 입출력 큐에 줄 서 있는 순서대로 프로세스의 입출력 작업을 수행한다.
  • 하드웨어 자원 뿐 아니라 소프트웨어 자원에도 기다리는 큐는 존재한다. 공유 데이터에 대한 접근 권한같은 경우에는 임의의 프로세스가 공유 데이터에 접근하고 있을 때 다른 프로세스가 동일 데이터에 접근하게 되면 일관성이 깨어질 수 있으므로 접근 중인 프로세스가 다 사용하고 반납하기 전까지는 다른 프로세스는 기다려야 한다.
  • 작업 큐(job queue) : 준비 큐와 장치 큐 이외에 운영체제가 시스템 내의 모든 프로세스를 관리하기 위한 큐로 프로세스의 상태와 무관하게 모든 프로세스가 이 작업 큐에 속하게 된다. 작업 큐에 있다고 해서 모두 메모리를 가지는 것은 아님 (준비 큐의 프로세스는 준비 상태, 장치 큐의 프로세스는 봉쇄 상태)

준비 큐 및 다양한 장치 큐

  • 장치 큐는 각 자원마다 큐가 하나씩 존재하고 여기서 큐헤더는 큐의 가장 앞부분이다. 큐는 각 프로세스의 PCB를 연결 리스트 형태로 관리하고 포인터를 사용해 순서를 정한다.

6. 스케줄러

  • 스케줄러 : 어떤 프로세스에게 자원을 할당할지 결정하는 OS 커널의 코드

※ 장기 스케줄러(long term scheduler) 

  • 작업 스케줄러라고도 하며, 어떤 프로세스를 준비 큐에 넣을지 결정하는 역할
  • 준비 큐의 프로세스는 CPU 제어권만 얻으면 당장이라도 실행될 수 있는 집합이고 CPU에서 실행되기 위해서는 프로세스가 메모리를 보유해야 하기 때문에 장기 스케줄러는 프로세스에 메모리를 할당하는 문제에 관여한다.
  • 시작 상태의 프로세스에 메모리를 할당을 승인할지 여부를 결정
  • 하지만 현대의 시분할 시스템용 OS에서는 프로세스가 시작 상태가 되면 장기 스케줄러 없이 곧바로 그 프로세스에 메모리를 할당해 준비 큐에 넣어준다. (장기 스케줄러를 안씀)

※ 단기 스케줄러(short term scheduler)

  • CPU 스케줄러라고도 하며, 준비 큐에 있는 프로세스들 중 어떤 프로세스에게 CPU를 할당할 것인지 결정하는 역할
  • 시분할 시스템에서는 타이머 인터럽트가 발생하면 단기 스케줄러가 호출된다.

중기 스케줄러(mid term scheduler)

  • 너무 많은 프로세스에게 메모리를 할당해 시스템의 성능이 저하되는 것을 방지하기 위해 메모리에 적재된 프로세스의 수를 동적으로 조절하기 위해 추가된 스케줄러
  • 메모리에 적재된 프로세스가 너무 많을 경우 CPU가 현재 수행해야 할 프로세스의 주소 공간조차도 메모리에 올려놓기 어려운 상황이 발생하고, 이는 빈번한 디스크 입출력을 불러일으켜 심각한 성능 저하를 초래한다.
  • 중기 스케줄러는 메모리에 올라온 프로세스 중 일부의 메모리를 통째로 빼앗아 디스크의 스왑 영역에 저장해두는데, 이를 스왑 아웃(swap out)이라고 한다.
  • 스왑 아웃의 우선순위 1순위는 블락 상태의 프로세스(메모리를 할당해봤자 사용 못함)이고 그 이후에도 메모리가 모자란다면 타이머 인터럽트가 발생해 준비 큐로 이동하는 프로세스를 스왑 아웃시킨다. 이를 통해 당장 실행해야 할 프로세스에 추가로 메모리를 부여함으로써 효율적인 운용이 가능하다.

  • 프로세스 상태에 실행(running), 준비(ready), 봉쇄(blocked) 이외에도 중지(suspended) 상태가 추가된다. 중지 상태는 외부에서 재개시키지 않는 한 다시 활성화될 수 없으므로 메모리 자원이 당장 필요하지 않다.
  • 중지 상태에서도 중지준비(suspended ready) 상태와 중지봉쇄(suspended blocked) 상태로 분화할 수 있다.

7. 프로세스의 생성

  • 운영체제가 프로세스를 전부 만드는 것이 아닌 부모 프로세스와 자식 프로세스가 존재하고 부모 프로세스는 자신이 생성한 모든 후손 프로세스들을 연쇄적으로 종료시킨 후에야 종료될 수 있다.
  • 프로세스는 자원을 필요로 함 : 운영체제로부터 받을 수 있고, 부모와 공유할 수 있다.
  • 수행 : 부모와 자식이 공존하며 수행되는 모델이 있고, 자식이 종료(terminate)될 때까지 부모가 기다리는(wait) 모델이 있음.

※ 프로세스의 생성 절차

  • 유닉스에서는 fork() 시스템 콜을 통해 새로운 프로세스 생성 가능하다. fork() 시스템 콜은 자식 프로세스를 생성할 때 부모 프로세스의 내용을 그대로 복제 생성(프로세스 id를 제외한 모든 정보, 운영체제 커널 내의 정보와 주소 공간의 정보)하게 된다.
  • 부모와 자식 프로세스는 주소 공간은 다르나 내용은 같음. fork()를 통해 생성된 자식 프로세스는 exec() 시스템 콜을 통해 새로운 프로그램으로 주소 공간을 덮어씌울 수 있음

※ 프로세스의 종료

  • 프로세스의 종료는 두 가지로 나눌 수 있는데, 자발적 종료와 비자발적 종료이다.

▷ 자발적 종료

  • 프로세스가 명령을 모두 수행한 후 exit()이라는 시스템 콜을 만나 OS에게 자신이 종료됨을 알릴 수 있다.
  • 종료를 통보받은 OS는 프로세스로부터 자원을 회수하고 시스템 내에서 이 프로세스를 정리한다.
  • exit() 함수는 프로그램 개발자가 명시적으로 호출하지 않아도 컴파일러에서 프로그램이 종료되는 시점에 자동으로 삽입한다.

▷ 비자발적 종료

  • 부모 프로세스가 자식 프로세스의 수행을 abort() 함수를 통해 강제로 종료시킨다.
  • 강제 종료가 발생하는 케이스는 다음과 같다.

  ①  자식 프로세스가 할당 자원의 한계치를 넘어서는 많은 양의 자원을 요구할 때

  ②  자식 프로세스에 할당된 작업이 더 이상 필요치 않을 때

  ③  부모 프로세스가 종료될 때

  • 사용자 계정을 서버 컴퓨터에 접속해 수행시킨 프로그램을 로그아웃 후에도 계속 수행시켜야 하는 경우가 있을 수 있음 → 로그인 창 아래에 생성된 모든 자식 프로세스들이 종료된다. → 이럴 때는 해당 프로세스를 로그아웃 후에도 존재하는 시스템 프로세스의 자식으로 이양시키는 절차 필요

※ 프로세스가 자식 프로세스를 생성하는 과정

  • OS는 자식 프로세스의 생성을 위해 fork() 시스템 콜 제공, 이 때 CPU 제어권이 커널로 넘어가고, 커널은 fork()를 호출한 프로세스를 복제해 자식 프로세스를 생성한다.
  • 이 때 생성된 자식 프로세스는 부모 프로세스의 주소 공간을 비롯한 프로그램 카운터 등 레지스터 상태, PCB 및 커널 스택 등 모든 문맥을 그대로 복제한다. (프로세스 식별자(ID) 는 다름)
  • 복제된 프로세스를 구별할 수 있는 한 가지는 fork() 함수의 결과값이 원본은 양수이고, 복제본은 0이라는 것이다.
  • 이 자식 프로세스에 독자적인 프로그램을 수행시킬 수 있는 exec() 시스템 콜을 유닉스에서 지원한다. exec() 시스템 콜은 프로세스가 지금까지의 상태를 잊어버리고 그 주소 공간을 완전히 새로운 프로그램으로 덮어씌운 후 새로운 프로그램의 첫 부분부터 수행을 시작하게 한다.
  • fork(), exec(), exit() 함수는 모두 사용자 프로세스가 직접 수행할 수 없는 특권명령에 해당되며, 이를 수행하기 위해서는 OS에 대행을 요청해야 한다.
  • 이외에도 wait() 함수가 존재하는데 fork() 후에 wait()을 호출하면 자식 프로세스가 종료될 때까지 부모 프로세스를 봉쇄 상태로 놓으며 자식 프로세스가 종료되는 순간 부모 프로세스는 준비 큐에 재진입하여 CPU를 얻을 수 있는 권한을 획득한다.

8. 프로세스 간의 협력

  • 프로세스는 각자 독립적인 주소 공간을 가지고 수행되고 임의의 프로세스가 다른 프로세스의 주소 공간을 참조하는 것은 허용되지 않는다.
  • 경우에 따라선 독립적인 프로세스들이 협력함으로써 업무 효율의 증진을 가져올 수 있다. 따라서, OS는 프로세스 간의 협력 메커니즘을 제공하여 하나의 프로세스가 다른 프로세스의 수행에 영향을 미칠 수 있게 한다.

※ IPC(Inter-Process Communication)

  • IPC : 하나의 컴퓨터 안에서 실행 중인 서로 다른 프로세스 간에 발생하는 통신
  • 이런 통신은 의사소통 기능 뿐 아니라 동기화도 함께 보장해주어야 한다. 공유 데이터가 존재한다는 것은 서로 다른 프로세스가 동시에 접근했을 때 동시성 문제를 일으킬 수 있기 때문이다(데이터의 불일치). 따라서 공유 데이터의 값을 변경하는 동안은 다른 프로세스의 접근을 허용하지 않는다.
  • IPC의 방법으로는 메시지 전달 방식과 공유 메모리 방식이 있다. (프로세스 사이에 공유 데이터 사용 여부의 차이)

▷ 메시지 전달 방식

  • 프로세스 간에 공유 데이터를 일체 사용하지 않고 메시지를 주고받으면서 통신하는 방식 (두 프로세스의 주소 공간이 다르므로 직접은 불가)
  • 메시지 통신을 하는 시스템은 커널에 의해 send(message)와 receive(message) 두 가지 연산을 제공받고, 이 두 연산을 통해 프로세스에서 OS로 시스템 콜 방식으로 요청하여 전달할 수 있다.
  • 프로세스 간에 커뮤니케이션 링크를 구성한 후 연산을 이용해 메시지를 주고받는데, 이 커뮤니케이션 링크의 구현 방법은 물리적인 방법과 논리적인 방법이 있을 수 있다.
  • 메시지의 전송 대상이 다른 프로세스인지 아니면 메일박스라는 일종의 저장공간인지에 따라 다시 나눌 수 있다.
① 직접 통신
- 통신하려는 프로세스의 이름을 명시적으로 표시  ex) send(P, message) // receive(Q, message), P와 Q가 프로세스의 이름을 지칭
- 커뮤니케이션 링크는 자동적으로 생성되고, 하나의 링크는 정확히 한 쌍의 프로세스에게 할당된다. 각 쌍의 링크는 오직 하나의 링크만이 존재하고, 단방향이 존재할 수 있으나 대부분 양방향성이다.
② 간접 통신
- 메시지를 메일박스 또는 포트로부터 전달받는다.
- 각 메일박스에는 고유의 ID가 있고, 메일박스를 공유하는 프로세스만 서로 통신을 할 수 있다.
- send(A, message) 는 A라는 메일박스에 메시지를 전송하는 것을 뜻하며, receive(A, message) 는 A라는 메일박스로부터 메시지를 전달받는 것을 뜻한다.
- 메일 박스에 만약 3개의 프로세스가 서로 공유하는 상태라면?
- 이를 해결하기 위해선 2개의 프로세스에만 링크를 할당하는 방법이나, 링크에 대한 receive() 연산을 매 시점 하나의 프로세스만 수행할 수 있도록 하는 방법이나, 시스템이 메시지 수신자를 임의로 결정해 송신자에게 어떤 프로세스가 수신했는지를 알리는 방법이 있다.

 

▷ 공유 메모리 방식

(a) 메시지 전달 방식과 (b) 공유 메모리 방식

  • 공유 메모리 방식에서는 프로세스들이 주소 공간의 일부를 공유한다.
  • OS에서 공유메모리를 사용하는 시스템 콜을 지원해 서로 다른 프로세스들이 그들의 주소 공간 중 일부를 공유할 수 있도록 한다.
  • 실제 구현은 프로세스 A와 B가 독자적인 주소 공간을 갖고 있으나 주소 공간이 물리적 메모리에 매핑될 때 공유메모리 주소 영역에 대해서는 동일한 물리적 메모리 주소로 매핑된다.
  • 공유메모리 방식은 프로세스 간의 통신을 수월하게 만드는 인터페이스를 제공하나, 데이터의 일관성 문제에 관해서는 커널이 책임지지 않기 때문에 프로세스들끼리 직접 책임져야 한다.