프로세스 간 통신
프로세스 간 통신의 개념
- 프로세스 내부 데이터 통신: 하나의 프로세스 내에 2개 이상의 스레드가 존재하는 경우의 통신. 프로세스 내부의 스레드는 전역 변수나 파일을 이용하여 데이터를 주고 받는다.
- 프로세스 간 데이터 통신: 같은 컴퓨터에 있는 여러 프로세스끼리의 통신. 공용 파일 또는 운영체제가 제공하는 파이프를 사용하여 통신한다.
- 네트워크를 이용한 데이터 통신: 여러 컴퓨터가 네트워크로 연결되어 있을 때의 통신. 프로세스가 소켓을 이용하여 데이터를 주고받는다.
💡 같은 컴퓨터에 있는 프로세스끼리도 소켓을 이용하여 통신할 수 있다. IP 주소로 127.0.0.1과 같이 루프백 주소를 사용하면 된다. 하지만 소켓을 사용하려면 많은 전처리를 해야 하기때문에 다른 프로세스 간 통신 방법보다 느리다.
프로세스 간 통신의 분류
- 통신 방향에 따른 분류
- 양방향 통신: 데이터를 동시에 양쪽 방향으로 전송할 수 있는 구조. 프로세스 간 통신에서 소켓 통신이 양방향 통신에 해당한다.
- 반양방향 통신: 데이터를 양쪽 방향으로 전송할 수 있지만 동시 전송은 불가능하고 특정 시점에 한쪽 방향으로만 전송할 수 있는 구조. 대표적인 예는 무전기다.
- 단방향 통신: 한쪽 방향으로만 데이터를 전송할 수 있는 구조. 프로세스 간 통신에서 전역 변수와 파이프가 단방향 통신에 해당한다.
- 통신 구현 방식에 따른 분류
- 대기가 있는 통신(동기화 통신): 데이터를 보냈다는 것을 받는 쪽에 알려주는 통신. 데이터를 받는 쪽은 데이터가 도착할 때까지 자동으로 대기 상태에 머물러있다. 파이프와 소켓이 동기화 통신에 해당한다.
- 대기가 없는 통신(비동기화 통신): 데이터를 받는 쪽이 반복문을 무한 실행하며 기다리는 바쁜 대기(busy wating)를 사용하여 데이터가 도착했는지 직접 확인한다. 전역 변수와 파일이 비동기화 통신에 해당한다. 전역 변수나 파일이 비동기화 통신에 해당한다.
프로세스 간 통신의 종류
프로세스 간 통신은 데이터를 주거나 받는 동작으로 이루어지면 이는 쓰기 연산과 읽기 연산으로 간소화 할 수 있다.
- send: 쓰기 연산
- receive: 읽기 연산
전역 변수를 이용한 통신
- 공동으로 관리하는 메모리를 사용하여 데이터를 주고받는 것이다.
- 데이터를 보내는 쪽: 전역 변수에 값을 쓴다.
- 데이터를 받는 쪽: 전역 변수의 값을 읽는다.
- 전역 변수로 양방향 통신을 하려면 전역 변수를 2개 사용해야 한다.
파일을 이용한 통신
- 파일 입출력 코드는 크게 열기, 쓰기 또는 읽기, 닫기로 구성되어 있다.
- 파일을 열 때 파일이 있는지, 또는 그 파일이 있다면 쓰기 권한이 있는지도 확인한다. 정상적으로 파일을 사용할 수 있다면 open() 함수는 fd(file descriptor)를 반환한다.
- 파일을 이용한 통신은 부모-자식 프로세스 간 통신에 많이사용되며 운영체제가 프로세스 동기화를 제공하지 않는다.
- 그래서 프로세스가 알아서 동기화를 해야하는데 주로 부모 프로세스가 wait() 함수를 이용해서 자식 프로세스의 작업이 끝날 때까지 기다렸다가 작업을 시작한다.
파이프를 이용한 통신
open() 함수로 기술자를 얻고 작업을 한 후 close() 함수로 마무리한다.
파이프로 양방향 통신을 하려면 파이프 2개를 사용해야 한다.
아래 그림에서 프로세스 B가 파이프1에 대해 읽기 연산을 수행했는데 프로세스 A가 파이프 1에 아직 쓰기 연산을 하지 않았다면 프로세스 B는 대기 상태가 된다.
파이프는 이름 없는 파이프와 이름 있는 파이프로 나뉜다.
- 이름 없는 파이프: 일반적으로 파이프라고 하면 이를 가리킨다. 부모와 자식 프로세스 혹은 같은 부모를 가진 자식 프로세스와 같이 서로 관련 있는 프로세스 간 통신에 사용된다.
- 이름 있는 파이프: FIFO라 불리는 특수 파일을 이용하며 서로 관련 없는 프로세스 간 통신에 사용된다.
소켓을 이용한 통신
- 프로시저 호출이 한 컴퓨터에 있는 함수를 호출하는 것이라면, 원격 프로시저 호출은 다른 컴퓨터에 있는 함수를 호출하는 것이다.
- 일반적으로 원격 프로시저 호출은 소켓을 이용하여 구현한다.
- 통신하고자 하는 프로세스는 소켓에 쓰기 연산을 하면 데이터가 전송되고, 읽기 연산을 하면 데이터를 받게 된다.
- 소켓은 프로세스 동기화를 지원하므로 데이터를 받는 쪽의 프로세스가 바쁜 대기를 하지 않아도 된다.
- 소켓은 하나만 사용해도 양방향 통신이 가능하다.
정리
| 종류 | 운영체제 동기화 지원 | open()/close() 사용 |
| —– | ————— | —————– |
| 전역 변수 | x(바쁜 대기) | x |
| 파일 | x(wait() 함수 이용) | o |
| 파이프 | o | o |
| 소켓 | o | o |
공유 자원과 임계구역
공유 자원의 접근
- 공유 자원(shared resource): 여러 프로세스가 공동으로 이용하는 변수, 메모리 ,파일 등을 말한다.
- 공유 자원은 공동으로 이용되기 때문에 누가 언제 데이터를 읽거나 쓰느냐에 따라 그 결과가 달라질 수 있다.
- 2개 이상의 프로세스가 공유 자원을 병행적으로 읽거나 쓰는 상황을 경쟁 조건(race condition)이 발생했다고한다.
임계구역
- 임계구역(critical section): 공유 자원 접근 순서에 따라 실행 결과가 달라지는 프롬그램의 영역
생산자-소비자 문제
임계구역과 관련된 전통적인 문제로 생산자-소비자 문제(producer-consumer problem)가 있다.
상황
- 생산자 프로세스와 소비자 프로세스가 서로 독립적으로 작업한다.
- 생산자 프로세스: 물건을 생산해서 버퍼에 넣는다.
- 소비자 프로세스: 버퍼에서 물건을 가져온다.
- 버퍼는 작업을 계속하기 위해 원형 버퍼를 사용한다.
- 버퍼가 비었는지 가득 찼는지 확인하기 위해 sum이라는 전역 변수를 사용한다. sum은 현재 버퍼에 있는 상품의 총수가 저장된다.

