프로세스
초기의 컴퓨터 시스템은 한번의 하나의 프로그램만 실행하도록 허용했다.
오늘날 컴퓨터는 메모리에 다수의 프로그램들이 적재되어 병행 실행되는 것을 허용한다.
제어와 구획화의 필요성에 따라 프로세스의 개념이 생겼다.
프로세스란 실행중인 프로그램이다.
프로세스 개념
CPU활동을 지칭하는 이름
일괄처리 시스템 : Job
시분할 시스템 : 사용자 프로그램, Task
이러한 명칭들이 있지만 모두 프로세스 라고 부른다.
프로세스
프로세스는 텍스트 섹션 으로 알려진 프로그램 코드 이상의 어떤것이다.
프로세스는 다음을 포함한다
프로그램 카운터
함수의 매개변수
프로세스 지역변수의 스택
전역변수의 데이터 섹션
동적 할당되는 메모리 힙
프로그램 그 자체는 프로세스가 아니다
프로세스는 다음에 실행할 명령어를 지정하는 프로그램 카운터와 관련자원의 집합을 가진 Activity entity다. (능동적인)
실행파일이 메모리에 적재될 떄 프로그램은 프로세스가 된다.
같은 프로그램을 두 번 실행한다면 그들은 다른 프로세스로 취급된다.
프로세스 자체가 다른 코드를 위한 실행환경으로 동작할 수 있다 - 예) JVM
프로세스 상태
프로세스는 실행되면서 상태가 변한다. 다음의 상태 중 하나에 존재한다.
new : 프로세스가 생성 중이다.
running : 명령어들이 실행되고 있다.
waiting : 프로세스가 이벤트를 기다린다.
ready : 프로세스가 프로세서에 할당되길 기다린다.
terminate : 프로세스의 실행이 종료됐다.
PCB; 프로세스 제어 블록
PCB는 프로세스와 연관된 여러 정보를 수록한다.
프로세스 상태
프로그램 카운터 : 프로세스가 다음에 실행할 명령어의 주소를 가리킨다.
CPU 레지스터 : 누산기, 인덱스 레지스터, 스택 레지스터, 범용 레지스터, 상태코드 정보가 포함된다. 프로그램 카운터와 함께 다뤄진다. 인터럽트 발생시 저장된다.
CPU 스케줄링 정보 : 프로세스 우선순위, 스케줄링 큐의 포인터를 포함한다.
메모리 관리 정보 : 페이지 테이블, 세그먼트 테이블, 기준 레지스터, 한계 레지스터를 포함한다.
Account 정보 : CPU사용시간, 실시간(real-time), 시간제한, PID등을 포함한다.
입출력 상태 정보 : 프로세스에 할당된 입출력 장치들과 열림 파일의 목록을 포함한다.
스레드
하나의 프로세스가 실행중이면 하나의 명령어 스레드가 실행중이라고 할 수 있다.
하나의 스레드만 실행중이라면 프로그램 내 여러가지 기능을 동시에 실행할 수 없다.
프로세스의 개념을 확장해 한 프로세스가 다수의 실행 스레드를 가질 수 있다.
스레드는 병렬로 실행될 수 있다.
Process scheduling
다중 프로그래밍의 목적은 CPU 이용을 최대화 하기 위해 프로세스가 항상 실행되도록 한다.
시분할의 목적은 각 프로그램이 실행되는 동안 사용자가 상호작용할 수 있도록 CPU를 빈번하게 교체하는 것이다.
프로세스 스케줄러 는 이 목적을 달성하기 위해 CPU에서 실행가능한 여러 프로세스들 중에서 하나의 프로세스를 선택한다.
스케줄링 큐
프로세스가 시스템에 들어오면 잡 큐 에 들어간다. 이 잡 큐는 시스템 안의 모든 프로세스로 구성된다.
잡 큐는 메인메모리에 존재한다.
준비 완료 큐 에는 준비 완료 상태에서 실행 대기하는 프로세스들이 유지된다. (연결리스트)
준비 완료 큐의 헤더는 첫번째와 마지막 PCB를 가리킨다. 각 PCB는 다음 PCB를 가리킨다.
시스템에는 준비완료 큐 뿐만 아니라 장치 큐, 입출력 큐 등이 있다 (이건 문장이 무슨말인지 모르겠다)
프로세스는 처음 준비 완료 큐에 놓인다. CPU를 dispath할 때 까지 대기한다. CPU가 할당되면 다음중 하나가 발생할 수 있다.
입출력 요청을 해 입출력 큐에 들어갈 수 있다.
자식 프로세스를 생성하고 자식 프로세스의 종료를 기다릴 수 있다.
인터럽트 결과에 의해 강제로 CPU로부터 제거되고 준비 완료 큐에 다시 대기할 수 있다.
종료되면 모든 큐에서 삭제되고 PCB와 자원을 반납한다.
스케줄러
프로세스는 라이프사이클 동안 다양한 스케줄링 큐에 이동한다. 이 과정을 스케줄러가 수행한다.
일괄처리 시스템에서는 즉시 실행될 수 있는 것보다 더 많은 프로세스들이 종종 제출된다. 이들은 메모리에 저장되어 나중에 실행될때 까지 유지된다.
장기 스케줄러 (잡 스케줄러) : 프로세스를 실행하기 위해 메모리로 적재한다.
실행 빈도 수가 작다. 시스템에서 새로운 프로세스를 생성하는 간격이 수 분까지 될 수 있다.
입출력 중심 프로세스와 CPU 중심 프로세스가 있다. 장기 스케줄러는 이들간의 프로시스 혼합을 선택하는것이 중요하다.
단기 스케줄러 (CPU 스케줄러) : 실행준비가 완료되어있는 프로세스 들 중에서 선택해 CPU를 할당한다.
단기 스케줄러는 CPU를 위해 자주 새로운 프로세스를 선택한다. 100 밀리초 마다 한번씩 실행된다. 매우 빨라야 한다.
UNIX와 Windows는 시분할 시스템으로 장기 스케줄러가 없다. 모든 프로세스를 단기 스케줄러를 위해 메모리에 할당한다.
시분할 시스템같은 운영체제들은 중기 스케줄러를 도입했다. 메모리에서 CPU의 자원을 획득하기 위한 프로세스를 제거함으로써 완화하는 방법이다. 차후 메모리에서 다시 프로세스를 불러와 중단 지점부터 다시 실행한다. 이것을 Swapping이라고 한다.
컨텍스트 스위칭
인터럽트는 운영체제가 CPU를 현재 작업에서 빼앗아 커널 루틴을 실행 할 수 있게 한다.
인터럽트가 발생하면 시스템은 인터럽트 처리가 끝난 후 컨텍스트를 복구할 수 있도록 현재 컨텍스트를 저장할 필요가 있다.
컨텍스트는 PCB에 표현된다.
컨텍스트는 CPU레지스터의 값, 프로세스 상태, 메모리 관리 정보 등을 포함한다.
컨텍스트 스위칭 은 CPU를 다른 프로세스로 교환 하려면 이전의 프로세스 상태를 보관하고 새로운 프로세스의 보관된 상태를 복구하는 작업이다.
컨텍스트 스위칭이 일어나는 시간은 순수한 오버헤드다.
프로세스 운영
시스탬 내의 프로시스들은 병행 실행될 수 있다.
반드시 동적으로 실행되고 제거되야 한다.
운영체제는 프로세스 생성 및 종료를 위한 기법을 제공해야 한다.
프로세스 생성
실행되는 동안 프로세스는 여러개의 프로세스를 생성할 수 있다.
생성하는 프로세스를 부모 프로세스, 새로운 프로세스는 자식 프로세스라고 부른다.
새로운 프로세스들은 또 다른 프로세스를 생성할 수 있다.
결과적으로 프로세스 트리를 형성한다.
운영체제는 PID(Process identifier)를 사용해 프로세스를 구분한다.
PID는 고유한 값을 가지며 정수다.
Linux
init 프로세스는 언제나 PID가 1이다.
시스템이 부팅되면 init은 다양한 사용자 프로세스를 생성한다.
자식 프로세스를 생성할 때 자신의 임무를 위해 자원이 필요하다. 자원은 운영체제로부터 할당 받거나 부모 프로세스의 자원만을 사용하도록 제한당할 수 있다.
부모 프로세스와 자식프로세스간의 관계
부모는 자식과 병행하게 실행을 지속한다
부모는 일부 또는 모든 자식이 실행을 종료할 때 까지 기다린다.
새로운 프로세스들은 주소 공간 측면에서 두가지 가능성이 있다.
자식 프로세스는 부모 프로세스의 복사본이다 (fork())
자식 프로세스가 자신에게 적재될 새로운 프로그램을 갖고 있다.
새로운 프로세스는 원래 프로세스의 주소 공간의 복사본으로 구성된다. 그래서 부모 프로세스가 쉽게 자식 프로세스와 통신할 수 있다.
fork()의 반환값으로 부모 프로세스는 자식 프로세스의 PID를 반환받고, 자식 프로세스는 0을 반환받는다.
fork()이후 exec()를 호출해 자신의 메모리 공간을 새로운 프로그램으로 교체한다. 프로세스의 주소 공간을 새 프로그램으로 덮어 쓰기 때문에 시스템 호출은 오류가 발생하지 않는 한 제어를 반환하지 않는다.
자식 프로세스가 exec()호출하지 않고 부모 프로세스의 복사본을 실행하는것을 막을 방법은 없다. 이 경우 병행 프로세스가 된다.
부모 프로세스는 wait()으로 자식 프로세스가 종료될 때 까지 기다린다.
프로세스 종료
프로세스가 마지막 문장의 실행을 끝내고 exit() 시스템 호출을 사용해 운영체제에 자신의 삭제를 요청하면 종료한다.
이 시점에서 프로세스는 자신의 부모 프로세스에게 상태 값을 반환할 수 있다 (wait() 호출을 통한)
모든 자원이 운영체제로 반납된다.
부모 프로세스는 적당한 시스템 호출을 통해 자식 프로세스의 종료를 유발할 수 있다. 자식의 PID를 알아야 한다.
자식이 자신에게 할당된 자원을 초과하여 사용할 때
자식에게 할당된 작업이 더이상 필요 없을 때
부모가 exit를 하는데 운영체제가 자식의 실행을 허용하지 않을때
부모프로세스가 종료된 이후에 자식 프로세스가 종료 될 수 없다.
프로세스가 종료되면 자식 프로세스들도 연쇄적으로 종료된다 (cascading termination)
프로세스를 종료하면 사용하던 자원은 운영체제가 되찾아간다. 그러나 프로세스의 종료 상태가 저장되는 프로세스 테이블의 해당 항목은 부모 프로세스가 wait()를 호출할 때 까지 남아있게 된다. 이런 프로세스를 좀비 프로세스라고 칭한다.
모든 프로세스는 종료시 아주 짧은 상태의 좀비 프로세스가 된다. 운영체제에 곧 반환된다.
부모 프로세스가 wait()대신 종료를 해버리면 자식 프로세스는 고아 프로세스가 된다.
Linux에서는 고아프로세스의 부모를 init으로 지정함으로써 이 문제를 해결한다.
IPC; Interprocess communication
프로세들은 서로 협력할 수 있다.
다른 프로세스와 통신을 허용하는 환경을 제공하는 이유
정보 공유 Information sharing : 여러 사용자가 동일한 정보를 원할 수 있다. 병행적으로 접근할 수 있어야 한다.
계산 가속화 Computation speedup : 복수개의 코어처리를 가진 경우 서브태스크로 나눠 작업을 병렬로 실행할 수 있어야 한다.
모듈성 Modularity : 시스템 기능을 별도의 프로세스 또는 스레드로 나눠 모듈식 형태로 시스템을 구성하기 원할 수 있다.
편의성 Convenience : 사용자들은 다수의 태스크를 병렬로 가질 수 있다.
협력적인 프로세스들은 데이터와 정보를 교환할 수 있는 Interprocess communication 기법을 필요로 한다.
프로세스간 통신에는 공유 메모리 와 메시지 전달 두가지 모델이 있다. 운영체제는 두가지를 모두 구현한다.
일반적으로 메시지 전달이 더 나은 성능을 보인다
공유 메모리는 공유데이터가 여러 캐시사이에서 이주하기 때문에 캐시 일관성 문제로 성능 저하가 발생한다.
공유 메모리 시스템
공유메모리 영역은 공유 메모리 세그먼틀르 생성한느 프로세스의 주소 공간에 위치한다.
공유메모리 세그먼트를 이용하고자 하는 다른 프로세스들은 이 세그먼트를 자신의 주소공간에 추가해야 한다.
일반적으로 하나의 주소 공간에 두 프로세스가 접근하는것을 허용하지 않기 때문에 제약을 해제하는 작업이 필요하다.
데이터 형식과 위치는 프로세스에 의해 결정된다. (운영체제 소관X)
생산자 프로세스와 소비자 프로세스가 병행으로 실행되게 하려면 공유 메모리 모델이 있다.
정보가 오가는 항목에 버퍼가 있어야 한다.
생산자와 소비자가 동기화 되어야 있지도 않은 항목을 소비하지 않을 수 있다.
무한버퍼 : 버퍼 크기에 제한이 없어 생산자는 항상 새로운 항목을 생산할 수 있으나 소비자는 새로운 항목을 기다려야 할 수도 있다.
유한버퍼 : 버퍼의 크기가 고정되어 있고 소비자는 반드시 대기 해야하고 모든 버퍼가 채워져 있을때는 생산자가 대기해야 한다.
메시지 전달 시스템
공유메모리 외에 협력하는 방법으로 메시지 전달 설비를 통해 프로세스간 통신 수단을 운영체제가 제공한다.
메시지 전달 방식은 동일한 주소 공간을 공유하지 않고도 프로세슫르이 통신을 하고, 그들의 동작을 동기화할 수 있도록 허용하는 기법을 제공한다.
메시지 전달 방식은 분산환경에서 유용하다.
메시지 전달 시스템은 최소한 send()와 receive()가 존재한다.
통신하기 위해 Communication link가 반드시 필요하다.
send()와 receive()를 구현하는 방법
직접 또는 간접 통신
동기식 또는 비동기식 통신
자동 또는 명시적 버퍼링
직접 통신
통신을 원하는 각 프로세스의 쌍들 사이에 연결이 자동적으로 구축된다. 프로세스들은 통신하기 위해 서로의 id만 알면 된다.
연결은 두 프로세스 사이에서만 이루어진다.
하나의 쌍에 하나의 연결만 존재한다.
주소 방식에서 대칭성을 보인다.
프로세스 지정으로 인해 프로세스 식별자를 바꾸면 지정된 다른 부분을 모두 검사해야 한다.
간접 통신
한쌍의 프로세스들 사이의 연결은 이들 프로세스가 공유 메일박스를 가질때만 구축된다.
연결은 두개 이상의 프로세스들과 연관될 수 있다.
통신하고 있는 각 프로세스들 사이에는 다수의 서로 다른 연결이 존재할 수 있다.
각 연결은 하나의 메일 박스에 대응한다.
동기화
프로세스는 send()와 receive() 원형을 호출해 서로 통신한다. 각 원형을 구현하기 위한 설계 옵션이 있다.
메시지 전달은 blocking과 non-blocking방식으로 전달된다.
동기식 송신 : 송신하는 프로세스는 메시지가 수신 프로세스 또는 메일 박스에 의해 수신될 때 까지 블로킹 한다.
비동기식 송신 : 송신하는 프로세스가 메시지를 보내고 작업을 재시작한다.
동기식 수신 : 수신자는 메시지가 도착할 때 까지 블로킹 된다.
비동기식 수신 : 수신자는 유효한 메시지를 받거나 널을 받는다.
send()와 receive()간에 다른 조합도 가능하다.
send()와 receive()가 모두 동기식일때 둘 간에 랑데뷰를 이룬다.
CS 통신
클라이언트-서버 환경에서 사용할 수 있는 세가지 다른 통신 전략이 있다.
소켓, RPC, 파이프
Socket
소켓은 통신의 endpoint를 뜻한다.
네트워크를 통해 통신하는 한 쌍의 프로세스는 프로세스당 하나씩, 한쌍의 소켓이 필요하다.
각 소켓은 IP와 port로 식별된다.
java 복사 public class DataServer {
public static void main (String[] args) {
try {
ServerSocket sock = new ServerSocket (6013 );
while (true ){
Socket client = sock.accept ();
PrintWriter pout = new PrintWriter (client.getOutputStream (), true );
pout.println (new Date ().toString ());
client.close ();
}
}
catch (IOException e){
System.err.println (e);
}
}
}
public class DataClient {
public static void main (String[] args) {
try {
Socket sock = new Socket ("127.0.0.1" , 6013 );
InputStream in = sock.getInputStream ();
BufferedReader bin = new BufferedReader (new InputStreamReader (in));
String line;
while ((line = bin.readLine ()) != null ) {
System.out.println (line);
sock.close ();
}
}
catch (IOException e){
System.err.println (e);
}
}
}
소켓은 바이트 스트림 기반이기 때문에 해석하는것은 서버와 클라이언트의 몫이다.
RPC; 원격 프로시저 호출
RPC는 두 시스템 사이에 통신하기 위해 프로시저 호출 기법을 분리해 생각하기 위한 방편이으로 IPC기반으로 만들어진다.
RPC는 IPC와 달리 전달되는 메시지가 구조화 되어있고 데이터의 패킷 수준을 넘어선다.
각 메시지는 원격지 포트에서 리스닝 중인 RPC데몬의 주소가 지정되어있고 함수, 매개변수가 포함되어 있다.
클라이언트와 서버의 데이터 표현방식이 빅엔디안과 리틀엔디안이 다를 경우에 대비해 XDR이라는 중립적인 표현방식으로 변환해 전송한다.
호출의 semantic이 실패하거나 중복되는것을 방지 하기 위해 정확히 한번만 처리되도록 보장해야 한다.
RPC는 분산 파일 시스템을 구현하는데 유용하다.
파이프
파이프는 두 프로세스가 통신할 수 있게 하는 전달자다.
통신에 여러 제약이 있다.
고려해야 하는 부분
파이프가 단방향 또는 양방향을 허용하는가?
양방향을 허용한다면 반이중인가? 전이중인가?
부모-자식 같은 특정 관계가 존재해야 하는가?
네트워크 통신이 가능한가? 같은 시스템 내에서만 가능한가?
일반 파이프
생산자-소비자 형태고 두 프로세스간의 통신을 허용한다.
단방향이다.
파이프를 생성한 프로세스 이외에는 접근할 수 없다.
통상 부모 프로세스가 파이프를 생성하고 fork한 자식 프로세스와 통신하기 위해 사용된다.
지명 파이프
지명 파이프는 양방향으로 통신이 가능하다
부모-자식 관계도 필요 없다.
지명 파이프가 구축되면 여러 프로세스들이 이를 사용해 통신할 수 있다.
두 프로세스는 동일한 시스템 내에 존재해야한다.
UNIX에서는 지명파이프를 FIFO라고 부른다.
실제 상황에서의 파이프
파이프는 한 명령어의 출력이 두번째 명령어의 입력으로 사용되는 경우, 명령어 라인 환경에서 자주 사용된다. ls는 디렉토리 목록을 생성한다. 특히 긴 디렉토리 목록의 경우 출력은 여러 화면에 걸쳐 스크롤 될 것이다. more 명령어는 한번에 한 화면 단위로 출력되도록 출력을 관리한다. ls와 more의 명령어 사이에 파이프를 구축하면 ls의 출력이 more의 입력으로 전달되도록 하여 사용자가 긴 디렉토리 목록을 한 화면 단위로 볼 수 있게 한다.