2022. 3. 22. 21:43ㆍMajor`/운영체제
프로세스
"Program in Execution"
디스크에 있는 프로그램이 실행되면 메인 메모리로 올라가게 되고, 메인 메모리로 올라가게 된 프로그램을 이제 프로세스라고 부른다
- text(code) : 실행 코드
- data : 전역 변수
- stack : 함수를 호출할 때 임시 데이터 저장장소 (함수 매개변수 / 복귀 주소 / 지역 변수)
- heap : 프로그램 실행 중 동적으로 할당되는 메모리
- text/data 영역의 크기는 거의 고정적이기 때문에 프로그램 실행 동안 크기가 변하지 않는다
- stack/heap은 프로그램 실행 도중 얼마든지 크기가 변경될 수 있다
CPU Bound Process
프로세스 대부분의 시간을 CPU 계산에 사용하는 프로세스
I/O Bound Process
프로세스 대부분의 시간을 I/O 하는데 사용하는 프로세스
Long-Term Scheduler는 CPU Bound Process & I/O Bound Process를 잘 섞어서 메인 메모리에 올려줘야 한다
왜냐하면 어느 한쪽으로 편향되어서 메인 메모리에 적재하면 CPU or I/O가 노는 시간이 많아지기 때문이다
그리고 CPU Scheduler는 각 프로세스의 특성을 잘 고려해서 CPU를 할당해줘야 한다.
만약 CPU Bound Process에게 CPU를 많이 할당해주면 user와의 interaction을 중요시하는 I/O Bound Process는 CPU를 늦게 할당받고 그에 따라서 user와의 응답속도가 느려진다
일반적으로 I/O Bound Process는 CPU Bound Process보다 CPU Burst Time이 짧다. 따라서 CPU Burst Time이 짧은 프로세스에게 CPU를 할당함으로써 전체적인 프로세스의 대기 시간을 줄일 수 있다
프로세스의 상태
new
- 프로세스를 생성하고 있는 단계
- kernel 주소 공간에 해당 프로세스만의 PCB가 만들어진 상태
ready
- 프로세스가 현재 메모리내에 적재되어 있고, 실행을 위한 여러 준비가 전부 완료된 상태
- 실행에 필요한 필수 자원들을 모두 얻은 상태
- 아직 CPU 할당을 받지 않았지만 CPU만 할당되면 바로 실행 가능하다
>> 메모리내에 여러개의 프로세스들이 "ready queue"에 대기하면서 CPU의 할당을 받을 준비를 하고 있다. 여기서 "CPU Scheduling"을 통해서 어느 프로세스에게 할당해줄지를 결정해준다. 물론 "ready queue"내에서는 각 프로세스의 PCB들이 본인의 Pointer를 통해서 서로 연결되어 있다.
running
- 프로세스가 CPU의 할당을 받아서 현재 명령어를 수행중인 상태
waiting (Blocked)
- running상태에서 I/O or event가 발생함에 따라서 해당되는 interrupt를 수행중이여서 프로세스는 대기중인 상태
- waiting은 CPU를 할당해줘도 명령어를 수행할 수 없는 상태이다
- I/O or event가 완료되어서 이제 다시 CPU의 할당을 받을 준비가 된 프로세스는 waiting → ready상태가 된다. 바로 running상태가 되지는 않는다
terminated
- 프로세스의 수행이 완료? 종료?된 상태
※ ready → running
ready에서 running이 된다는 의미는 다른 측면에서 바라보면 running중이던 프로세스는 waiting이 되거나 terminated가 되거나 다시 ready가 되었다는 의미이다.
따라서 이 과정에서 반드시 수행해야 하는 job이 "Context Switch"이다
※ Context Switch
1) CPU를 뺏긴 프로세스의 입장에서는 뺏기기 전까지 자신의 수행 과정(문맥)들을 전부 kernel의 data영역에 존재하는 본인의 PCB 자료구조에 저장해두어야 한다. 저장을 해두어야 다음에 또 실행될 때 "현재 문맥"에서부터 실행이 가능할 것이다
2) CPU를 얻은 프로세스의 입장에서는 이 프로세스도 본인의 PCB 자료구조에 "나는 어디까지 실행이 되었었고, 그때 나의 상태? 레지스터값? PC값?은 이랬었다"라는 정보들이 저장이 되어있을 것이다. 그래서 이런 PCB 정보들을 "본인의 문맥"으로 가져와서 이제 문맥에 맞게 실행되도록 해줘야 한다
- 이런 Context Switch는 'Dispatcher'가 수행해준다
- context switch에 의해서 바로 CPU의 할당 대상이 변경되는 것이 아니라, "context switch"를 수행하는 데도 일정 시간이 걸린다. 따라서 수행한 이후에 CPU가 할당된다
- "context switch"되는 동안에 시스템이 어떠한 유용한 일도 하지 못하기 때문에 "context switch time"은 순수한 overhead이다
- "context switch time"은 메모리 속도/레지스터 수/특수 명령어 등에 좌우되므로, 기계마다 다르다
- 물론 여러 프로세스에 대한 "context switch" 공통 작업들을 일종의 하드웨어로 만들어서 "context switch" 속도를 향상시킬 수는 있다
- 그러나 한번 하드웨어로 만들어버리면 변경이 불가능하다
>> context switch가 발생하면 CPU를 뺏긴 프로세스가 사용하던 캐시 메모리를 전부 지워버려야 한다
>> "cache memory flush"는 굉장한 overhead이다
※ running → ready
running중인 프로세스가 바로 ready가 되는 경우는 "Timer Interrupt"에 의해서 발생된다
Timer Interrupt는 해당 프로세스가 본인한테 할당된 CPU 시간을 전부 써서 발생한 H/W Interrupt이고, ready가 될 때도 본인의 문맥 정보들을 kernel에 존재하는 본인의 PCB에 저장해야 한다
※ running → waiting → ready
running중이던 프로세스에 I/O가 발생하게 되면 당연히 I/O에 대한 처리를 해줘야 한다. 처리를 할 동안 해당 프로세스는 waiting상태가 된다. waiting상태는 CPU를 할당받아도 어차피 아무것도 할 수 없는 상태이다. I/O or event가 종료되면 이제 해당 프로세스는 다시 CPU의 할당을 받을 수 있고 그에 따라서 ready상태가 된다
PCB (Process Control Block)
- OS가 각 프로세스를 관리하기 위해 본인의 kernel 주소 공간의 "Data" 영역에 생성한 자료구조
- 구조체로 유지한다
1) OS가 관리상 사용하는 정보
- 프로세스 상태 (ready / running / waiting / ....)
- 프로세스 id (고유 id)
- 스케줄링 정보
- 우선순위
- 포인터 :: Queue에서 PCB들을 연결해준다
2) CPU 수행 관련 하드웨어 값 (프로세스의 문맥을 위한)
- PC
- 각종 레지스터들
3) 메모리 관련
- code & data & stack
4) 파일 관련
- 프로세스가 사용중 & 사용할 파일 list / ....
프로세스의 context
context? 특정 시점을 기준으로 프로세스의 정보들
- 프로세스가 현재까지 어떤 일들을 수행했는가?
- 프로세스가 현재 어떤 상태인가? (new / ready / running / waiting / terminated / suspended)
프로세스는 실행이되면 프로세스 본인만의 독자적인 주소 공간을 보유하게 된다
- 실행 과정에서 "PC"가 code 영역 내에 존재하는 여러 명령어들을 순차적으로 가리킴에 따라 명령어들을 CPU가 실행하게 된다
$ 현재 시점에서 PC가 어디를 가리키고 있는가
- PC가 가리키고 있는 지점을 분석해보면 해당 프로세스의 code 영역 내에 어느 부분까지 실행을 완료했는가를 파악할 수 있다
$ 프로세스 메모리(data / stack / code)에는 어떤 값들을 담고 잇는가
- code 내의 명령어들을 실행하면서 "함수를 호출"했다면 당연히 stack에 관련 내용들이 쌓여있을 것이다
- data 영역 내에 변수 값들에는 변화가 있나? 어떤 값으로 변화했지?
- 프로세스가 실행되면서 CPU 내부 레지스터들의 값에는 어떤 값들이 있고, 해당 값들은 어떻게 변하였고, 예를 들어서 PC를 분석해보면 어떤 명령어까지 실행했는가를 알 수 있다
>> 이러한 모든 요소들을 파악해야 프로세스의 현재 상태를 파악할 수 있다
문맥 1) CPU의 수행 상태를 나타내는 하드웨어 문맥
- PC
- 각종 레지스터들
문맥 2) 프로세스의 주소 공간
- 메모리의 code & data & stack 공간
문맥 3) 프로세스 관련 kernel 자료 구조
- PCB
- kernel stack
※ user program(process)가 System Call을 통해서 OS에 어떤 서비스를 요청하면
- PC는 kernel 주소 공간의 code의 어느 명령어를 가리키게 되고
- kernel 주소 공간의 code 내에서 함수가 호출된다면 해당 함수와 관련된 정보들은 kernel 주소 공간의 stack에 쌓이게 된다
- 여기서 kernel 주소 공간의 "stack"에는 각 프로세스 별로 kernel stack을 나누어서 관리한다
3가지 Queue : Job / Ready / Device
Job Queue
보조저장장치에 존재하는 Queue
Long-Term Scheduler에 의해서 Job Queue에 존재하는 프로세스 중 어느 프로세스를 메인 메모리로 load해줄까를 결정한다
Ready Queue
메인메모리에 존재하는 Queue
Short-Term Scheduler에 의해서 Ready Queue에 존재하는 프로세스 중 어느 프로세스에게 CPU를 할당해줄까를 CPU 스케줄링에 의해서 결정한다
Device Queue
Device Controller 안에 존재하는 "Local Buffer"에 있는 Queue
각 I/O 장치별로 서비스를 원하는 프로세스들이 여기 Queue에서 대기하게 된다 (Waiting 상태)
해당 프로세스는 I/O 작업이 종료되면 다시 Ready Queue로 들어간다
스케줄러 종류 (term에 따른 구분)
Long-Term Scheduler
- 보조저장장치에 존재하는 프로세스 중에서 어느 프로세스를 메인 메모리로 올려줄까?
- "degree of multiprogramming"을 제어
- 메인 메모리에 올라가 있는 프로세스의 수
- CPU Bound Process & I/O Bound Process들을 적절히 mix해서 메인 메모리로 올려줘야 한다
- 이 스케줄러는 자주 발생하지 않는다
Why?
프로세스를 메인 메모리로 올리려면 일단 최우선적으로 메인 메모리에 프로세스를 올릴 공간이 있어야 한다. 이 공간은 대표적으로 프로세스가 종료되면 생기기 때문에 자주 발생할 수 없는 스케줄러이다
>> 현대 Time Sharing System에서 사용되는 OS는 일반적으로 Long-Term Scheduler를 두지 않는다. 왜냐하면 과거에는 적은 양의 메모리를 많은 프로세스들에게 할당하면 프로세스 당 메모리 보유량이 적어서 Long-Term Scheduler가 이를 조절했지만 현재 OS는 프로세스가 시작되면 바로 메인 메모리에 적재되므로 Long-Term Scheduler를 거의 사용하지 않는다
Short-Term Scheduler
- ready queue에 존재하는 여러 프로세스 중 "어느 프로세스에게 CPU를 할당해줄까?"
- 보통 "context switch"는 굉장히 자주 발생한다 :: "Timer" & 따라서 그에 따른 CPU 할당을 해주는 스케줄러도 굉장히 빈번하게 일을 한다
- [ms] 단위
Medium-Term Scheduler (Swapper)
만약 Waiting → Ready로 넘어가지 못하거나 Ready → Running으로 넘어가지 못하는 프로세스들이 존재한다고 하자. 이러면 결국 해당 프로세스는 실행은 안되면서 계속 메모리를 보유하고 있는 비효율적인 상황이 발생할 것이다.
이런 경우에서 메모리에 적재되어있는 비효율적인 프로세스를 다시 디스크로 쫓아낸다 : Swap-Out
그리고 나중에 해당 프로세스가 필요에 의해서 다시 메모리에 적재될 수 있다 : Swap-In
- "degree of multiprogramming"을 제어
- 메인 메모리에 올라가 있는 프로세스의 수
Medium-Term Scheduler 때문에 프로세스의 상태에도 변화가 생겼다
Suspended
- 외부적인 이유로 인해서 프로세스의 수행이 강제로 중지된 상태
- 메모리에 너무 많은 프로세스가 있어서 특정 프로세스를 쫓아냄 : Medium-Term Scheduler
- 프로그램을 break key를 통해서 일시 정지 : User
>> 외부(user | Medium-Term)에서 resume을 해줘야만 다시 active 상태(ready / running / waiting)이 된다
>> 이와 달리 Waiting(Blocked)는 자신이 요청한 event or I/O가 완료되면 다시 ready상태로 변경된다
프로세스 생성
부모 프로세스는 실행되는 동안에 여러개의 자식 프로세스를 생성할 수 있다
그러면 생성된 자식 프로세스는 또 여러개의 자식 프로세스를 생성할 수 있고,,,,,
이렇게 반복됨에 따라서 프로세스의 트리를 형성하게 된다
→ 각 프로세스들은 본인의 고유한 ID(pid)를 보유하고 있기 때문에 수많은 프로세스는 각각 구분될 수 있다
일반적으로 프로세스가 자식 프로세스를 생성해주면 자식 프로세스도 하나의 프로세스이므로 자원(CPU, 메모리, I/O)가 필요할 것이다
자원을 얻는 방법은 1) OS로부터 직접 얻기(부모와 공유 X) / 2) 부모의 자원 그대로 사용 / 3) 부모의 일부 자원 사용으로 나눠질 수 있다
- 일반적 경우는 1)의 경우이다
- 결국 자식 프로세스도 하나의 프로세스이고 프로세스 간에 서로 CPU를 할당받기 위해 경쟁하기 때문이다
새로 생성된 프로세스들을 실행할 때 2가지 가능한 방법이 존재한다
- 부모 & 자식 프로세스가 공존하며 실행
- 자식 프로세스가 종료될때까지 부모 프로세스는 아무일도 하지 못하고 기다리기
새로 생성된 프로세스들의 주소 공간 측면을 바라볼 때 다음과 같은 2가지 경우가 존재할 것이다
- 자식 프로세스는 부모 프로세스의 복사본이다 : 동일한 프로그램/데이터
- 자식 프로세스는 자신에게 적재될 새로운 프로그램을 보유
UNIX 기반 프로세스 연산
프로세스 생성 : fork() & exec()
부모 프로세스가 자식 프로세스를 생성할 때 fork() 시스템 콜을 이용해서 생성한다
- 사실상 fork()를 통해서 생성해주면 자식 프로세스는 부모 프로세스의 "복제본"이다
int main(){
int pid;
pid = fork();
if (pid < 0){
fprintf(stderr, "Fork Failed");
return 1;
}
else if (pid == 0){ // this is child
printf("Hello, I'm Child");
execlp("echo", "echo", "Hello", " World!", (char *)0);
}
else // this is parent
printf("Hello, I'm Parent");
}
위의 예시는 부모 프로세스의 예시이고, "pid = fork()"를 통해서 자식 프로세스를 생성하면 아래와 같은 자식 프로세스가 생성될 것이다
// 부모 프로세스
int main(){
int pid;
pid = fork();
if (pid < 0){
fprintf(stderr, "Fork Failed");
return 1;
}
else if (pid == 0){ // this is child
printf("Hello, I'm Child");
execlp("echo", "echo", "Hello", " World!", (char *)0);
}
else // this is parent
printf("Hello, I'm Parent");
}
// 자식 프로세스
int main(){
int pid;
pid = fork();
if (pid < 0){
fprintf(stderr, "Fork Failed");
return 1;
}
else if (pid == 0){ // this is child
printf("Hello, I'm Child");
execlp("echo", "echo", "Hello", " World!", (char *)0);
}
else // this is parent
printf("Hello, I'm Parent");
}
fork()만 수행해서 새로운 프로세스를 생성하게 되면 일단 부모 프로세스의 복사본을 가지게 된다
따라서 자식 프로세스는 부모 프로세스의 모든 정보(PC / 메모리 / ...)들을 그대로 물려받게 된다
여기서 자식 프로세스는 "int pid"부터 실행?
X : 왜냐하면 부모 프로세스의 모든 정보들을 물려받고 그 정보에는 코드의 어디부분까지 실행되었는가도 포함되어 있기 때문에 자식 프로세스 입장에서는 "기억은 안나지만 나도 pid=fork()까지 실행했구나"라고 생각을 할 것이기 때문에 자식 프로세스는 "pid = fork()"다음인 if문부터 실행하게 된다
그리고 fork()로부터 return값에도 차이가 존재하는데 자식 프로세스는 "0"을 return하고, 부모 프로세스는 "0"보다 큰 값을 return하게 된다
// 자식 프로세스
int main(){
int pid;
pid = fork();
if (pid < 0){
fprintf(stderr, "Fork Failed");
return 1;
}
else if (pid == 0){ // this is child
printf("Hello, I'm Child");
}
else // this is parent
printf("Hello, I'm Parent");
}
if문부터 계속실행하다가 exec() 시스템콜을 만나게 되면 해당 프로세스는 완전히 새로운 프로그램으로 태어난다 :: 덮어씌우기
exec() 시스템콜을 수행하게 되면 그 이전으로 영원히 돌아가지 못한다
- exec() 시스템 콜은 반드시 fork() 이후에만 수행되는 것이 아니라, fork()가 없어도 수행될 수 있다
(부모)프로세스 잠들기 : wait()
- 자식 프로세스가 종료될때까지 sleep시키는 시스템 콜이다
- Block 상태
- 자식 프로세스가 종료되면 깨워준다
- ready 상태
int main(){
int pid;
pid = fork();
if (pid == 0){ // this is child
....
....
}
else{ // this is parent
wait();
}
}
프로세스 종료 : exit()
자신에게 할당되었던 모든 자원(물리/가상 메모리, 파일, 버퍼, ....)들이 전부 해제되고 OS에 반납된다
부모 프로세스가 자식 프로세스를 kill하려면 자식 프로세스의 pid를 알아야지 kill할 수 있다
- 이러한 이유 때문에 부모로부터 자식이 생성되면 자식의 신원이 부모로 전달된다
부모 프로세스가 자식 프로세스를 kill할 때는 여러가지 이유가 존재한다 >> 자식 프로세스 입장에서는 비자발적 종료이다
- 자식 프로세스가 할당된 자원 그 이상을 사용할 때
- 자식 프로세스에게 할당된 task가 더이상 없을 때
- 부모 프로세스가 exit()을 요청하는데, OS가 "자식 프로세스 계속 실행"을 허용하지 않는 경우
몇몇 OS에서는 부모 프로세스가 종료한 이후에 자식 프로세스가 존재할 수 없다. 이러한 시스템에서는 부모 프로세스가 종료되면 반드시 자식 프로세스도 종료되어야 한다. 이러한 종료 방식을 연쇄식 종료(비자발적 종료)라고 한다
통상적으로 코딩을 할 때 main()함수가 종료되면 해당 프로그램이 종료되는데 main() 함수가 return되는 위치에 compiler가 자동으로 exit()를 삽입해준다
프로세스 간 통신 : IPC (InterProcess Communication)
OS내에서 실행되는 병행 프로세스들은 독립적 or 협력적 프로세스이다
- 독립적 프로세스 : 하나의 프로세스가 다른 프로세스의 수행에 영향을 미치지 않는다
- 협력적 프로세스 : 하나의 프로세스가 다른 프로세스의 수행에 영향을 미칠 수 있다
협력적 프로세스는 프로세스 간에 데이터를 서로 교환할 수 있는 메커니즘이 반드시 필요하다
- shared memory & message passing
1) Shared Memory : 공유 메모리
협력 프로세스들에 의해서 공유되는 메모리 영역이 생성된다
- 물론 여기서 시스템 콜을 통해서 공유 메모리 영역을 구축해야 한다 (OS 개입)
- OS는 공유 메모리 영역을 구축할 때만 개입하고 그 이후에 공유 메모리에 대한 접근은 일반 메모리 접근으로 취급되어서 kernel의 도움이 필요가 없다
프로세스들은 Shared Memory에 데이터를 읽고 쓰고 함으로써 정보를 교환한다
※ "Producer - Consumer Problem"
생산자(Producer)는 정보를 생산하고(데이터 쓰기), 소비자(Consumer)는 정보를 소비한다(데이터 읽기)
여기서 생산자 프로세스, 소비자 프로세스를 동시에 병행으로 실행하려면 둘 사이에 데이터를 읽고 쓰기위한 "buffer"가 반드시 사용 가능한 상태이여야 한다
- 여기서 "buffer"는 두 프로세스가 공유하는 메모리 영역에 존재
두 프로세스가 반드시 동기화가 되어야 buffer에 존재하지 않는 = 생산되지 않은 데이터를 소비자가 읽으려고 시도조차 하지 않을 것이다
▶ 무한 버퍼 (Unbounded Buffer)
- 버퍼의 크기에 실질적 한계 X
- 소비자는 새로운 데이터를 기다려야 할 수 있지만, 생산자는 무한으로 데이터를 생산할 수 있다
▶ 유한 버퍼 (Bounded Buffer)
- 버퍼의 크기가 고정
- 버퍼가 비어있으면 소비자는 반드시 대기해야 하고, 버퍼가 가득 차있으면 생산자는 대기해야 한다
2) Message Passing : 메시지 전달 시스템
OS의 kernel내의 "mailbox"를 통해서 서로 데이터를 교환하는 방식
1. 일단 두 프로세스가 통신을 원한다면 먼저 "통신 연결(Communication Link)"이 설정되어야 한다
- 물리적 구현
2. 통신 연결이 확립되면 이제 send()/receive() 연산을 논리적으로 구현해서 서로 데이터를 교환하면 된다
- 직접/간접 통신
- 동기/비동기 통신
- 자동/명시적 버퍼링
▶ 직접/간접 통신
직접 통신은 각 프로세스간에 서로 송/수신자의 이름을 명시하는 통신이다
Q : send(P, message)
- 프로세스 Q가 프로세스 P에게 메시지 송신
P : receive(Q, message)
- 프로세스 P는 프로세스 Q가 송신한 메시지 수신
- 직접 통신은 각 프로세스 사이의 연결이 자동적으로 구축된다, 프로세스들은 통신을 하기 위한 상대방의 신원만 파악하면 된다
- 통신 연결은 정확하게 sender/receiver 둘 사이에서만 연관된다
- 통신하는 프로세스들의 각 쌍 사이에는 반드시 하나의 연결만 존재해야 한다
간접 통신은 mailbox/port를 통해서 메시지가 송수신된다. 그리고 각 메일박스는 고유한 id를 가진다
Q : send(A, message)
- 프로세스 Q가 메일박스 A에 메시지 송신
P : receive(A, message)
- 프로세스 P는 메일박스 A로부터 메시지 수신
- 한 쌍의 프로세스들 사이의 연결은 이들 프로세스가 공유 메일박스를 가질 때만 구축
- 연결은 2개 이상의 프로세스들과 연관될 수 있다
- 통신 중인 각 프로세스 사이에는 여러개의 서로 다른 연결이 존재할 수 있고, 각 연결은 하나의 메일박스에 대응된다
▶ 동기/비동기 통신 : Blocking/non-blocking
Blocking 송수신
# 송신
- 송신 프로세스가 수신 프로세스/메일박스에 메시지를 일단 보내면 수신 프로세스/메일박스가 수신완료될때까지 blocking된다
# 수신
- 송신 프로세스가 송신한 메시지를 사용 가능할 때까지 수신 프로세스는 blocking된다
>> 송신 프로세스는 상대방이 수신 다 할 때까지 blocking & 수신 프로세스는 메시지 사용 가능할 때까지 blocking
non-blocking 송수신
# 송신
- 송신 프로세스는 수신 프로세스/메일박스에 메시지를 보내고 수신 완료와 상관없이 계속 메시지를 보낼 수 있다
# 수신
- 수신 프로세스는 유효 메시지 or NULL을 수신하게 된다
>> 송신 프로세스는 수신완료랑 관계없이 계속 메시지 송신 & 수신 프로세스는 메시지 사용 여부와 관계없이 수신을 받기 때문에 유효한 메시지 or NULL을 수신
▶ 자동/명시적 버퍼링
통신이 직접/간접과 관계없이 서로 교환되는 메시지는 임시 큐에 들어있다. 큐에는 3가지 방식이 존재한다
# 무용량 (Zero Capacity)
큐의 최대 길이가 0이다
→ 이 의미는 메시지가 대기할 수 없다는 의미이다
>> 따라서 송신자는 수신자가 메시지를 전부 수신할 때까지 기다려야 한다
# 유한 용량 (Bounded Capacity)
큐는 유한한 길이 n을 가진다
→ 큐에는 최대 n개의 메시지를 보유할 수 있다
- 큐에 용량이 남아있다면, 송신자는 대기하지 않고 계속 송신한다
- 큐에 용량이 없다면, 송신자는 큐에 용량에 생길 때까지 반드시 blocking되어야 한다
# 무한 용량 (Unbounded Capacity)
큐는 잠재적으로 무한한 길이를 가진다.
→ 메시지들이 얼마든지 큐 안에서 대기할 수 있다
>> 따라서 송신자는 절대로 blocking되지 않는다
클라이언트 - 서버 환경에서의 통신
1) 소켓 (Socket)
양 프로세스마다 하나의 소켓을 보유하고 있고, 소켓은 IP주소 + 포트번호를 통해서 구분한다
2) RPC : 원격 프로시저 호출
네트워크에 연결된 두 시스템 사이의 통신에 사용하기 위해서 프로시저 호출 기법을 추상화하는 방법으로 설계되었다.
각 메시지에는 원격지 포트에서 listen 중인 RPC디먼의 주소가 지정되어 있고, 실행되어야 할 함수의 식별자, 해당 함수에게 전달되어야 할 매개변수가 포함되어 있다.
>> 이 후 요청된 함수가 실행되고 어떤 출력이든 별도의 메시지를 통해서 client에게 return된다
3) 파이프
- 두 프로세스가 통신할 수 있게 하는 전달자로서 동작한다
- 초기에 UNIX 시스템에서 제공하는 IPC 기법 중 하나였다.
파이프는 통산 프로세스 간에 통신하는 더 간단한 방법 중 하나지만, 통신할 때 여러 제약이 존재한다
- 파이프가 단방향 통신? 양방향 통신?을 허용하는가
- 만약 양방향 통신을 허용한다면 Half Duplex인가 Full Duplex인가
- Half Duplex는 한순간에는 한 방향 전송만 가능하고, Full Duplex는 동시에 양방향 데이터 전송이 가능
- 통신하는 두 프로세스 간에 부모-자식과 같은 특정 관계가 존재해야만 하는가
- 파이프는 네트워크를 통해서 통신이 가능한가 or 동일 기계 안에 존재하는 두 프로세스끼리만 통신 가능한가
▶ Ordinary Pipes
- Producer-Consumer 형태로 두 프로세스 간의 통신을 허용한다
- 생산자는 한 종단에서 쓰고, 소비자는 다른 한 종단에서 읽는다
>> 결과적으로 단방향 통신만 가능하다. 양방향 통신을 하려면 2개의 파이프를 사용해야 한다
▶ Named Pipes
- 양방향 통신이 가능하고, 부모-자식 같은 관계도 필요없다
- 여러 프로세스들이 Named Pipes를 통해서 통신할 수 있다
- 통신 프로세스가 종료해도 Named Pipes는 계속 존재한다
- FIFO구조이므로 양방향 통신을 허용하지만, 반이중 전송만 가능하다
- Bi-Directional -- Half Duplex