본문 바로가기

OS/OS

공룡책 3장 프로세스

반응형
SMALL

이 글은 공룡책으로 유명한 운영체제 9판을 가지고 작성한 글입니다.

다르거나 이상한 점이 있다면 댓글로 알려주시면 감사하겠습니다.


옛날엔 한 번에 하나의 프로세스만을 실행하도록 했지만, 오늘 날의 컴퓨터 발전으로 다수의 프로그램이 적재되어 실행되는 것을 허용하게 되었습니다. 그렇기에 프로그램을 보다 견고하게 제어해야하여 프로세스의 개념이 나오게 되었습니다.

 

프로세스란?

프로세스란 실행 중인 프로그램을 말합니다. 멀티태스킹(시분할) 시스템에서 작업의 단위가 되기도 합니다.

프로세스는 스택, 힙, 데이터, 텍스트 섹션들을 가지고 있습니다.

  • 스택의 경우엔 함수의 매개변수, 복귀 주소, 지역 변수와 같은 임시적인 자료를 가지고 있습니다.
  • 데이터는 전역 변수를 포함합니다.
  • 의 경우엔 프로그램 실행 중에 동적으로 할당되는 메모리입니다.
  • 텍스트는 프로그램 코드를 포함하고 있습니다.

프로그램과 프로세스를 구분할 필요가 있습니다. 프로그램은 디스크에 저장된 파일과 같은 수동적인 존재입니다. 이러한 수동적인 존재에서 자원을 가져 능동적인 존재가 바로 저희가 공부할 프로세스입니다.

프로세스의 상태

프로세스의 일대기를 한 번 보도록 합시다. 프로세스는 실행부터 종료까지 어느 라이프 사이클(상태)를 가지게 됩니다. 이는 그림으로 보는 것이 좀 더 직관적이니 그림을 보여주겠습니다.

프로세스 상태 (Life Cycle)

각 상태를 설명해보자면

  • new : 프로세스가 생성 중입니다.
  • ready : 프로세스가 처리기에 할당되기를 기다립니다.
  • running : 명령어들이 실행되고 있습니다.
  • waiting : 프로세스가 어떤 사건(입출력, 신호 수신)이 일어나기를 기다립니다.
  • terminated : 프로세스의 실행이 종료됩니다.

이들의 이름은 때에따라 다를 수 있지만, 이들이 나타내는 상태는 모든 시스템에서 찾아볼 수 있습니다. 저희는 여기서 한 순간에 한 처리기상에서 오직 하나의 프로세스만이 실행되는 것을 인지해야합니다. 하지만, 많은 프로세스가 준비완료 및 대기 상태에 있을 수 있다는 것도 같이 인지해야합니다.

프로세스 제어 블록 (PCB : Process Control Block)

각 프로세스는 운영체제에서 프로세스 제어 블록(이하 PCB)에 의해 표현됩니다.

PCB는 특정 프로세스와 연관된 여러 정보를 가지고 있습니다.

  • 프로세스 상태 : 위에서 설명한 프로세스 상태를 담고 있습니다.
  • 프로그램 카운터 : 특정 프로세스가 다음에 실행할 명령어의 주소를 가리키고 있습니다.
  • CPU 레지스터 : 프로세스가 인터럽트 발생 시 어디까지 실행되었는지 상태를 담고 있습니다.
  • CPU-스케줄링 정보 : 프로세스 우선순위 등을 포함합니다.
  • 메모리 관리 정보 : 메모리와 관련된 테이블 정보를 포함합니다.
  • 회계 정보 : CPU 사용 시간, 시간 제한, 저장 번호를 포함합니다.
  • 입출력 상태 정보 : 프로세스에게 할당된 입출력 장치 정보를 가지고 있습니다.

만약 2개의 프로세스 사이에서 CPU가 전환된다면 PCB에 상태를 저장하고, 읽어오면서 CPU를 전환하게 됩니다.

정말 많은 정보를 담고 있는 것이 PCB 입니다. 간단하게 PCB는 프로세스의 주민등록증이라 생각하시면 될 것 같습니다.

스레드

현재까지 설명한 프로세스 모델은 프로세스가 단일의 실행 스레드를 가진 프로그램임을 가정했습니다. 스레드에 대해선 추후 상세하게 포스팅하겠습니다.


