프로세스 개념
•
운영체제에서 CPU의 활동들을 작업(job)이나 사용자 프로그램, 태스크(task)라고 불렀었다.
•
멀티 테스킹을 지원하지 않는 임베디드 장치에서 하나의 프로그램만 실행 시키거나, 멀티 테스킹을 통해 하나의 시스템에서 여러 프로그램들을 한 번에 실행킬 때 운영체제는 프로그램을 위해 자원을 할당하거나 메모리를 관리하는 기능을 제공할 수 있다. 이러한 모든 활동을 프로세스라 부른다.
1.
프로세스
•
프로세스란 실행 중인 프로그램으로 볼 수 있다. 운영체제는 프로세스가 생성될 때 프로세스를 위해 다음과 같이 메모리를 배치한다.
◦
텍스트 섹션 : 프로그램의 실행 코드가 저장되는 위치로 프로그램 실행 시간 동안 크기가 변하지 않는다.
◦
데이터 섹션 : 전역 변수 저장되는 위치로 프로그램 실행 시간 동안 크기가 변하지 않는다.
◦
힙 섹션 : 프로그램 실행 중 동적으로 할당되는 메모리로 메모리가 동적으로 할당됨에 따라 힙 영역이 커진다.
◦
스택 섹션 : 함수를 호출할 때 매개변수나 복귀 주소, 지역 변수를 포함하는 활성화 레코드를 스택에 저장한다.
•
스택 영역과 힙 영역은 서로의 방향으로 메모리를 저장하는데, 메모리 위치를 서로 침범하지 않도록 운영체제에서 관리한다.
•
프로그램은 명령어 리스트를 내용으로 가지는 디스크에 저장된 실행 파일로, 프로그램 그 자체는 프로세스가 아니다.
•
프로세스는 프로그램 카운터와 관련 자원의 집합을 가지고, 프로그램 실행 파일이 메모리에 로드되면 프로세스가 된다.
2.
프로세스의 상태
•
프로세스는 해당 프로세스의 현재 활동에 따라 그 상태가 변한다.
•
생성(new) : 프로세스가 생성 중인 상태
•
실행(running) : 명령어들이 실행되고 있는 상태
•
대기(waiting) : 프로세스가 어떤 이벤트(입출력 완료 혹은 인터럽트 수신)가 발생하기를 기다리는 상태
•
준비(ready) : 프로세스가 프로세서에 할당되는 것을 기다리고 있는 상태
•
종료(terminated) : 프로세스의 실행이 종료된 상태
3.
프로세스 제어 블록
•
각 프로세스는 운영체제에서 프로세스 제어 블록(Process Control Block, PCB)으로 관리된다.
•
프로그램 카운터(PC, Program Counter) : 프로세스가 다음 실행할 명령어의 주소를 가리킨다.
•
CPU 레지스터 집합 : 누산기(accumulator), 인덱스 레지스터, 스텍 레지스터 등 프로세스가 스케줄링할 때 올바르게 실행되도록하기 위해 인터럽트 발생 시 저장되는 값이다.
•
CPU 스케줄링 정보 : 프로세스 우선순위, 스케줄 큐의 포인터 등 스케줄링에 관련된 정보를 저장한다.
•
메모리 관리 정보 : 운영체제에 의해 사용되는 메모리 시스템에 따라 기준(base) 레지스터와 한계(limit) 레지스터 값, 페이지 테이블, 세그먼트 테이블 등의 정보를 저장한다.
•
회계(accounting) 정보 : CPU 사용 시간과 관련해서 실시간, 시간 제한, 계정 번호, 프로세스 번호 등을 포함한다.
•
입출력 상태 정보 : 프로세스에 할당된 입출력 장치들과 열린 파일 목록 등을 포함한다.
4.
스레드
•
앞서 공부했던 프로세스 개념들은 프로세스가 단일 스레드를 실행하는 프로그램을 가정했다. 단일 제어 스레드는 프로세스가 한 번에 한 가지 일만을 수행하는데, 최근의 대부분 운영체제는 하나의 프로세스가 다수의 실행 스레드를 가질 수 있다.
프로세스 스케줄링
•
다중 프로그래밍은 CPU의 이용을 최대화 하기 위해 항상 어떤 프로세스가 실행되어 있도록 하는 것이 목표인데, CPU 스케줄링은 이렇게 CPU의 이용을 최대화 하기 위해 CPU 코어에 실행되는 프로세스를 빈번하게 교체하는 것이다.
•
프로세스 스케줄러는 코어에서 실행 가능한 여러 프로세스 중 하나의 프로세스를 선택하여 실행시킨다.
•
메모리에 로드되어 있는 프로세스의 수를 다중 프로그래밍 정도라고 한다.
1.
스케줄링 큐
•
프로세스가 시스템에 들어가면 준비 큐(Ready Queue)에 들어가 준비 상태가 되고 CPU 코어에서 실행되기를 기다린다. 준비 큐 헤더에는 리스트의 첫 PCB에 대한 포인터가 저장되고 각 PCB는 준비 큐의 다음 PCB를 가리키는 연결 리스트가 구성된다.
•
시스템에는 입출력 장치에 입출력 요청을 걸고 입출력이 완료되기를 기다리는 것과 같이 특정 이벤트가 발생하기를 기다리는 대기 큐(Wait Queue)도 포함된다.
•
프로세스 스케줄링은 일반적으로 아래와 같이 큐잉 다이어그램으로 표현된다.
◦
새 프로세스는 생성되면 준비 큐에 놓이게 되고, 디스패치 될 때까지 기다린다.
◦
프로세스에 입출력이 필요하다면 입출력 장치에 요청을 하고 입출력 대기 큐에 들어간다.
◦
프로세스 스케줄링에 따른 할당된 CPU 점유 시간(time slice)이 끝나면 다시 준비 큐로 들어가게 된다.
◦
프로세스가 자식 프로세스를 만들고 자식 프로세스가 종료되기를 기다려야 하는 상황이면 그동안 대기 큐에 들어가 있게 된다.
•
프로세스가 종료되면 종료 시점에 모든 큐에서 제거되고 PCB 및 할당된 자원들이 반환된다.
2.
CPU 스케줄링
•
CPU 스케줄러는 준비 큐에 있는 프로세스들 중 하나를 선택하여 CPU 코어에 할당하는데, 어느 프로세스를 선택할 지와 해당 프로세스를 얼마만큼 실행시킬 지를 결정하는 과정을 CPU 스케줄링이라 부른다.
•
일부 운영체제에서는 일부 프로세스를 swap 영역에 프로세스를 저장해두었다가 나중에 다시 불러와서 동작시키는 스와핑(swaping)의 중간 형태의 스케줄링을 하기도 한다.
3.
문맥 교환(context switching)
•
인터럽트는 운영체제가 CPU 코어를 현재 작업에서 뺏어 커널 루틴을 실행시킬 수 있다. 이렇게 인터럽트가 발생하면 시스템은 인터럽트 처리가 끝난 후 문맥을 복구할 수 있도록 현재 실행 중인 프로세스의 현재 상태를 저장(state save)하고, 인터럽트 처리가 끝난 후 프로세스의 마지막 상태를 복구(state restore)한다.
•
인터럽트 뿐만 아니라 CPU 코어를 다른 프로세스로 교환하는 과정에서도 이러한 문맥 교환(context switching)이 발생한다. 문맥 교환이 발생하면 커널은 프로세스의 PCB에 현재 상태(문맥)을 저장하고, 실행할 프로세스의 PCB에 저장된 상태(문맥)을 복구한다.
•
이러한 문맥 교환 과정은 문맥 교환이 진행되는 동안 시스템이 아무런 일을 할 수가 없기 때문에 순수한 오버헤드이고, 문맥 교환의 속도와 시간은 하드웨어마다 다르다.
프로세스에 대한 연산
1.
프로세스 생성
•
프로세스는 실행되는 동안 여러 개의 새로운 프로세스를 생성할 수 있고, 이렇게 생성하는 프로세스를 부모 프로세스라 부르고 새로운 프로세스는 자식 프로세스라 부른다. 이렇게 생성된 프로세스도 마찬가지로 다른 프로세스들을 생성할 수 있고, 그로인한 부모/자식 관계로 프로세스의 트리 구조를 형성한다.
•
UNIX나 Linux, Windows 같은 대부분 운영체제들은 프로세스마다 고유의 프로세스 식별자(PID)를 할당하여 프로세스를 구분한다.
•
프로세스가 새로운 프로세스를 생성할 때, 부모와 자식 프로세스를 병행하게 실행하는 방법과 부모가 자식 프로세스들의 일부나 전체가 실행을 종료할 때까지 기다리는 방법이 있다.
•
프로세스를 주소 공간 측면에서 보면 자식 프로세스는 부모 프로세스와 동일한 프로그램과 데이터를 가질 수 있고, 또 자식 프로세스가 자신에게 로드될 새로운 프로그램을 가지고 있을 수 있다.
•
UNIX 운영체제에서는 fork() 시스템 콜을 하게되면 부모 프로세스의 주소 공간을 복사하여 자식 프로세스를 생성하게 되고, exec() 시스템 콜을 하게되면 자신의 메모리 공간을 새로운 프로그램으로 교체한다.
2.
프로세스 종료
•
프로세스의 종료는 프로세스가 마지막 문장의 실행을 끝내고 exit 시스템 콜을 호출하여 운영체제에 삭제를 요청하면 프로세스에 할당된 모든 자원이 해제되고 종료된다. 부모 프로세스가 wait 시스템 콜로 기다리고 있다면, 종료된 자식 프로세스의 상태 값을 반환할 수 있다.
•
일부 시스템에서는 부모 프로세스가 종료한 이후 자식 프로세스가 존재할 수 없도록 하는데, 이러한 시스템에서 하나의 프로세스가 종료되면 그 프로세스의 모든 자식 프로세스들이 종료된다. 이러한 종료를 연쇄식 종료(cascading termination)이라 부르며 운영체제에서 실행된다.
•
프로세스가 종료하면 사용하던 자원은 운영체제가 회수하는데, 프로세스의 종료 상태가 저장되는 프로세스 테이블은 부모 프로세스가 wait()을 호출할 때까지 남아 있게 된다. 종료되었지만 부모 프로세스가 아직 wait()을 호출하지 않아 종료되지 않은 것처럼 보여지는 프로세스를 좀비(zombie) 프로세스라 부르고, 좀비 프로세스로 존재하는 기간은 일반적으로 매우 짧다.
•
부모 프로세스가 자식 프로세스를 생성하고 자식 프로세스보다 먼저 종료하게 되면, 자식 프로세스는 고아(orphan) 프로세스가 된다. UNIX는 고아 프로세스를 init 프로세스의 자식 프로세스로 지정함으로써 이 문제를 해결한다.
3.
안드로이드 프로세스
•
전경(forground) 프로세스 : 사용자가 현재 상호작용 하고 있는 응용 프로그램으로, 화면에 보이는 현재 프로세스
•
가시적(visible) 프로세스 : 전경에서 직접 볼 수 없지만 전경 프로세스에 표시되는 활동을 하는 프로세스
•
배경(background) 프로세스 : 활동을 수행하고 있지만 사용자가 인식하지 못하는 프로세스
•
서비스(service) 프로세스 : 백그라운드 프로세스와 유사하지만 사용자가 인지할 수 있는 활동(음악 스트리밍 등)
•
빈(empty) 프로세스 : 응용 프로그램과 관련된 활성 구성요소가 없는 프로세스
•
시스템 자원을 회수할 때는 프로세스가 서비스를 제공하고 가시적인가 등에 따라 프로세스의 중요도 순위로 프로세스를 종료한다. (빈 프로세스를 먼저 종료하고 백그라운드 프로세스를 종료한다.)
프로세스 간 통신(IPC, Inter-Process Communication)
•
운영체제 내에서 실행되는 프로세스들은 실행 중인 다른 프로세스들과 데이터를 공유하지 않는 독립적 환경이다.
•
프로세스는 정보 공유, 계산 가속화, 모듈성을 위해 다른 프로세스들과 자료를 공유할 필요가 있다.
•
프로세스들은 서로 데이터를 교환하기 위해서 프로세스 간 통신(IPC, interprocess communication) 기법이 필요하다.
•
프로세스 간 통신에는 기본적으로 공유 메모리(shared memory)와 메세지 전달(message passing) 두 가지 모델이 있다.
•
메세지 전달 방식은 충돌을 회피할 필요가 없기 때문에 적은 양의 데이터를 교환하는데 유용하고, 분산 시스템에서 공유 메모리보다 구현하기 쉽다. 반면 메세지 전달은 시스템 콜을 통해 구현되므로, 커널 간섭 등의 오버헤드로 공유 메모리 방식보다 느리다. 공유 메모리 방식은 공유 메모리 영역을 구축할 때만 시스템 콜을 사용하고 메모리 접근에는 커널의 도움이 필요하지 않아 더 빠르다.
공유 메모리를 통한 IPC
•
공유 메모리를 사용해서 프로세스 간 통신을 하기 위해서는 공유 메모리 세그먼트를 생성하는 프로세스의 주소 공간에 공유 메모리 영역을 구축해야한다. 해당 공유 메모리 영역을 통해 통신하려는 프로세스들은 이 세그먼트를 자신의 주소 공간에 추가해야 한다
•
일반 적으로 운영체제는 하나의 프로세스가 다른 프로세스의 메모리에 접근하는 것을 허용하지 않는데, 공유 메모리는 둘 이상의 프로세스가 위의 제약 사항을 받지 않는다는 것에 동의하는 과정이 필요하다.
•
생산자-소비자 문제를 적용해보면, 생산자 프로세스는 정보를 생산하고 소비자 프로세스는 정보를 소비한다. 생산자와 소비자 프로세스들이 병행으로 실행되도록 하려면, 생산자와 소비자가 공유하는 메모리 버퍼를 두면 된다. 생산자와 소비자는 반드시 동기화 되어야 생산되지 않은 항목을 소비하려 시도하는 경우가 발생하지 않는다.
•
버퍼에는 버퍼의 크기에 제한이 없는 무한 버퍼(unbounded buffer)와 버퍼의 크기가 고정되어 있는 유한 버퍼(bounded buffer)가 있다. 공유 버퍼는 저장할 위치와 소비할 위치인 두 개의 논리 포인터를 가진 원형의 배열 형태로 구현된다.
메세지 전달을 통한 IPC
•
메세지 전달 방식은 통신하는 프로세스들이 네트워크에 연결된 다른 컴퓨터의 프로세스인 경우처럼 분산 환경에서 유용하다. 메세지 전달 시스템은 send(message)와 receive(message) 두 가지 연산을 통해 구현된다.
1.
명명
•
직접 통신에서는 통신하는 각 프로세스들이 통신의 수신자와 송신자의 이름을 명시해야 한다.
•
이런 직접 통신 기법은 송신자와 수신자 프로세스가 서로 상대방의 이름을 제시해야 한다는 대칭성을 가질 수도 있고, 송신자만 수신자의 이름을 명시하고 수신자는 송신자를 알 필요가 없는 비대칭성을 가지게 할 수도 있다.
•
이런 직접 통신 기법은 모듈성을 제한한다는 단점이 있다. 프로세스의 이름을 바꾸면 모든 다른 프로세스에서 다른 프로세스의 이름 부분을 확인하여 수정해야한다.
•
간접 통신에서는 메세지를 메일박스(mailbox)나 포트(port)로 송신하고, 다른 프로세스에서도 메일박스나 포트를 통해 송신된 메세지를 받는다.
2.
동기화
•
봉쇄형(blocking) 방식은 송신 프로세스가 메세지를 보낸 후 수신 프로세스에서 해당 메세지를 읽을 때까지 메일박스가 봉쇄되어 송신 프로세스가 메세지를 더 보낼 수 없는 방식이다. 반대로 수신 프로세스는 메일박스에 메세지가 들어와 읽을 수 있을 때까지 봉쇄된다.
•
비봉쇄형(non-blocking) 방식은 송신 프로세스나 수신 프로세스는 메일박스의 봉쇄 없이 자유롭게 작업을 처리하는 방식이다.
•
송신 프로세스가 봉쇄형 보내기 방식이고 수신 프로세스가 봉쇄형 받기 방식을 사용한다면, 송신자와 수신자 간에 랑데부(rendezvous)를 하게 된다.
3.
버퍼링
•
직접 통신이든 간접 통신이든 메세지 전달 방식은 프로세스간 통신할 때 메세지를 임시 큐에 넣어둔다. 큐를 구현하는 방법은 버퍼가 없는 메세지 시스템인 무용량, 자동 버퍼링이라 불리는 유한 용량과 무한 용량 구조로 구현할 수 있다.
•
무용량(zero capacity) : 큐의 최대 길이가 0인 구조로, 송신자는 수신자가 메세지를 수신할 때까지 기다려야 한다.
•
유한 용량(bounded capacity) : 큐가 n개의 유한한 길이를 가지는 구조로, 최대 n개의 메세지가 큐 안에 들어 있을 수 있다.
•
무한 용량(unbounded capacity) : 큐가 잠재적으로 무한한 길이를 가지는 구조로, 메세지를 제한 없이 큐에 넣을 수 있다.
IPC 시스템의 예시
1.
POSIX 공유 메모리
•
POSIX 시스템에서는 공유 메모리를 위한 API를 제공한다. 공유 메모리는 메모리의 특정 영역을 파일과 연관시키고(memory-mapped), 공유 메모리 객체를 메모리에 매핑하여 메모리 객체를 통해 데이터를 쓰고 읽는다.
2.
Mach 메세지 전달
•
Mach 커널은 태스크 간 통신을 포함하여 대부분의 통신을 메세지 전달 방식으로 수행한다. 포트(port)라고 불리는 메일박스를 통해 메세지를 주고 받는다.
3.
Windows
•
Windows는 서브시스템을 지원하고, 응용 프로그램은 메세지 전달 기법을 통해 통신한다. Windows의 메세지 전달 장치는 고급 로컬 프로시저 호출 설비(ALPC, advanced local procedure call facility)라 불린다.
4.
파이프
•
일반 파이프
◦
일반 파이프는 생산자-소비자 형태로 두 프로세스 간의 통신을 하는데, 생산자는 파이프의 쓰기 측에 데이터를 쓰고 소비자는 읽기 측에서 데이터를 꺼내 읽는다. 이런 구조로 일반 파이프는 단방향으로만 데이터를 전송할 수 있고, 양방향 통신이 필요하다면 2개의 파이프를 사용해야한다.
◦
Windows 시스템에서 일반 파이프는 익명 파이프(anonymous pipe)라고 불리고, 부모-자식 프로세스에서만 사용된다.
•
지명 파이프(named pipe)
◦
지명 파이프는 양방향 통신이 가능하며, 파이프를 파일 시스템의 보통 파일처럼 저장해 해당 이름으로 여러 프로세스에서 접근 가능하기 때문에 부모-자식 관계가 아닌 프로세스 간에도 사용 가능하다.
◦
지명 파이프는 통신 프로세스가 종료되면 사라지는 일반 파이프와는 달리 계속 존재할 수 있으며, 하나의 파이프에 다수의 writer를 가질 수 있다.
클라이언트 서버 환경에서의 통신
1.
소켓
•
소켓(socket)은 통신의 극점(endpoint)를 의미하는데, 소켓마다 IP 주소와 포트 번호를 가지고 있다.
•
일반적으로 소켓은 클라이언트-서버 구조를 사용하여, 서버에 지정된 포트에 클라이언트 요청이 들어올 때까지 기다리고 요청이 수신되면 서버는 클라이언트 연결 요청을 수락하여 연결된다.
•
클라이언트 프로세스가 연결을 요청하면 호스트 컴퓨터가 1024보다 큰 임의의 정수인 포트 번호를 부여하고, 해당 포트 번호를 따라 패킷을 교환한다.
•
소켓을 이용한 통신은 분산된 환경에서 널리 사용되고 효율적이지만, 스레드 간 구조화되지 않은 바이트 스트림만 통신하도록하기 때문에 너무 낮은 수준의 통신 방법이다.
2.
원격 프로시저 호출(RPC, Remote Procedure Calls)
•
네트워크에 연결된 두 시스템 사이의 통신에 사용하기 위해, 프로시저 호출 기법을 추상화하여 IPC 기반 위에 설계한 기법이다.
프로시저
루틴, 서브루틴, 함수와 같은 뜻으로 사용되며, 하나의 프로시저는 특정 작업을 수행하기 위한 프로그램의 일부분이다.
•
별도의 원격 제어를 위한 코딩 없이, 원격에 있는 함수나 프로시저를 실행할 수 있게 해주는 통신 방법이다. 클라이언트에서 원격 서버와 IPC를 처리하는 로컬 메소드를 호출하는데, 프로그래머가 로컬이든 원격이든 동일한 함수나 프로시저를 호출하여 사용할 수 있다.
•
프로세스들이 서로 다른 시스템 위에서 돌아가기 때문에 원격 서비스를 제공하려면 메세지 기반 통신을 해야한다. 그로 인해 데이터 패킷 뿐만 아니라 메세지 전송도 가능하다.
•
RMI(Remote Method Invocation)
◦
RPC를 객체지향으로 구현한 것으로, 서버 측은 skeleton이 클라이언트 측은 stub이 필요하다. 클라이언트의 stub에서 데이터가 Marshalling 되어 전송되면 Skeleton에서 Unmarshalling하여 원래의 형태로 복원한다.
•
Stub
◦
원격에 존재하는 프로그램을 대신하는 작은 루틴으로, 클라이언트에서 각 Remote Procedure마다 별도의 stub이 존재한다. 서버의 port를 찾고 데이터를 marshalling 하는 역할을 한다.
•
Skeleton
◦
stub과 비슷한 역할을 하는 서버의 보조 객체이다.
•
Marshalling / Unmarshalling
◦
Marshalling은 데이터를 바이트로 쪼개서 TCP/IP 같은 통신 채널을 통해 전송될 수 있도록 형태를 바꿔주는 과정을 말한다.
◦
Unmarshalling은 반대로 전송 받은 데이터를 원래의 형태로 복원하는 과정을 말한다.
◦
기계마다 little-endian / big-endian의 적용이 다르듯, 기계 간의 데이터 처리 차이를 해결하게 위해 기종 중립적인 데이터 표현 방식인 XDR(external data representation)을 사용한다.
•
RPC가 실패하는 경우에도 클라이언트가 정보를 받을 수 있게하는 로직이 존재한다.
◦
Exactly-Once
▪
메세지에 TimeStamp를 붙여서 받았는지 여부를 확인하는 방식으로, 서버에서 모든 메세지에 대한 TimeStamp를 가지고 있는다.
▪
서버에 존재하지 않는 TimeStamp의 경우에는 메세지를 처리하고, 그렇지 않으면 해당 메세지를 무시한다.
◦
At Most Once
▪
서버가 메세지를 받을 때 ACK 메세지를 클라이언트에 전달한다.
•
클라이언트에서 포트를 알아내는 방법은 2가지가 존재한다.
◦
포트를 고정하고 컴파일할 때 RPC에 이 고정된 포트를 주는 방법이다. 컴파일 되고 난 후 서버는 그 포트 번호를 임의로 바꿀 수 없다.
◦
MatchMaker라는 랑데부용 Daemon을 사용하는 방법으로, MatchMaker에 RPC의 포트 번호를 요청하고 클라이언트에 이 포트 번호를 반환하는 방식이다. 초기 오버헤드가 있지만 위의 방식보다는 더 유연한 방식이다.
•
RPC는 분산 파일 시스템(DFS, Disturbed File System)을 구현하는데 유용하다.
3.
Android RPC
•
Android 운영체제는 바인더 프레임워크에 여러 IPC 기법을 포함하고 있는데, 그 중 RPC 또한 제공을 한다.
•
Android는 응용 프로그램 구성요소를 기본 빌딩 블록으로 정의하고, 어플리케이션은 여러 응용 프로그램 구성요소를 결합하여 필요한 기능을 구현할 수 있다.
•
응용 프로그램 구성요소 중 하나로 서비스가 있다. 서비스는 음악 스트리밍 같이 사용자 인터페이스가 없지만 백그라운드로 실행되며 장기 실행 연산을 하거나 원격 프로세스에 대한 작업을 수행한다. 이러한 서비스 메소드를 RPC를 통해 호출하면 해당 서비스가 바운드되어 클라이언트-서버 통신을 제공할 수 있다.