- 생산자 프로세스와 소비자 프로세스가 서로 독립적으로 작업한다.
문제가 발생하는 상황
- 생산자가 물건 하나를 buf 4에 저장했다. sum을 4로 바꿔야 하나 아직 바꾸지 못했다.
- 소비자가 물건 하나를 소비했다. sum을 2로 바꿔야 하나 아직 바꾸지 못했다.
- 이 상태에서 1. 생산자의
sum = sum + 1;
과 2. 소비자의sum = sum - 1;
이 거의 동시에 실행되면 문제가 발생한다. 생산자와 소비자가 독립적이기 떄문에 상대방이 sum을 바꾸려는 것을 모른채 현재 상태인 sum = 3을 읽어서 작업을 한다. - 미세한 시간 차를 두고 1, 2 또는 2, 1 순서로 실행되면 sum 값을 2나 4가 된다.
임계구역 해결 조건
- 상호 배제(Mutual Exclusion): 한 프로세스가 임계구역에 들어가면 다른 프로세스는 임계구역에 들어갈 수 없다.
- 한정 대기(Bounded Waiting): 어떤 프로세스도 무한 대기하지 않아야 한다. 즉 특정 프로세스가 임계구역에 진입하지 못하면 안된다.
- 진행의 융통성(Progress): 한 프로세스가 다른 프로세스의 진행을 방해해서는 안 된다.
임계구역 해결 방법
임계구역 해결 조건을 고려한 코드 설계
상호 배제 문제