프로세스 스케줄링

현재까지 프로세스가 어떤 것인지를 살펴보았습니다. 멀티 태스킹의 목저은 프로그램이 실행되는 동안 사용자가 상호 작용할 수 있도록 프로세스 사이에서 CPU를 교체하는 것입니다. 이 목적을 달성하기 위해서 프로세스 스케줄러는 CPU에서 실행 가능한 여러 프로세스들 중 하나의 프로세스를 선택합니다.

단일 처리기 시스템에선 프로세스가 한 개 이상 실행될 수 없습니다. 만일 프로세스가 여러개 있다면, 나머지 프로세스들은 다시 스케줄 될 때까지 대기해야 합니다.

스케줄링 큐

프로세스가 시스템에 들어오면 보통 Ready Queue라 불리는 리스트 상에 유지됩니다. 이 큐는 일반적으로 연결 리스트로 저장됩니다. 헤더는 리스트의 첫번째와 마지막 PCB를 가지는 머리와 꼬리 포인터 필드를 가집니다.

새로운 프로세스는 Ready Queue에 놓이고 실행을 위해 CPU를 할당받을(dispatch) 때까지 대기합니다. 그렇게 CPU를 할당받으면, 아래와 같은 사건들 중 하나가 발생할 수 있습니다.

  • 프로세스가 입출력 요청을 하여 Waiting Queue에 넣어질 수 있습니다.
  • 프로세스가 새로운 자식 프로세스를 생성하고 자식 프로세스의 종료를 기다릴 수 있습니다.
  • 프로세스가 인터럽트의 결과로 강제로 CPU로부터 제거되고, Ready Queue에 다시 놓일 수 있습니다.

프로세스가 종료되면 모든 큐에서 삭제되고 PCB와 자원을 반납(deallocate) 합니다.

스케줄러

프로세스는 일생 동안 다양한 스케줄링 큐를 이주합니다. 운영체제는 어떤 방식으로든 스케줄리을 위해 프로세스를 큐에서 선택해야합니다. 선택 절차는 스케줄러에 의해 수행됩니다.

  • 장기 스케줄러(Job 스케줄러)는 프로세스들을 선택하여 실행하기 위해 메모리로 적재합니다.
  • 단기 스케줄러(CPU 스케줄러)는 실행 준비가 완료된 프로세스 중에서, 하나에게 CPU를 할당합니다.

이 두 스케줄러의 차이는 실행 빈도에 있습니다. 단기 스케줄러는 반드시 자주 새로운 프로세스를 선택해야합니다. 실행간격이 짧기 때문에, 반드시 매우 빨라야합니다. 느리다면 그만큼 CPU가 놀게되니 낭비하게 됩니다.

그에 비해서 장기 스케줄러는 실행 빈도수가 적습니다. 시스템에서 새로운 프로세스를 생성하는 간격은 몇 분이 될 수 있습니다. 장기 스케줄러는 신중한 선택을 하는 것이 중요합니다.

문맥 교환(Context Switching)

인터럽트는 운영체제가 작업 중인 CPU를 중단시켜 커널 루틴을 실행할 수 있게 합니다. 인터럽트가 발생하면 시스템은 인터럽트 처리가 끝난 후에 문맥을 복구할 수 있도록 현재 실행 중인 프로세스의 문맥을 저장할 필요가 있습니다. 즉, 프로세스를 중단했다가 재개하는 작업입니다. 그렇기에 PCB에 저장됩니다.

문맥은 CPU 레지스터의 값, 프로세스 상태, 메모리 관리 정보를 포함합니다.

CPU를 다른 프로세스로 교환하려면 이전의 상태를 저장하고 새로운 프로세스 상태를 복구하는 작업이 필요합니다. 이 작업을 문맥 교환(Context Switch)라고 합니다. PCB에서 저장된 문맥 교환을 불러오고, PCB에 보관하는 작업을 거칩니다.

문맥 교환 시간은 어쩔 수 없는 오버헤드입니다.


프로세스 연산

대부분 시스템 내의 프로세스는 병행 실행이 될 수 있습니다. 또한, 동적으로 생성되고 제거되어야합니다. 그러므로 운영체제는 프로세스 생성 및 종료를 위한 기법을 제공해야 합니다.

