OS Concepts 10th 3장: Processes
본 글은 Operating System Concepts 10th (운영체제) 책을 보며 내용을 개인 공부에 목적으로 정리했습니다.
이전에 운영체제 관련 강의들을 들으면서 정리한 시리즈 글들이 있는데,
지식을 습득하는 데 있어 가장 느리지만 가장 빠른 방법이 원본책을 자세히 보는 것이라 생각됩니다.
책 내용들을 최대한 이해하기 위해 거의 모든 내용을 담고 있습니다.
책 pdf 링크 : Operating System Concepts 10th Edition by Abraham Silberschatz Peter B Galvin Greg Gagne pdf free download
연습 문제 정답지 : Solutions of Practice Exercises, Tenth Edition of Operating System Concepts, AVI SILBERSCHATZ
3. Process
- 오늘날의 컴퓨터 시스템들은 메모리에 다수의 프로그램이 적재(load)되어 동시(concurrent) 실행되는 것을 허용한다.
- 이러한 발전은 다양한 프로그램을 보다 견고하게 제어하고 보다 구획화(compartmentalization)할 것을 필요로 했고, 프로세스의 개념이 등장했다.
- 프로세스는 실행 중인 프로그램을 말하며, 현대 컴퓨터 시스템에서 작업의 단위이다.
- 목표
- 프로세스의 개별 구성요소를 식별하고 운영체제에서 해당 구성요소가 어떻게 표현되고 스케줄 되는지 기술한다.
- 운영체제에서 프로세스를 생성하고 종료하는 방법을 설명한다. 이러한 작업을 수행하는 적절한 시스템 콜을 사용하여 프로그램의 개발 등이 포함된다.
- 공유 메모리 및 메시지 전달을 사용하는 프로세스 간 통신을 설명하고 대조한다.
- 파이프와 POSIX 공유 메모리를 사용하여 프로세스 간 통신을 수행하는 프로그램을 설계한다.
- 소켓과 원격 프로시저 호출을 사용하여 클라이언트-서버 통신을 설명한다.
- Linux 운영체제와 상호 작용하는 커널 모듈을 설계한다.
3.1 Process Concept
- 초창기 컴퓨터는 CPU 활동들을 작업(job)을 실행하는 일괄처리(batch) 시스템이었고, 사용자 프로그램(user program) 또는 태스크(task)를 실행하는 시분할 시스템이 뒤를 이었다.
- 현대에 와서 이러한 모든 활동을 프로세스라고 부른다.
3.1.1 The Process
- 프로세스의 현재 활동의 상태(status)는 프로그램 카운터(program counter) 값과 프로세서 레지스터의 내용으로 나타낸다.
- 프로그램 카운터(program counter) : 마이크로프로세서 내부에 있는 레지스터 중의 하나로서, 다음에 실행될 명령어의 주소를 가지고 있어 실행할 기계어 코드의 위치를 지정한다. (=명령어 포인터) 1
- 프로세스의 메모리 배치는 다음 그림과 같이 여러 섹션으로 구분된다.
- Text section : 실행가능한 코드
- Data section : 전역 변수(global variable)
- Heap section : 프로그램 실행 중에 동적으로 할당되는 메모리
- 동적(dynamic) : 그때 그때 생성하는 것을 말한다.
- 동적 메모리 할당 : 실행 시간 동안 사용할 메모리 공간을 할당하는 것을 말한다.
- Stack section : 함수를 호출할 때 임시 데이터 저장장소(함수 파라미터, 리턴 주소, 지역 변수)
- 텍스트 및 데이터 섹션의 크기는 고정되기 때문에 프로그램 실행 시간 동안 크기가 변하지 않는다. 그러나 스택 및 힙 섹션은 프로그램 실행 중에 동적으로 줄어들거나 커질 수 있다.
- 함수가 호출될 때마다 함수 파라미터, 지역 변수 및 리턴 주소를 포함하는 활성화 레코드(activation record)가 스택에 푸시(push)된다.
- 함수로부터 제어(control)가 리턴되면 스택에서 활성화 레코드가 팝(pop) 된다.
- 메모리가 동적으로 할당됨에 따라 힙이 커지고 메모리가 시스템에 반환되면 축소된다.
- 실행 파일이 메모리에 적재될 때 프로그램은 프로세스가 된다.
- 프로세스가 실행되는 과정에서 많은 프로세스를 생성하는 것이 보통이다.
- 예를 들어 대부분의 상황에서 실행 가능한 Java 프로그램은 JVM 안에서 실행된다. JVM은 적재된 Java 코드를 해석하고 그 코드를 대신하여 원 기계어를 이용하여 행동을 취하는 프로세스로서 프로그램을 실행한다.
- 다음은 C 프로그램의 메모리 배치 그림이다.
3.1.2 Process State
- 프로세스는 실행되면 그 상태(state)가 변한다.
- 프로세스의 상태는 부분적으로 그 프로세스의 현재의 활동에 따라서 정의된다.
- 새로운(new) : 프로세스가 생성중이다.
- 준비(ready) : 프로세스가 프로세서에 할당되기를 기다린다.
- 실행(running) : 명령어들이 실행되고 있다.
- 대기(waiting) : 프로세스가 어떤 이벤트(입출력 완료 등)가 일어나기를 기다린다.
- 종료(terminated) : 프로세스의 실행이 종료되었다.
3.1.3 Process Control Block
- 각 프로세스는 운영체제에서 프로세스 제어 블록(process control block, PCB)에 의해 표현된다.
- PCB는 특정 프로세스와 연관된 여러 정보를 수록하며, 다음과 같은 것들을 포함한다.
- 프로세스 상태(process state) : new, ready, running, waiting, halted(정지) 상태 등이다.
- 프로그램 카운터(program counter) : 이 프로세스가 다음에 실행할 명령어의 주소를 가리킨다.
- CPU 레지스터들(registers) : 레지스터에는 누산기(accumulator), 인덱스 레지스터, 스택 레지스터, 범용 레지스터들과 상태 코드 정보가 포함된다.
- CPU-스케줄링 정보 : 이 정보는 프로세스 우선순위, 스케줄 큐에 대한 포인터와 다른 스케줄 파라미터를 포함한다.
- 메모리 관리 정보(memory-management information) : 이 정보는 운영체제에 의해 사용되는 메모리 시스템에 따라 기준(base) 레지스터와 한계(limit) 레지스터의 값, 운영체제가 사용하는 메모리 시스템에 따라 페이지 테이블(page table) 또는 세그먼트 테이블(segment table) 등과 같은 정보를 포함한다.
- 회계(accounting) 정보 : 이 정보는 CPU 사용 시간과 경과된 실시간, 시간 제한, 계정 번호, 잡 또는 프로세스 번호 등을 포함한다.
- 입출력 상태 정보 : 이 정보는 이 프로세스에 할당된 입출력 장치들과 open 파일의 목록 등을 포함한다.
- 즉, PCB는 약간의 회계 데이터와 함께 프로세스를 시작시키거나 다시 시작시키는 데 필요한 모든 데이터를 위한 저장소의 역할을 한다.
3.1.4 Threads
- 싱글 제어 스레드는 프로세스가 한 번에 한 가지 일만 실행하도록 허용한다.
- 현대 운영체제는 프로세스 개념을 확장하여 한 프로세스가 다수의 실행 스레드를 가질 수 있도록 한다.
- 프로세스가 한 번에 하나 이상의 일을 수행할 수 있도록 허용한다.
- ex. 워드 프로세서는 하나의 스레드에 사용자 입력 관리를 맡기는 동안 다른 스레드가 철자 검사기를 수행하도록 만들 수 있다.
3.2 Process Scheduling
- 멀티 프로그래밍의 목적은 CPU 이용을 최대화하기 위하여 항상 어떤 프로세스가 실행되도록 하는 데 있다.
- 시분할(time sharing)의 목적은 각 프로그램이 실행되는 동안 사용자가 상호 작용할 수 있도록 프로세스들 사이에서 CPU 코어를 빈번하게 교체하는 것이다.
- 이 목적을 달성하기 위해 프로세스 스케줄러(process scheduler)는 코어에서 실행 가능한 여러 프로세스 중에서 하나의 프로세스를 선택한다.
- 스케줄링(scheduling) : 시스템의 목표를 달성할 수 있도록 프로세서를 할당하는 일련의 과정
- 스케줄러(scheduler) : ready 상태의 프로세스 중에서 이번에 CPU를 줄 프로세스를 결정하는 프로그램
3.2.1 Scheduling Queue
- 프로세스가 시스템에 들어가면 준비(ready) 큐에 들어가서 준비 상태가 되어 CPU 코어에서 실행되기를 기다린다.
- 이 큐는 연결 리스트로 저장된다.
- 준비 큐 헤더(header)에는 리스트의 첫 번째 PCB에 대한 포인터가 저장되고 각 PCB에는 준비 큐의 다음 PCB를 가리키는 포인터 필드가 포함된다.
- I/O 완료와 같은 특정 이벤트가 발생하기를 기다리는 프로세스는 대기 큐에 삽입된다.
작업 큐(Job Queue) : 현재 시스템 내에 있는 모든 프로세스의 집합
준비 큐(Ready Queue) : 현재 메모리 내에 있으면서 CPU를 잡아서 실행되기를 기다리는 프로세스의 집합
장치 큐(Device Queue) : I/O 디바이스의 처리를 기다리는 프로세스의 집합 2
- 프로세스 스케줄링의 일반적인 표현은 Figure 3.5와 같은 큐잉 다이어그램(queueing diagram)이다.
- 준비 큐와 대기 큐의 집합의 2가지 유형의 큐가 제시되어 있다.
- 원은 큐에 서비스를 제공하는 자원을 나타내고 화살표는 시스템의 프로세스의 흐름을 나타낸다.
- 새 프로세스는 처음에 준비 큐에 놓인다. 프로세스는 실행을 위해 선택되거나 또는 디스패치(dispatch) 될 때까지 기다린다.
- 프로세스에 CPU 코어가 할당되고 실행 상태가 되면, 여러 이벤트 중 하나가 발생할 수 있다.
3.2.2 CPU Scheduling
- 프로세스는 수명주기 동안 준비 큐와 다양한 대기 큐를 이동한다(migrate).
- CPU 스케줄러(cpu scheduler)의 역할은 준비 큐에 있는 프로세스 중에서 선택된 하나의 프로세스에 CPU 코어를 할당하는 것이다.
- CPU 스케줄러는 CPU를 할당하기 위한 새 프로세스를 자주 선택해야 한다.
- 일부 운영체제는 스와핑(swapping)으로 알려진 중간 형태의 스케줄링을 가지고 있다.
- 핵심 아이디어는 메모리에서 프로세스를 제거하여 멀티 프로그래밍의 정도를 감소시키는 것이 유리할 수 있다는 것이다.
- 프로세스를 메모리에서 디스크로 스왑아웃(swapped out)하고 현재 상태를 저장하고, 이후 디스크에서 메모리로 스왑인(swapped in)하여 상태를 복원할 수 있기 때문에 이 기법을 스와핑(swapping)이라 한다.
- 스와핑은 일반적으로 메모리가 초과 사용되어 가용공간을 확보해야 할 때만 필요하다.
3.2.3 Context Switch
- 인터럽트는 운영체제가 CPU 코어를 현재 작업에서 뺏어 내어 커널 루틴을 실행할 수 있게 한다.
- 인터럽트가 발생하면 시스템은 인터럽트 처리가 끝난 후에 컨텍스트(context)를 복구할 수 있도록 현재 실행 중인 프로세스의 현재 컨텍스트를 저장할 필요가 있다.
- 컨텍스트(context) : 현재 CPU를 사용중인 프로세스의 CPU 제어권이 다른 프로세스로 이양되는 과정 3
- 컨텍스트는 프로세스의 PCB에 표현된다.
- 컨텍스트는 CPU 레지스터의 값, 프로세스의 상태, 메모리 관리 정보 등을 포함한다.
- 일반적으로 커널 모드이건 사용자 모드이건 CPU의 현재 상태를 저장하는(state save) 작업을 수행하고, 나중에 연산을 재개하기 위하여 상태 복구 작업(state restore)을 수행한다.
- CPU 코어를 다른 프로세스로 교환하려면 이전의 프로세스의 상태를 보관하고 새로운 프로세스의 보관된 상태를 복구하는 작업이 필요한데, 이 작업을 컨텍스트 교환(context switch)라 한다.
- 컨텍스트 교환이 일어나면, 커널은 과거 프로세스의 컨텍스트를 PCB에 저장하고, 실행이 스케줄된 새로운 프로세스의 저장된 컨텍스트를 복구한다.
- 컨텍스트 교환이 진행될 동안 시스템이 아무런 유용한 일을 못 하기 때문에 컨텍스트 교환 시간은 순수한 오버헤드이다.
- 오버헤드(overhead) : 컨텍스트 교환에 필요한 시간, 메모리 등을 말한다.
3.2 Operation on Processes
- 대부분 시스템 내의 프로세스들은 동시 실행될 수 있으며, 반드시 동적(그때 그때)으로 생성되고, 제거되어야 한다.
- 그러므로 운영체제는 프로세스 생성 및 종료를 위한 기법을 제공해야 한다.
3.3.1 Process Creation
- 실행되는 동안 프로세스는 여러 개의 새로운 프로세스들을 생성할 수 있다.
- 생성하는 프로세스를 부모(parent) 프로세스라고 부르고, 새로운 프로세스는 자식(child) 프로세스라고 부른다.
- 이 새로운 프로세스들은 각각 다시 다른 프로세스들을 생성할 수 있으며, 그 결과 프로세스의 트리(tree)를 형성한다.
- 운영체제는 고유한 프로세스 식별자(process identifier, pid)를 사용하여 프로세스를 구분한다.
- 이 식별자를 통하여 커널이 유지하고 있는 프로세스의 다양한 속성에 접근하기 위한 인덱스로 사용된다.
-
프로세스가 새로운 프로세스를 생성할 때, 두 프로세스를 실행시키는 데 두 가지 가능한 방법이 존재한다.
- 부모는 자식과 동시하게 실행을 계속한다.
- 부모는 일부 또는 모든 자식이 실행을 종료할 때까지 기다린다.
-
새로운 프로세스들의 주소 공간 측면에서 볼 때 다음과 같은 두 가지 가능성이 있다.
- 자식 프로세스는 부모 프로세스의 복사본이다.
- 자식 프로세스가 자신에게 적재될 새로운 프로그램을 가지고 있다.
- UNIX 시스템 콜을 봐본다. (figure 3.9)
- 동일한 프로그램의 복사본을 실행하는 두 개의 서로 다른 프로세스를 갖는다.
- 유일한 차이점은 자식 프로세스에 보이는 pid의 값은 0이고, 반면에 부모 프로세스에게 보이는 pid 값은 0보다 큰 정수 값이라는 것이다.
- 자식 프로세스는 오픈 파일과 같은 리소스뿐 아니라 특권과 스케줄링 속성을 부모 프로세스로부터 상속받는다.
- 그런 후에 자식 프로세스는
exec()
시스템 콜을 사용하여 자신의 주소 공간을 UNIX 명령/bin/ls
로 덮어쓴다. - 부모는
wait()
시스템 콜로 자식 프로세스가 끝나기를 기다린다. - 자식 프로세스가 끝나면, 부모 프로세스는
wait()
호출로부터 재개하여,exit()
시스템 콜을 사용하여 끝낸다.
3.3.2 Process Termination
- 프로세스가 마지막 문장의 실행을 끝내고, exit 시스템 콜을 사용하여 운영체제에 자신의 삭제를 요청하면 종료한다.
- 이 시점에서, 프로세스는 자신을 기다리고 있는 부모 프로세스에 상태 값을 반환할 수 있다.
- 물리 메모리와 가상 메모리 오픈 파일, 입출력 버퍼를 포함한 프로세스의 모든 자원이 할당 해제되고 운영체제로 반납된다.
- 부모는 다음과 같이 여러 가지 이유로 인하여 자식 중 하나의 실행을 종료할 수 있다.
- 자식이 자신에게 할당된 리소스를 초과하여 사용할 때, 이때는 부모가 자식들의 상태를 검사할 수 있는 방편이 주어져야 한다.
- 자식에게 할당된 태스크가 더 이상 필요 없을 때
- 부모가 exit를 하는데, 운영체제는 부모가 exit 한 후에 자식이 실행을 계속하는 것을 허용하지 않는 경우
- 몇몇 시스템에서는 부모 프로세스가 종료한 이후에 자식 프로세스가 존재할 수 없다.
- 그러한 시스템에서는 프로세스가 종료되면 그로부터 비롯된 모든 자식 프로세스들도 종료되어야 한다. 이것을 연쇄식 종료(cascading termination)이라 하며, 이 작업은 운영체제가 시행한다.
- 좀비(zombie) 프로세스 : 종료되었지만 부모 프로세스가 아직 wait() 호출을 하지 않은 프로세스
- 고아(orphan) 프로세스 : 부모 프로세스가 wait()를 호출하는 대신 종료할 때 부닥친 자식 프로세스
3.4 Interprocess Communication
- 운영체제 내에서 실행되는 동시 프로세스들은 독립적이거나 또는 협력적인 프로세스들 일 수 있다.
- 프로세스가 시스템에서 실행 중인 다른 프로세스들과 데이터를 공유하지 않는 프로세스는 독립적(independent)이다.
- 프로세스가 시스템에서 실행 중인 다른 프로세스들에 영향을 주거나 받는다면 협력적(cooperating)인 프로세스들이다.
- 프로세스 협력을 허용하는 환경을 제공하는 데는 다음과 같은 이유가 있다.
- 정보 공유(information sharing) : 여러 응용 프로그램이 동일한 정보에 흥미를 느낄 수 있으므로, 그러한 정보를 동시적(concurrent)으로 접근할 수 있는 환경을 제공해야 한다.
- 계산 가속화(computation speedup) : 만일 우리가 특정 태스크를 빨리 실행하고자 한다면, 그것을 서브태스크로 나누어 이들 각각이 다른 서브태스크들과 병렬(parallel)로 실행되게 해야 한다.
- 모듈성(modularity) : 시스템 기능을 별도의 프로세스들 또는 스레드들로 나누어, 모듈식 형태로 시스템을 구성하기를 원할 수 있다.
- 협력적 프로세스들은 데이터를 교환할 수 있는, 즉 서로 데이터를 보내거나 받을 수 있는 프로세스 간 통신(interprocess communication, IPC) 기법이 필요하다.
- 프로세스 간 통신에는 기본적으로 공유 메모리(shared memory)와 메시지 전달(message passing)의 두 가지 모델이 있다.
- 공유 메모리 모델에서는 협력 프로세스들에 의해 공유되는 메모리의 영역이 구축된다.
- 프로세스들은 그 영역에 데이터를 읽고 쓰고 함으로써 정보를 교환할 수 있다.
- 메시지 전달 모델에서는 통신이 협력 프로세스들 사이에 교환되는 메시지를 통하여 이루어진다.
- 운영체제에서는 통상적으로 두 모델을 쓴다.
- 메시지 전달 모델은 충돌을 회피할 필요가 없기 때문에 적은 양의 데이터를 교환하는 데 유용하다.
- 메시지 전달은 또한 분산 시스템에서 공유 메모리보다 구현하기 쉽다.
- 메시지 전달 시스템은 시스템 콜을 사용하여 구현되므로 커널 간섭 등의 부가적인 시간 소비 작업 필요하기 때문에 공유 메모리 모델이 메시지 전달보다 더 빠르다.
- 공유 메모리 시스템에서는 공유 메모리 영역을 구축할 때만 시스템 콜이 필요하다.
- 공유 메모리 영역이 구축되면 모든 접근은 일반적인 메모리 접근으로 취급되어 커널의 도움이 필요 없다.
3.5 IPC in Shared-Memory Systems
- 공유 메모리를 사용하는 프로세스 간 통신에서는 통신하는 프로세스들이 공유 메모리 영역을 구축해야 한다.
- 공유 메모리 영역은 공유 메모리 세그먼트(segment)를 생성하는 프로세스의 주소 공간에 위치한다.
- 이 공유 메모리 세그먼트를 이용하여 통신하고자 하는 다른 프로세스들은 이 세그먼트를 자신의 주소 공간에 추가하여야 한다.
- 공유 메모리는 둘 이상의 프로세스가 제약 조건을 제거하는 것에 동의하는 것을 필요로 한다.
- 제약 조건은 운영체제는 한 프로세스가 다른 프로세스의 메모리에 접근하는 것을 금지한다.
- 그런 후에 프로세스들은 공유 영역에 읽고 씀으로써 정보를 교환할 수 있다.
- 데이터의 형식과 위치는 이들 프로세스에 의해 결정되며 운영체제의 소관이 아니다.
- 또한 프로세스들은 동시에 동일한 위치에 쓰지 않도록 책임져야 한다.
3.6 IPC in Message-Passing Systems
- 메시지 전달(message passing) 방식은 동일한 주소 공간을 공유하지 않고도 프로세스들이 통신을 하고, 그들의 동작을 동기화할 수 있도록 허용하는 기법을 제공한다.
- 메시지 전달 방식은 통신하는 프로세스들이 네트워크에 의해 연결된 다른 컴퓨터들에 존재할 수 있는 분산 환경에서 특히 유용하다.
- 메시지 전달 시스템은 최소한 2가지 연산을 제공한다.
send(message)
receive(message)
- 프로세스가 보낸 메시지는 고정(fixed) 길이일 수도 있고 가변(variable) 길이일 수도 있다.
- 고정 길이 메시지만 보낼 수 있으면 시스템 수준의 구현은 직선적(straightforward)이지만 이러한 제한은 프로그래밍 작업을 더욱 힘들게 한다.
- 가변 길이 메시지는 보다 복잡한 시스템 수준의 구현이 있어야 하지만, 프로그래밍 작업은 더 간단해진다.
- 만약 프로세스 $P$와 $Q$가 통신을 원하면, 반드시 서로 메시지를 보내고 받아야 한다.
- 이들 사이에 통신 연결(communication link)이 설정되어야 한다.
- 하나의 링크와
send()/receive()
연산을 논리적으로 구현하는 방법은 다음과 같다.- direct 또는 indirect 통신(communication)
- synchronous 또는 asynchronous 통신(communication)
- automatic 또는 explicit 버퍼링(buffering)
3.6.1 Naming
- 통신을 원하는 프로세스들은 서로를 가리킬 방법이 있어야 한다. 이들은 직접(direct) 또는 간접(indirect) 통신을 사용할 수 있다.
- 직접(direct) 통신 하에서, 통신을 원하는 각 프로세스는 통신의 수신자(recipient) 또는 송신자(sender)의 이름을 명시해야 한다.
send(P, message)
: 프로세스 P에 메시지를 전송한다.receive(Q, message)
: 프로세스 Q로부터 메시지를 수신한다.
-
이 기법에서 통신 연결은 다음의 특성을 가진다.
- 통신을 원하는 각 프로세스의 쌍들 사이에 연결이 자동으로 구축된다. 프로세스들은 통신하기 위해 상대방의 신원(identity)만 알면 된다.
- 연결은 정확히 두 프로세스 사이에만 연관된다.
- 통신하는 프로세스들의 각 쌍 사이에는 정확하게 하나의 연결이 존재해야 한다.
- 위 기법은 주소 방식에서 대칭성(symmetry)을 보인다.
- 기법의 변형으로서 주소 지정 시에 비대칭(asymmetry)을 사용할 수도 있다.
- 송신자만 수신자 이름을 지명하며, 수신자는 송신자의 이름을 제시할 필요가 없다.
send(P, message)
: 프로세스 P에 메시지를 전송한다.receive(id, message)
: 임의의 프로세스로부터 메시지를 수신한다. 변수 id는 통신을 발생시킨 프로세스의 이름으로 설정된다.
- 간접(indirect) 통신에서 메시지들은 메일박스(mailbox) 또는 포트(port)로 송신되고, 그것으로부터 수신된다.
- 메일박스(mailbox) : 추상적으로 프로세스들에 의해 메시지들이 넣어지고, 메시지들이 제거될 수 있는 객체라고 볼 수 있다.
- 포트(port) : 운영체제 통신의 종단점. 네트워크 서비스나 특정 프로세스를 식별하는 논리 단위 1
- 각 메일박스는 고유의 id를 가진다.
- 두 프로세스들이 공유 메일박스를 가질 때만 이들 프로세스가 통신할 수 있다.
send(A, message)
: 메일박스 A로 메시지를 송신한다.receive(A, message)
: 메일박스 A로부터 메시지를 수신한다.
-
이 방법에서 통신 연결은 다음의 성질을 가진다.
- 한 쌍의 프로세스들 사이의 연결은 이들 프로세스가 공유 메일박스를 가질 때만 구축된다.
- 연결은 두 개 이상의 프로세스들과 연관될 수 있다.
- 통신하고 있는 각 프로세스 사이에는 다수의 서로 다른 연결이 존재할 수 있고, 각 연결은 하나의 메일박스에 대응된다.
- 메일박스는 한 프로세스 또는 운영체제에 의해 소유될 수 있다.
- 메일박스가 한 프로세스에 의해 소유된다면, 소유자와 메일박스의 사용자를 구분할 수 있다.
- 메일박스는 프로세스의 주소 공간의 일부이다.
- 소유자는 이 메일박스로부터 메시지를 수신만 가능한 프로세스이다.
- 메일박스의 사용자는 메일박스에 메시지를 송신만 할 수 있는 프로세스이다.
- 메일박스를 소유하고 있는 프로세스가 종료할 때 메일박스는 사라진다.
- 그 후에 이 메일박스로 메시지를 송신하는 모든 프로세스는 더는 메일박스가 존재하지 않는다는 사실을 반드시 통보받아야 한다.
- 반면에, 운영체제가 소유한 메일박스는 자체적으로 존재한다.
- 이것은 독립적인 것으로 어떤 특정한 프로세스에 예속되지 않는다.
3.6.2 Synchronization
- 메시지 전달은 블록형(blocking)이거나 비블록형(nonblocking) 방식으로 전달된다. 이 두 방식은 각각 동기식(synchronous)과 비동기식(asynchronous)이라고도 알려져 있다.
- blocking send : 송신하는 프로세스는 메시지가 수신 프로세스 또는 메일박스에 의해 수신될 때까지 블록된다.
- nonblocking send : 송신하는 프로세스가 메시지를 보내고 작업을 재시작한다.
- blocking receive : 메시지가 이용 가능할 때까지 수신 프로세스가 블록된다.
- nonblocking receive : 송신하는 프로세스가 유효한 메시지 또는 null을 받는다.
- send()와 receive()가 모두 블록형일 때, 송신자와 수신자 간에 랑데부(rendezvous)를 하게 된다.
- 랑데부(rendezvous) : 두 개 이상의 프로세스를 네트워크에 어떠한 수동 설정 없이 연결한다는 의미이다. 3
3.6.3 Buffering
- 통신이 직접적이든 간접적이든 간에, 통신하는 프로세스들에 의해 교환되는 메시지는 임시 큐(queue)에 들어 있다.
- 이러한 큐를 구현하는 방식은 3가지가 있다.
- 무용량(zero capacity) : 큐의 최대 길이가 0이다. 즉, 링크는 자체 안에 대기하는 메시지들을 가질 수 없다. 이 경우, 송신자는 수신자가 메시지를 수신할 때까지 기다려야 한다.
- 유한 용량(bounded capacity) : 큐는 유한한 길이 n을 가진다. 즉, 최대 n개의 메시지가 그 안에 들어 있을 수 있다. 링크가 만약 만원이면, 송신자는 큐 안에서 공간이 이용 가능할 때까지 반드시 블록되어야 한다.
- 무한 용량(unbounded capacity) : 큐는 잠재적으로 무한한 길이를 가진다. 따라서 메시지들이 얼마든지 큐 안에서 대기할 수 있다. 송신자는 절대 블록되지 않는다.
3.7 Examples of IPC Systems
- 이 부분은 예시이므로 생략한다.
3.8 Communication in Client-Server Systems
- 클라이언트 서버에서 사용할 수 있는 두 가지 다른 통신 전략에 대해 설명한다.
3.8.1 Socket
- 소켓(socket)은 통신의 극점(endpoint)을 뜻한다.
- 두 프로세스가 네트워크상에서 통신을 하려면 양 프로세스마다 하나씩, 총 2개의 소켓이 필요해진다.
- 각 소켓은 IP 주소와 포트 번호 2가지를 접합(concatenate)해서 구별한다.
- 일반적으로 소켓은 클라이언트-서버(client-server) 구조를 사용한다.
- 서버는 지정된 포트에 클라이언트 요청 메시지가 도착하기를 기다리게 된다.
- 요청이 수신되면 서버는 클라이언트 소켓으로부터 연결 요청을 수락함으로써 연결이 완성된다.
- Telent, ftp 및 http 등의 특정 서비스를 구현하는 서버는 well-known 포트로부터 메시지를 기다린다.
- SSH 서버 : 22번 포트
- FTP 서버 : 21번 포트
- HTTP 서버 : 80번 포트
- 1024 미만의 모든 포트는 well-known 포트로 간주되며 표준 서비스를 구현하는 데 사용된다.
- Java는 3가지 종류의 소켓을 제공한다. 연결 기반(connection-oriented) TCP(Transmission Control Protocol) 소켓은 Socket 클래스로 구현된다. 비연결성(Connectionless) UDP(User Datagram Protocol) 소켓은 DatagramSocket 클래스를 사용한다. MulticastSocket 클래스는 DatagramSocket 클래스의 서브클래스이다.
- Multicast 소켓은 데이터를 여러 수신자에게 보낼 수 있다.
- 다음 예제 프로그램은 연결 기반 TCP 소켓을 사용하는 data 서버를 설명한다.
// Data server
import java.net.*;
import java.io.*;
public class DateServer
{
public static void main(String[] args) {
try {
// 서버는 포트 6013을 listen 한다는 것을 가정하는 ServerSocket를 생성한다.
ServerSocket sock = new ServerSocket(6013);
/* now listen for connections */
while (true) {
/*
accept() 메소드를 이용하여 listen 하게 된다.
서버는 accept() 메소드에서 클라이언트가 연결을 요청할 때까지 블록된다.
연결 요청이 들어오면 accept()는 클라이언트와 통신하기 위해 사용할 수 있는 소켓을 반환한다.
*/
Socket client = sock.accept();
// PrintWriter 객체는 추후 클라이언트와 통신하는 데 사용된다.
PrintWriter pout = new
PrintWriter(client.getOutputStream(), true);
// PrintWriter 객체는 서버가 print()나 println()과 같은 루틴을 서서 소켓에 데이터를 쓸 수 있게 한다.
pout.println(new java.util.Date().toString());
/* close the socket and resume */
/* listening for connections */
client.close();
}
}
catch (IOException ioe) {
System.err.println(ioe);
}
}
}
- 클라이언트는 소켓을 생성하고 서버가 listen 하는 포트와 연결함으로써 서버와 통신을 시작한다.
- listen() : 클라이언트의 연결요청을 기다리는 함수 [^6]
- IP 주소 127.0.0.1은 루프백(loopback)을 나타내는 특별한 IP 주소이다.
- 컴퓨터가 IP 주소 127.0.0.1을 참조하면 그 자신 기계를 지칭하는 것이다.
- 이처럼 하면 같은 기계에 있는 클라이언트와 서버가 TCP/IP 프로토콜을 사용하여 통신하게 된다.
// Date Client
import java.net.*;
import java.io.*;
public class DateClient
{
public static void main(String[] args) {
try {
/*
클라이언트는 Socket을 생성하고 IP 주소 127.0.0.1에 있는 포트 6013의 서버와 연결해주기를 요청한다.
*/
Socket sock = new Socket("127.0.0.1",6013);
InputStream in = sock.getInputStream();
BufferedReader bin = new
BufferedReader(new InputStreamReader(in));
/* read the date from the socket */
String line;
while ( (line = bin.readLine()) != null)
System.out.println(line);
/* close the socket connection*/
sock.close();
}
catch (IOException ioe) {
System.err.println(ioe);
}
}
}
- 소켓을 이용한 통신은 분산된 프로세스 간에 널리 사용되고 효율적이기는 하지만 너무 낮은 수준이다.
- 우선 소켓은 스레드 간에 구조화되지 않은 바이트 스트림만을 통신하도록 하기 때문이다.
3.8.2 Remote Procedure Calls, RPC
- RPC는 네트워크에 연결된 두 시스템 사이의 통신에 사용하기 위하여 프로시저 호출 기법을 추상화(abstract)하는 방법으로 설계되었다.
- 원격 프로시저 호출(Remote Procedure Call, RPC) : 별도의 원격 제어를 위한 코딩 없이 다른 주소 공간에서 함수나 프로시저를 실행할 수 있게 하는 프로세스 간 통신 기술이다. 즉, 원격 프로시저 호출을 이용하면 프로그래머는 함수가 실행 프로그램에 로컬 위치에 있든 원격 위치에 있든 동일한 코드를 이용할 수 있다. 2
- 프로시저(procedure) : 명령 단위가 수행하는 절차
- RPC는 IPC 기반 위에 만들어졌지만 여기서는 프로세스들이 서로 다른 시스템 위에서 돌아가기 때문에 원격 서비스를 제공하기 위해서는 메시지 기반 통신을 해야 한다.
- IPC 방식과는 달리 RPC 통신에서 전달되는 메시지는 구조화되어 있고, 따라서 데이터의 패킷 수준을 넘어서게 된다.
- 각 메시지에는 원격지 포트에서 listen 중인 RPC 데몬의 주소가 지정되어 있고 실행되어야 할 함수의 식별자, 그리고 그 함수에게 전달되어야 할 파라미터가 포함된다.
- 그런 후에 요청된 함수가 실행되고 어떤 출력이든지 별도의 메시지를 통해 요청자에게 반환된다.
- 포트(port)는 단순히 메시지 패키지의 시작 부분에 포함되는 정수이다.
- 원격 프로세스가 어떤 서비스를 받고자 하면 그 서비스에 대응되는 적절한 포트 주소로 메시지를 보내야 한다.
이미지출처 [^4]
- RPC는 클라이언트가 원격 호스트의 프로시저 호출하는 것을 마치 자기의 프로시저 호출하는 것처럼 해준다.
- RPC 시스템은 클라이언트 쪽에 스텁(stub)을 제공하여 통신을 하는 데 필요한 자세한 사항들을 숨겨 준다.
- 클라이언트가 원격 프로시저를 호출하면 RPC는 그에 대응하는 스텁을 호출하고 원격 프로시저가 필요로 하는 파라미터를 건네준다.
- 그러면 스텁이 원격 서버의 포트를 찾고 파라미터를 정돈(마샬, marshall)한다.
- 이에 대응되는 스텁이 서버에도 존재하여 서버 측 스텁이 메시지를 수신한 후 적절한 서버의 프로시저를 호출한다.
- 파라미터 정돈(parameter marshalling)은 클라이언트와 서버 기기의 데이터 표현 방식의 차이 문제를 해결한다.
- 이런 차이를 해결하기 위해 대부분의 RPC 시스템은 기종 중립적인(machine-independent) 데이터 표현 방식을 정의한다. XDR(external data representation)이 이러한 표현 방식 중 하나이다.
- 클라이언트 측에서는 서버에게 데이터를 보내기 전 파라미터 정돈(marshalling) 작업의 일환으로 전송할 데이터를 기종 중립적인 XDR 형태로 바꾸어서 보낸다.
- 수신측 기계에서는 XDR 데이터를 받으면 파라미터를 풀어내면서(unmarshaled) 자기 기종의 형태로 데이터를 바꾼(convert) 후 서버에게로 넘겨준다.
- 클라이언트와 서버 간의 통신 문제를 고려해봐야 한다.
- 일반적인 프로시저 호출의 경우, 바인딩(binding)이라는 작업이 링킹/적재/실행 시점에 행해진다.
- 바인딩(binding) : 프로그램에 사용된 구성 요소의 실제 값 또는 속성을 결정짓는 행위를 의미한다. 예를 들어 함수를 호출하는 부분에서 실제 함수가 위치한 메모리를 연결하는 것도 바인딩에 해당한다. [^5]
- 이때 프로시저의 이름이 프로시저의 메모리 주소로 변환된다.
- 이와 마찬가지로 RPC도 클라이언트와 서버의 포트를 바인딩 해야 하는데, 클라이언트는 서버의 포트 번호를 어떻게 알 수 있는가?
- 이를 위해 2가지 방법이 사용된다.
- 고정된 포트 주소 형태로 미리 정해 놓는 방법이다.
- 컴파일 할 때 RPC에는 이 고정된 포트 번호를 준다.
- 컴파일 되고 나면 그 후에는 서버가 그 포트 번호를 임의로 바꿀 수 없다.
- 랑데부(rendezvous) 방식에 의해 동적(dynamic)으로 바인딩한다.
- 보통 운영체제는 미리 정해져 있는 고정 RPC 포트를 통해 랑데부용 데몬(matchmaker라고 불림)을 제공한다.
- 그러면 클라이언트가 자신이 실행하기를 원하는 RPC 이름을 담고 있는 메시지를 랑데부 데몬에게 보내서, RPC 이름에 대응하는 포트 번호가 무엇인지 알려달라고 요청한다.
- 그러면 포트 번호가 클라이언트에게 반환되고, 클라이언트는 그 포트 번호로 RPC 요청을 계속 보낸다.
- RPC는 분산 파일 시스템(DFS)을 구현하는 데 유용하다.
댓글남기기