- 상호 배제 문제를 해결한 것처럼 보이지만 해결하지 못했다. 아래의 경우에 해당된다.
- P1이
while(lock==true)
를 통과하고 타임아웃이 발생하여 대기 상태가 된다. - P2가
while(lock==true)
를 통과한다. - P1과 P2가 임계구역에 진입할 수 있게된다.
- P1이
- 또한 잠금이 풀리기 위해서 바쁜 대기를 해야된다는 단점도 있다.
- 상호 배제 문제를 해결한 것처럼 보이지만 해결하지 못했다. 아래의 경우에 해당된다.
한정 대기 문제

- 상호 배제가 보장된다.
- 하지만 아래의 경우에 교착 상태(deadlock)이 발생할 수 있다.
- P1이
lock1=true;
를 실행한 후 타임아웃이 발생하여 P2가 실행 상태로 바뀐다. - P2가
lock2=true;
를 실행한다. - P1과 P2는 while문에서 서로가 lock을 풀어주기를 기다리는 상태가 된다.
- P1이
- 또한, 확장성에도 문제가 있다. 프로세스가 늘어나면 검사해야 하는 lock의 개수도 늘어나 비효율적이다.
진행의 융통성 문제

- 상호 배제와 한정 대기를 보장한다.
- 진행의 융통성 융통성을 보장하지 못한다.
- 한 프로세스가 두 번 연달아 임계구역에 진입하고 싶어도 그럴 수 없다.
- 이렇게 프로세스의진행이 다른 프로세스로 인해 방해받는 현상을 경직된 동기화(lockstep synchronization)라고 한다.
하드웨어적인 해결 방법
- 상호배제 문제 코드에서
while(lock==true);
문과lock=true;
문이 하드웨어적으로 동시에 실행하면 임계구역 문제를 해결할 수 있다.

- 여전히 바쁜 대기를 사용해서 자원 낭비가 발생하는 문제가 있다.
- 상호배제 문제 코드에서
피터슨 알고리즘

- 두 프로세스가 동시에 lock을 설정했더라도 turn을 사용하여 다른 프로세스에게 양보하게 된다.
- 하드웨어의 도움이 필요가 없다.
- 임계구역 해결의 세 가지 조건을 모두 해결했다.
- 하지만, 2개의 프로세스만 사용 가능하다는 한계가 있다.
데커 알고리즘

- 만약, 동시에 lock이 걸렸을 경우 turn을 확인하여, turn에 해당하는 프로세스가 락을 풀어 기다리는 구조다.
- 하드웨어의 도움이 필요없다.
- 프로세스가 늘어나면 변수도 늘어나고 전체적인 알고리즘도 복잡해진다.
세마포어
- 기존 알고리즘들은 바쁜 대기를 사용하여 자원을 낭비하고 복잡하다는 단점이 있다. 세마포어는 이를 해결할 수 있다.
- 세마포어는 임계구역에 진입하기 전에 스위치를 사용 중으로 놓고 임계구역으로 들어간다. 이후에 도착하는프로세스는 앞의 프로세스가 작업을 마칠 때까지 기다린다.
- 프로세스가 작업을 마치면 세마포어는 다른 프로세스에 임계구역을 사용하라는 동기화 신호를 보낸다. 이를 통해 바쁜 대기를 할 필요가 없다.