프로세스 생성(Process Creation)

프로세스는 여러 개의 새로운 프로세스를 생성할 수 있습니다. 앞에서 언급했듯이 생성하는 프로세스를 부모 프로세스, 만들어진 프로세스를 자식 프로세스라고 부릅니다. 이 새로운 프로세스는 또 다른 프로세스를 생성할 수 있으며 그 결과 프로세스의 트리를 형성합니다.

UNIX, Linux, Windows와 같은 대부분 운영체제들은 유일한 프로세스 식별자(pid)를 사용하여 프로세스를 구분합니다. (보통 정수입니다) pid는 시스템의 각 프로세스에게 고유한 값을 가지도록 할당합니다. 이 식별자를 통해 커널이 유지하고 있는 프로세스의 다양한 속성에 접근하기 위한 각주(index)로 사용됩니다.

자식 프로세스 또한 임무를 달성하려면 자원이 필요합니다. 이 자원은 운영체제로부터 얻거나, 부모 프로세스가 가진 자원 부분을 사용할 수 있습니다. (뒤의 경우엔 자식 프로세스가 과부하를 일으키는 일을 방지할 수 있습니다)

자원 뿐만 아니라 초기화 데이터(입력) 역시 자식 프로세스에게 전달할 수 있습니다.

프로세스 종료(Process Termination)

프로세스가 마지막 실행을 끝내고, exit 시스템 호출을 사용하여 운영체제에게 끝내달라고 요청합니다. 이 시점에서 프로세스는 자신의 부모 프로세스에게 상태 값을 반환할 수 있습니다. 물리 메모리, 가상 메모리, 입출력 버퍼 등을 포함한 프로세스의 모든 자원이 운영체제로 반납됩니다.


프로세스간 통신

운영체제 내에서 실행되는 병행 프로세스들은 독립적이거나 협력적인 프로세스일 수 있습니다. 한 프로세스가 다른 프로세스에게 영향을 주거나 받지 않는다면 독립적인 프로세스 이며, 다른 프로세스에게 영향을 주거나 받는다면 협력적인 프로세스입니다.

프로세스 협력을 하는 이유는 몇가지가 있습니다.

  • 정보 공유 : 여러 사용자가 동일한 정보에 흥미를 가질 수 있기에, 협력해야하는 환경이 필요할 수 있습니다.
  • 계산 가속화 : 만일 특정 일을 하고자할 때, 일을 나누어 서브태스크로 병렬 실행하게 만들면 빨리 일을 처리 할 수 있습니다. 단, 여러 개의 코어를 가진 경우에만 가능합니다.
  • 모듈성 : 시스템 기능을 별도의 프로세스로 나누어서 모듈식 시스템을 구성하기를 원할 수 있습니다.
  • 편의성 : 사용자들이 한 순간에 많은 작업을 가질 수 있습니다. 편집, 음악 듣기, 컴파일 작업등 병렬로 일을 처리해야 할 때 프로세스간 협력이 필요합니다.

협력적 프로세스 사이에서 데이터와 정보를 교환할 수 있는 프로세스간 통신 기법이 필요합니다. 프로세스 통신에는 공유 메모리, 메시지 전달 두 가지 모델이 있습니다.

공유 메모리 경우엔 협력 프로세스가 공요하는 메모리 영역이 구축됩니다. 프로세스는 그 메모리 영역을 통해서 데이터를 읽고 쓰고 할 수 있습니다.

메시지 전달 경우엔 메시지 큐를 사이에 두고 두 프로세스가 연락하는 경우입니다. 충돌을 회피할 필요가 없기에 적은 양의 데이터를 교환하는데 유용합니다. 또한 분산 시스템에서 구현하기 쉽습니다.

메시지 전달의 경우엔 커널의 간섭이 필요하기 때문에 (통신사라고 보시면 됩니다) 공유 메모리가 메모리 전달보다 더 빠릅니다.

공유 메모리 시스템

공유 메모리를 사용하려면 통신하는 프로세스들이 공유 메모리 영역을 구축해야 합니다. 보통 공유 메모리 영역은 메모리 세그먼트를 생성하는 프로세스의 주소 공간에 위치합니다.