- Semaphore(n): 전역변수 RS를 n으로 초기화 한다. RS에는 현재 사용 가능한 자원의 수가 저장된다.
- P(): 잠금을 수앻아는 코드로, RS가 0보다 크면 1만큼 감소시키고 임계구역에 진입한다. 만약 RS가 0이하면 0보다 커질 때까지 기다린다.
- V(): 잠금 해제와 동기화를 같이 수행한다. RS값을 1 증가하고 세마포어에서 기다리는 프로세스에게 임계구역에 진입해도 좋다는 wake_up 신호를 보낸다.
- P()와 V()는 내부 코드를 검사와 지정(TestAndSet)을 사용하여 분리 실행되지 않고 완전히 실행되게 해야한다.
모니터
- 세마포어는 P()나 V() 호출을 잘못 사용하면 임계구역을 보호하지 못 할 수도 있다는 문제가 있다.
- 공유 자원을 사용할 때 모든 프로세스가 세마포어 알고리즘을 따르도록 구현한다면 굳이 P()와 V()를 직접 사용할 필요가 없어진다. 이를 실제로 구현한 것이 모니터(monitor)이다.
- 모니터가 구현되어 있는 대표적인 예가 Java이다.
파일, 파이프, 소켓 프로그래밍
파일
- 파일 내의 데이터는 한 줄로 길게 저장되는 이를 순차 파일(sequential file)이라고 한다.
- 순차 파일에 접근하는 방식을 순차적 접근(sequential access)이라한다.
- open() 함수로 파일을 여는 방식에는 읽기 전용, 읽기/쓰기, 쓰기 전용, 생성 등이 있다.
- 자식 프로세스에 파일 기술자가 복사되기 때문에 open() 한 번이지만 close() 자식 프로세스에서 한 번, 부모 프로세스에서 한 번, 총 두 번 발생한다.
- 파일 기술자의 위치 정보는 부모 프로세스와 자식 프로세스가 공유한다. 따라서 부포 프레서스는 lseek() 함수를 통해 파일 기술자의 위치를 움직여야된다.
파이프
- 파일과 다르게 파이프에서는 read()와 write()의 기술자가 따로 존재한다. 다라서 동기화가 가능하다.
- 부모 프로세스와 자식 프로세스에서 각자 필요없는 파일 기술자를 닫는다.
- 자식 프로세스가 fd[1]에 쓰고 부모 프로세스가 fd[0]으로 받는 구조이다.
- 자식 프로세스에서는 fd[0]을 닫고, 부모 프로세스에서는 fd[1]을 닫는다.
네트워킹
- 클라이언트
- 소켓을 생성한 후 connect()를 통하여 서버와 접속을 시도한다.
- 서버와 접속되면 read()혹은 write() 작업을 하며, 작업이 끝나면 소켓 기술자를 닫는다.
- 서버
- 소켓을 생성한 후 bind()를 사용하여 생성한 소켓을 특정 포트에 등록한다.
- 소켓이 포트에 등록되면 listen()을 실행하여 클라이언트를 받을 준비를 한다.
- accept()는 여러 클라이언트의 connect() 중 하나를 골라 작업을 시작하게 해준다.
- 하나의 포트에 소켓 여러 개 생성이 가능하다.
- 소켓을 하나만 생성할 수 있다면 네이버가 운영하는 서버가 단 한 사람만 접속할 수 있다는 의미다.
연습 문제
반양방향 통신
바쁜 대기
아래
- 대기가 없는 통신: 파일, 전역 변수
- 대기가 있는 통신: 파이프, 소켓
파이프는 단방향 통신만 가능하기 때문에 양방향 통신을 하기 위해서는 2개가 필요하다.
경쟁 조건
임계 구역
상호 배제
진행의 융통성(Progress)
세마포어
동시에 임계구역에 접근할 수 있는 프로세스의 개수
아래
if RS>0 then RS=RS-1; else block();
아래
RS=RS+1; wake_up();
P()와 V()의 호출을 잘못한 경우
검사 및 지정
모니터
심화 문제
아래
- 단방향 통신: 데이터를 한 방향으로만 전송할 수 있는 통신(전역변수, 파일, 파이프)
- 반양방향 통신: 데이터를 한 방향으로 전송할 수 있지만 한 순간에는 한 방향으로만 전송할 수 있는 통신(무전기)
- 양방향 통신: 데이터를 양 방향으로 전송할 수 있는 통신(소켓)
아래
- 대기가 없는 통신: 데이터를 받는 쪽이 바쁜 대기를 통해 데이터가 도착했는지 직접 확인해야되는 통신. (전역변수, 파일)
- 대가기 있는 통신: 데이터를 주는 쪽이 데이터를 받는 쪽에게 데이터가 도착했다는 것을 알려주는 통신. (파이프, 소켓)
믹서기를 사용할 때 동시에 여러 명이 믹서기를 사용할 수 없다. 믹서기가 돌아가면 다른 재료들은 믹서기가 멈출 때 까지 기다려야한다.
상호배제가 보장되지 않는다. P1의 while문이 통과되고 타임아웃이 발생해 P2의 while문이 통과하여 두 프로세스가 모두 임계구역에 진입할 수 있게 된다.
진행의 융통성이 보장되지 않는다. P2가 연속으로 임계구역에 진입하고 싶어도 P1이 임계구역을 한번 거칠 동안은 대기상태가 된다.
아래
#inlude <stdio.h> #include <unistd.h> #include <fcntl.h> void main() { int pid, fd; char buf[5]; fd = open("com.txt", O_RDWR); pid = fork(); if (pid < 0 || fd < 0) exit(-1); else if (pid == 0) { write(fd, "Test", 5); close(fd); exit(0); } else { wait(0); lseek(fd, 0, SEEK_SET); read(fd, buf, 5); printf("%s", buf); close(fd); exit(0); } }