일반적으로 운영체제는 한 프로세스가 다른 프로세스의 메모리를 접근하는 것을 금지합니다. 공유 메모리는 둘 이상의 프로세스가 이 제약 조건을 제거하는 것에 동의하는 것 입니다. 그런 후에 프로세스는 공유 영역에 읽고 씀으로서 정보를 교환할 수 있습니다.

여기서 일반적인 프로세스 패러다임인 생산자-소비자 문제를 생각해봅시다. 생산자 프로세스는 정보를 생산하고, 소비자 프로세스는 정보를 소비합니다. 예를 들어 컴파일러는 어셈블리 코드를 생산하고, 어셈블러는 이를 소비할 수 있겠죠.

이렇게 생산자 소비자 문제의 해결책 중 하나가 공유메모리를 사용하는 것입니다. 생산자와 소비자 프로세스들이 병행으로 실행되려면, 생산자가 정보를 채워 넣고 소비자가 소모할 수 있도록 항목들의 버퍼가 반드시 사용 가능해야합니다.

이 버퍼는 공유 메모리 영역에 존재합니다.

무한 버퍼와 유한 버퍼 두가지 버퍼가 사용됩니다.

무한 버퍼는 버퍼의 크기에 한계가 없어서 소비자는 계속 기다려야만 할 수 있고, 생산자는 항상 새로운 항목을 생산할 수 있습니다.

유한 버퍼는 버퍼의 크기가 고정되어 있습니다. 버퍼가 비어 있으면 소비자는 반드시 대기해야하며, 버퍼가 채워져 있으면 생산자는 대기해야 합니다.

유한 버퍼에선 in과 out을 갖는 원형 배열로 구현됩니다.

메시지 전달 시스템

공유 메모리로 어떻게 통신을 하는지 살펴보았습니다. 메시지 전달 방식은 동일한 주소 공간을 공유하지 않고도 프로세스들이 통신하고 동기화할 수 있도록 허영하는 기법을 제공합니다. 네트워크에 연결된 컴퓨터들 사이에서 특히 유용합니다.

그렇다면 메시지 전달 시스템의 특성을 살펴보겠습니다.

  • Naming : 통신을 원하는 프로세스끼리 서로를 가리키야하는데, 이는 간접 통신 또는 직접 통신을 사용할 수 있습니다. 직접 통신의 경우엔 함수에 원하는 프로세스 주소를 같이 보내는 것입니다. 이를 위해선 송신자와 수신자 모두 상대방의 이름을 지명해야 합니다. (누구한테 보내는지, 누구로부터 받는지) 그렇기에 대칭성을 보입니다. 비대칭도 사용할 수 있습니다. 보내는 사람만 수신자 이름을 지명할 경우입니다.
  • Synchronization : 메시지 전달은 Blocking, non-Blocking 방식으로 전달됩니다. (동기식, 비동기식이라고도 합니다.
    • Blocking 보내기 : 보내는 프로세스가 받는 프로세스에 의해 메시지 받아질 때까지 쉽니다.
    • non-Blocking 보내기 : 보내는 프로세스가 메시지를 보내고 작업을 계속합니다.
    • Blocking 받기 : 메시지가 이용 가능할 때까지 받는 프로세스를 봉쇄합니다.
    • non-Blocking 받기 : 보내는 프로세스가 유효한 메시지 또는 널을 받습니다. 
  • Buffering : 메시지가 직접이든 간접이든, 메시지는 임시 큐에 있습니다. 이 큐를 구현하는 방식은 세 가지가 있습니다.
    • 무용량 : 큐의 길이가 0입니다. 즉, 대기하는 메시지를 가질 수 없습니다. 보내는 프로세스는 받는 프로세스가 받을 때까지 기다려야 합니다.
    • 유한 용량 : n의 길이를 가집니다. 최대 n개의 메시지가 있을 수 있고, 최대가 아니라면 보내는 프로세스는 계속 실행을 합니다. 가득 차버리면 이 큐가 여유가 생길때 까지 보내는 프로세스는 작업을 할 수 없습니다.
    • 무한 용량 : 보내는 프로세스는 계속해서 작업할 수 있습니다.

클라이언트 서버 환경에서 통신

공유메모리와 메시지 전달 기법을 이용해서 프로세스들이 통신하는 방법에 대해 얘기했습니다. 이 기법은 클라이언트 서버 시스템의 통신에도 사용할 수 있습니다.

소켓(Socket)

소켓은 통신의 극점(endpoint)을 뜻합니다. 두 프로세스가 네트워크상 통신을 하려면 양 프로세스마다 하나씩, 총 두 개의 소켓이 필요합니다.

각 소켓은 IP 주소와 포트 번호 두 가지를 접합해서 구별합니다. 일반적으로 소켓은 클라이언트-서버 구조를 사용합니다. 서버는 지정된 port에 클라이언트 요청 메시지가 도착하기를 기다리고, 요청이 수신되면 서버는 클라이언트 소켓으로부터 연결 요청을 수락함으로서 연결이 완성됩니다.

http의 특정 서비스를 구현하는 서버는 well-known 포트로부터 메시지를 기다립니다. ("well-known"이란 전 세계에서 표준으로 사용하는 포트 번호라는 의미입니다) http는 80번 포트를 사용합니다.

클라이언트 프로세스가 연결을 요청하면 호스트 컴퓨터가 포트 번호를 부여합니다. 이 번호는 1024보다 큰 임의의 정수가 됩니다.

예를 들어 IP 주소 146.86.5.20인 호스트 X에 있는 클라이언트가 IP 주소 161.25.19.8의 웹 서버에 접속하려고 한다면 호스트 X는 클라이언트에게 포트 1625를 부여합니다. 연결은 한 쌍의 소켓, 호스트 x의 146.86.5.20:1625와 웹서버의 161.25.19.8.80으로 구성됩니다. 두 호스트 사이에 패킷들이 오갈 때 그 패킷들은 이 목적지 포트 번호가 지정하는데 따라 적절한 프로세스에게로 배달됩니다.

모든 연결은 유일해야 합니다. 따라서 호스트 X에 있는 다른 클라이언트 프로세스가 위와 동일한 웹 서버와 통신을 하면 그 클라이언트는1024보다 크고 1625가 아닌 다른 포트 번호를 부여받게 됩니다. 이것은 모든 연결이 유일한 소켓 쌍으로 구성되는 것을 보장합니다.

Java 얘기를 해봅시다. Java는 세 가지 종류의 소켓을 제공합니다. 연결 기반(TCP) 소켓은 Socket 클래스로 구현됩니다. 비연결성(UDP) 소켓은 DatagramSocket 클래스를 사용합니다. 마지막으로 MulticastSocket 클래스는 DatagramSocket 클래스의 서브 클래스입니다. Multicast 소켓은 데이터를 여러 수신자들에게 보낼 수 있습니다.

파이프

파이프는 두 프로세스가 통신할 수 있게 하는 전달자로서 동작합니다. 파이프는 통산 프로세스들 간에 통신하는 간단한 방법 중의 하나이지만 통신할 때 여러 제약을 가집니다.

  • 파이프가 단방향 통신 또는 양방향 통신을 허용하는가?
  • 양방향 통신이 허용된다면 반이중(half duplex) 방식인가, 전이중(full duplex) 방식인가?
    • 반이중 방식은 한 순간에 한 방향 전송만 가능하고 전이중 방식은 동시에 양방향 데이터 전송이 가능합니다.
  • 통신하는 두 프로세스 간에 부모-자식과 같은 특정 관계가 존재해야만 하는가?
  • 파이프는 네트워크를 통하여 통신이 가능한가, 아니면 동일한 기계 안에 존재하는 두 프로세스끼리만 통신할 수 있는가?

일반적으로 일반 파이프와 지명 파이프의 두 가지 유형의 파이프가 있는데 이를 살펴보겠습니다.

 

일반 파이프

일반 파이프는 생산자-소비자 형태로 두 프로세스 간의 통신을 허용합니다. 생산자는 파이프의 한 끝에서 쓰고 (쓰기 종단), 소비자는 다른 종단에서 읽습니다. (읽기 종단) 결과적으로 일반 파이프는 한쪽으로만 데이터를 전송할 수 있으며 오직 단방향 통신만 가능합니다.

만일 양방향 통신을 하려면 두 개의 파이프를 사용해야합니다.

 

지명 파이프

지명 파이프는 좀 더 강력한 통신 도구를 제공합니다. 통신은 양방향으로 가능하며 부모-자식 관계도 필요하지 않습니다. 지명 파이프가 구축되면 여러 프로세스들이 이를 사용하여 통신할 수 있습니다.


요약

프로세스는 실행 중인 프로그램입니다. 프로세스는 실행되면서 상태가 변합니다. 프로세스의 상태는 프로세스의 현재 활동에 의해 정의됩니다.

상태로는 new, ready, running, waiting, terminated 상태 중의 하나이며 그 상태들과 프로세스의 정보들은 PCB에 저장됩니다.

한 프로세스가 실행되지 않을 땐 Ready Queue에 놓이게 됩니다. 운영체제는 크게 두 가지 부류의 큐가 존재하는데, 하나는 Waiting Queue이며, 다른 하나는 Ready Queue입니다. Ready Queue는 실행할 준비가 완료되어 CPU를 기다리고 잇는 프로세스를 가지고 있습니다.

운영체제는 다양한 스케줄링 큐로부터 프로세스를 선택해야 합니다. 장기 스케줄링(잡 스케줄링)은 CPU를 차지하기 위해 경쟁할 프로세스를 선택하는 일입니다. 장기 스케줄리은 자원 할당 고려 사항, 주 메모리 관리에 의해 많은 영향을 받습니다. 단기 스케줄링(CPU 스케줄링)은 Ready Queue로부터 하나의 프로세스를 선택하는 일입니다.

운영체제는 부모 프로세스가 자식 프로세스를 생성할 수 있는 기법을 제공해야 합니다. 부모는 작업을 진행하기 전에 자식 프로세스가 종료되는 것을 기다릴 수 있으며 자식 프로세스와 병행하게 실행될 수 있습니다. 병행 실행을 하는 이유는 정보 공유, 계산 속도 증가, 모듈성, 편의성이 있습니다.

운영체제 내에서 실행되는 프로세스들은 독립적인 프로세스이거나 협력적 프로레스 입니다. 협력적인 프로세스는 서로 통신하기 위해 프로세스 간 통신 기법을 필요로 합니다. 통신 기법엔 두 가지가 있는데, 공유 메모리 기법과 메시지 전달 기법입니다. 공유 메모리 기법은 통신 프로세스들이 어떤 변수를 공유할 것을 필요로 합니다. 공유 메모리 시스템에서 통신을 제공하기 위한 책임은 응용 프로그래머에게 있습니다. 즉, 운영체제는 단지 공유 메모리만 제공하는 것입니다.

메시지 전달 기법은 프로세스가 메시지 교환하는 것을 허용합니다. 통신을 제공하는 책임은 운영체제 자체에 있습니다. 이 두 가지 기법은 서로 배타적인 방법은 아니며, 하나의 운영체제에서 두 가지 방법을 같이 사용할 수 있습니다.

클라이언트 서버 시스템에서 통신은 소켓, 원격 프로시저 호출(RPC, remote procesure call, 이 글에선 다루지 않았습니다) 파이프를 사용합니다.

소켓은 통신의 극점으로 정의된 것입니다. 응용 프로그램 두 개가 통신할 때에는 두 개의 소켓이 만들어지고 통신 채널 양단에 하나씩 배정됩니다.  파이프의 경우엔 프로세스 끼리 서로 통신할 수 있는 상대적으로 간단한 방법을 제공합니다. 일반 파이프가 부모와 자식 프로세스 간의 통신을 허용하는 반면에 지명 파이프는 서로 무관한 프로세스 사이의 통신을 허용합니다.


이상 공룡책 3장 프로세스였습니다. ^_^

반응형
LIST

'OS > OS' 카테고리의 다른 글

공룡책 5장 CPU 스케줄링  (2) 2019.11.06
공룡책 4장 스레드  (0) 2019.11.03
하이버네이트(Hibernate)란?  (0) 2019.06.30
멀티스레딩이란?  (0) 2019.06.17
프로세스와 스레드의 차이  (0) 2019.06.17