나를 기록하다
article thumbnail
반응형

쉽게 배우는 운영체제

프로세스 간 통신

프로세스 간 통신의 개념

  • 프로세스(또는 스레드)는 독립적으로 실행
  • 협업 또는 데이터 병렬 처리를 위해서는 데이터를 주고 받아야 함
  • 스레드: 하나의 프로세스 내에서 자원을 공유하는 실행 단위
프로세스 간 통신(IPC; Inter Process Communication)
하나의 프로세스에 속한 스레드끼리 데이터를 주고받는 것은 어렵지 않음.
서로 다른 프로세스는 사용하는 메모리 영역이 다르기 때문에 공유된 메모리를 통하여 통신하는 것이 어려움.
운영체제가 제공하는 프로세스와 프로세스끼리 쉽게 데이터를 주고받을 수 있는 통신 방법을 IPC라고 함.

프로세스 간 통신 종류

  • 공유 메모리나 공유 파일을 이용한 통신(프로세스 내부 데이터 통신)
    • 프로세스 간 통신 중 가장 원시적인 방식
    • 일정한 메모리 영역이나 파일을 공유하고 이를 통하여 데이터를 주고 받음
    • 데이터를 주고받는 방법을 프로세스끼리 알아서 결정해야 함
  • 파이프를 이용한 통신(프로세스 간 데이터 통신)
    • 하나의 컴퓨터 내에서 프로세스 간 통신에 많이 사용되는 수단
    • 프로세스 간 통신을 위하여 운영체제가 제공하는 통신 기법
    • 파이프는 fork()로 만들어진 부모-자식 간 통신에 사용
  • 소켓을 이용한 통신(네트워크를 이용한 데이터 통신)
    • 컴퓨터와 컴퓨터가 네트워크로 연결된 경우의 통신에 주로 사용되는 수단
    • 네트워크로 연결된 컴퓨터에서 데이터를 주고받기 위한 통신 기법
    • 같은 컴퓨터 내의 프로세스끼리도 소켓으로 통신 가능
    • 파이프와 비교시 초기화할 내용도 많고 시스템 자원도 많이 사용
      → 같은 컴퓨터 내에서 소켓으로 통신하는 것은 비효율적
통신이란?
데이터를 주거나(send) 받는(receive) 것
데이터를 보내는 프로세스는 send(message) 사용
데이터를 받는 쪽에서는 receive(message) 사용
데이터를 주고받는 작업은 복잡하다.
(예시) 통신하려는 상대 프로세스를 어떻게 찾을지, 데이터의 크기는 얼마로 할지, 데이터 도착 여부를 어떻게 확인할지 등의 문제를 해결해야 함

 

프로세스 간 통신의 분류

통신 방향에 따른 분류

  • 양방향 통신(duplex communication)
    • 데이터를 양쪽 방향으로 동시에 전송할 수 있는 구조, 일반적인 통신
    • 소켓을 이용한 통신
  • 반양방향 통신(halfduplex communication)
    • 데이터를 양쪽 방향으로 전송할 수 있지만 동시 전송 불가능, 특정 시점에 한쪽 방향으로 전송할 수 있는 구조
    • 무전기
  • 단방향 통신(simplex communication)
    • 모스 부호처럼 한쪽 방향으로만 데이터를 전송할 수 있는 구조
    • 공유 메모리나 공유 파일을 이용한 통신, 파이프를 이용한 통신

 

통신 구현 방식에 따른 분류

  • 공유 메모리를 사용하는 통신 방식의 가장 큰 문제점: 상대방이 데이터를 언제 보낼지 받는 쪽에서는 모른다는 것
  • 바쁜 대기(busy waiting)
    • 상태 변화를 살펴보기 위해 반복문을 무한 실행하며 기다리는 것
    • 시스템 차원에서 자원 낭비가 크기 때문에 안좋은 프로그램의 전형적인 예
  • 동기화(synchronization)
    • 바쁜 대기 문제를 해결하기 위해 데이터가 도착했음을 알려준다.
    • 동기화 사용 시 바쁜 대기를 하지 않아도 운영체제가 알아서 알려준다.
    • (예시) 메신저 메시지 도착 알림
  • 대기가 있는 통신(blocking communication), 동기화 통신(synchronous communication)
    • 동기화를 지원하는 방식
    • 데이터를 받는 쪽은 데이터가 도착할 때까지 자동으로 대기 상태에 머물러 있음
    • 통신 기법 - 파이프, 소켓
    • 통신 기기 - 전화
  • 대기가 없는 통신(non-blocking communication), 비동기화 통신(asynchronous communication)
    • 동기화를 지원하지 않는 방식
    • 데이터를 받는 쪽은 바쁜 대기를 사용하여 데이터가 도착했는지 여부를 직접 확인
    • 통신 기법 - 공유 메모리, 공유 파일
    • 통신 기기 - 전보

 

프로세스 간 통신의 종류

파일을 이용한 통신

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{	int fd;
	char buf[5];
    
    fd=open("com.txt", 0_RDWR);
    
    write(fd, "Test", 5);
    
    read(fd, buf, 5);
    
    close(fd);
    
    exit(0);
}

파일 열기

  •  open()
    • 준비 단계, 사용하고자 하는 파일이 있는지, 파일이 있다면 쓰기 권한이 있는지 확인
    • 정상적으로 파일을 사용할 수 있다면 fd 반환
  • fd(file descriptor; 파일 기술자)
    • 해당 파일에 접근할 수 있는 일종의 열쇠
    • 어떤 파일에 쓰기나 읽기 연산을 하려면 먼저 fd를 얻고 작업이 다 끝나면 fd를 반환해야 함
  • open("com.txt", 0_RDWR)
    • com.txt 파일을 읽기와 쓰기를 할 수 있는 형태로 준비한다는 뜻
    • 0_RDWR : 읽기와 쓰기 작업을 하겠다는 것
    • 0_RDONLY : 읽기 전용
    • 정상적으로 파일이 열리면 open() 함수는 fd를 사용자에게 반환

파일 쓰기 또는 읽기

  • write(fd, "Test", 5)
    • fd, 즉 com.txt 파일에 Test라는 문자열을 쓰라는 뜻
    • Test의 크기가 5B이기에 마짐가에 5라고 명시(문자열의 끝을 알리는 특수 부호인 null 포함)
  • read(fd, buf, 5) : fd, 즉 com.txt 파일에서 5B를 읽어 변수 buf에 저장하라는 뜻

파일 닫기

  • close(fd) : fd가 가리키는 파일, 즉 com.txt 파일을 닫는다.

 

  • 프로세스가 입출력 관리 프로세스에
    1. 쓰기를 요구 → 데이터 저장
    2. 읽기를 요구 → 입출력 관리 프로세스로부터 데이터를 가져옴
  • 쓰기 연산 : 하드디스크에 데이터를 전송하는 명령
  • 읽기 연산 : 하드디스크로 데이터를 가져오는 명령

→ 파일 입출력도 통신이다.

운영체제 입장에서 보면 저장장치의 데이터를 읽고 쓰는 것도 일반 프로세스와 입출력 프로세스 간의 통신이다.

 

  • 파일을 이용한 통신
    • 부모 - 자식 관계의 프로세스 간 통신에 많이 사용
    • 운영체제가 프로세스 동기화 제공 x
    • 주로 부모 프로세스가 wait() 함수를 이용하여 자식 프로세스의 작업이 끝날 때까지 기다렸다가 작업 시작

 

파이프를 이용한 통신

  • 운영체제가 제공하는 동기화 통신 방식
  • open() 함수로 기술자를 얻어 작업한 후 close() 함수로 마무리
  • 단방향 통신(공유 메모리나 공유 파일을 이용한 통신과 같음)
  • 파이프로 양방향 통신을 하려면 파이프 2개를 사용해야 함
  • 쓰기 연산 : 데이터 전송 / 읽기 연산 : 데이터 수신

파이프를 이용한 통신

  • 만약 파이프 B가 파이프 1에 대해 읽기 연산을 수행했는데 프로세스 A가 파이프 1에 아직 쓰기 연산을 하지 않았다면 프로세스 B는 대기 상태가 된다. 이러한 대기 상태는 프로세스 A가 파이프 1에 데이터를 쓰는 순간 자동으로 풀려 동기화가 이루어진다. 따라서 프로세스 B는 바쁜 대기를 하지 않아도 된다.
  • 이름 없는 파이프(anonymous pipe)
    • 일반적으로 파이프라고 하면 이름 없는 파이프를 가리킴
    • 부모와 자식 프로세스 혹은 같은 부모를 가진 자식 프로세스와 같이 서로 관련 있는 프로세스 간 통신에 사용
  • 이름 있는 파이프(named pipe)
    • FIFO라 불리는 특수 파일을 이용하며 서로 관련 없는 프로세스 간 통신에 사용

 

소켓을 이용한 통신

네트워킹에서 소켓의 의미

  • 네트워킹: 여러 컴퓨터에 있는 프로세스끼리의 통신, 소켓을 이용하여 통신한다.
  1. 인터넷 사용시 TCP(Transmission Control Protocol) / IP(Internet Protocol)가 있어야 함
    • IP의 역할: 목적지까지 데이터를 전송하는 것. 이때 사용되는 주소가 IP 주소
  2. 데이터는 IP 주소에 해당하는 컴퓨터에 도착
  3. 해당 컴퓨터에는 네트워크를 사용하는 많은 수의 프로세스 존재, 각 서버로부터 컴퓨터에 도착하는 모든 데이터의 IP 주소는 같음 → 하나의 컴퓨터 내에서 네트워크를 사용하는 각 프로세스를 구분할 주소가 포트(port) 번호
  • 포트 번호: TCP가 네트워크를 사용하는 프로세스를 구분하기 위해 사용하는 주소
    • IP 주소 : 아파트의 동 번호
    • 포트 번호 : 해당 동의 호수
  • 네트워크를 사용하는 프로세스가 생성되면 운영체제로부터 0 ~ 65535 사이의 포트 번호를 부여받음
  • 데몬(daemon) :  서버에서 돌아가는 프로세스
    • 서버용 데몬도 포트 번호가 필요함
    • 서버용 데몬에 임의의 포트 번호를 부여하면 클라이언트가 해당 번호를 매번 찾아야 함
      → 중요한 서버용 프로세스(데몬)에는 미리 정해진 포트 번호 할당
      (예시) HTTPD : 80, FTPD : 21 등
  • 소켓(socket)
    • 하나의 포트에 여러 클라이언트를 연결하려면 소켓이 필요함
    • 클라이언트들은 포트에 연결된 멀티 소켓에 하나씩 연결됨
    • 서버 프로세스(데몬)은 소켓을 사용하여 동시에 여러 클라이언트에 서비스 제공
네트워크에서 데이터를 보낸다는 것 = 클라이언트 소켓이 서버 소켓에 데이터를 보내는 것
→ 그래서 네트워크 프로그래밍을 소켓 프로그래밍이라고 부른다.
  • 네트워킹 상황에서 통신할 때는 원격 프로시저 호출이나 소켓을 이용
    • 프로시저 호출 : 한 컴퓨터에 있는 함수 호출
    • 원격 프로시저 호출 : 다른 컴퓨터에 있는 함수 호출
      (예시) 자바에서 다른 컴퓨터에 있는 객체의 메서드 호출

소켓을 이용한 통신

  • 일반적으로 원격 프로시저 호출은 소켓을 이용하여 구현
  • 다른 컴퓨터에 있는 프로세스와 통신하려면 그 컴퓨터의 위치를 파악하고, 원격지의 시스템 내 여러 프로세스 중 어떤 것과 통신할지 결정해야 함
  • 통신하려는 프로세스가 소켓에 쓰기 연산을 하면 데이터 전송, 읽기 연산을 하면 데이터 수신
  • 소켓은 프로세스 동기화 지원 → 바쁜 대기 필요 X

공유 자원과 임계구역

공유 자원에 대한 접근

  • 공유 자원(shared resource): 여러 프로세스가 공동으로 이용하는 변수, 메모리, 파일

공유 자원에 대한 접근

  • 경쟁 조건(race condition) : 위 그림처럼 2개 이상의 프로세스가 공유 자원을 병행해서 읽거나 쓰는 상황

 

임계구역(critical section)

  • 공유 자원 접근 순서에 따라 실행 결과가 달라지는 프로그램의 영역
  • 각 프로세스가 전역 변수를 사용하는 부분, 즉 예금을 확인하고 입금한 후에 예금을 저장하는 부분이 임계구역

 

생산자 - 소비자 문제(producer-consumer problem)

  • 생산자-소비자 문제에서 생산자 프로세스와 소비자 프로세스가 서로 독립적으로 작업한다.
  • 생산자는 물건을 계속 생산해서 버퍼에 넣고[input(buf)] 소비자는 버퍼에서 계속 물건을 가져온다[output(buf)].
  • 작업을 계속하기 위해 버퍼는 원형 버퍼를 사용

실행 순서에 따른 결과 차이

  • 생산자 코드와 소비자 코드가 동시에 실행되면 문제 발생
    → 생산자와 소비자가 전역 변수 sum에 접근하는 타이밍을 서로 맞추지 않았기 때문
    1. 생산자가 물건 하나를 buf에 저장. sum을 4로 바꿔야 하지만 아직 바꾸지 못함
    2. 소비자가 물건 하나 소비. sum을 2로 바꿔야 하지만 아직 바꾸지 못함
    3. 이 상태에서 1번 생산자의 sum = sum + 1과 2번 소비자의 sum = sum - 1이 거의 동시에 실행되면 문제 발생
    4. 생산자와 소비자가 독립적이기 때문에 상대방이 sum을 바꾸려는 것을 모른 채 현재 상태인 sum = 3을 읽어서 작업
    5. 미세한 시간 차를 두고 1번, 2번 또는 2번, 1번 순서로 실행되면 sum 값은 2나 4가 된다.

 

임계구역 문제 해결 조건

  • 상호 배제(mutual exclusion)
    • 한 프로세스가 임계구역에 들어가면 다른 프로세스는 임계구역에 들어갈 수 없음
    • 임계구역 내에는 한 번에 하나의 프로세스만 있어야 함
  • 한정 대기(bounded waiting)
    • 어떤 프로세스도 임계구역에 진입하지 못하여 무한 대기하지 않아야 함
  • 진행의 융통성(progress flexibility)
    • 한 프로세스가 다른 프로세스의 진행을 방해해서는 안됨

임계구역 문제 해결 방법

  • 잠금(lock)을 이용하는 것
  • 임계구역 문제를 해결하기 위한 세 가지 조건인 상호 배제, 한정 대기, 진행의 융통성을 모두 만족하는 잠금, 잠금 해제, 동기화 구현 방법

 

기본 코드 소개

  • boolean 변수 - true / false 값을 가지므로 잠금을 표현하기에 적합

 

임계구역 문제 해결 조건을 고려한 코드 설계

상호 배제 문제

전역 변수로 잠금을 구현한 코드

  • lock=true : 잠겨 있다 → 잠금이 해제될 때까지 무한 루프를 돌면서 대기한다.
  • lock=false : 잠금 해제 → lock을 걸고 작업을 하며 작업을 마치면 잠금을 해제한다.
    • 임계구역을 사용해도 좋다고 다른 프로세스에 보내는 동기화 신호

 

문제 발생 - 동시 진입 상황(상호 배제 조건 충족 X)

문제

  • 임계구역에 진입한 프로세스 없음(lock=false), 1 →2 →3 →4 순서로 실행
  1. P1이 while(lock==true) 실행. 임계구역에 프로세스 x → 무한 루프 빠져나옴. 하지만 다음 문장 실행하려는 순간 자신에게 주어진 CPU 시간을 다 써서(타임아웃) 준비 상태로 옮겨짐. 문맥 교환이 발생하고 P2가 실행 상태로 바뀜
  2. P2가 while(lock==true) 실행. P1에 잠금을 걸지 않았기 때문에 lock은 여전히 false, P2는 임계구역 진입 가능
  3. P1이 lock=true 실행. 임계구역에 잠금을 걸고 진입
  4. P2도 lock=true 실행. 임계구역에 잠금을 걸고 진입, 결국 둘 다 임계구역에 진입

문제점

  1. 둘 다 임계구역에 진입하므로 상호 배제 조건을 보장 못함
  2. while(lock==true) : 잠금이 풀리기를 기다리려면 바쁜 대기를 해야함

한정 대기 문제

상호 배제 조건을 충족하는 코드

  • 잠금 2개 사용 → 상호 배제 보장
  1. p1이 lock1=true 실행 후 타임아웃. 문맥 교환 발생, p2가 실행 상태로 바뀜
  2. p2도 lock2=true 실행 후 타임아웃. 문맥 교환 발생, p1이 실행 상태로 바뀜
  3. p2가 lock2=true 실행 → p1은 while(lock2==true)문에서 무한 루프 빠짐
  4. p1이 lock1=true 실행 → p2도 while(lock1==true)문에서 무한 루프 빠짐

p1과 p2 둘다 while문을 빠져나오지 못하고 무한 루프에 빠져서 임계구역에 진입 불가 → 한정 대기 조건 보장하지 못하는 상황으로 교착 상태(deadlock)라고 함. 교착 상태는 프로세스가 살아 있으나 작업이 진행되지 못하는 상태

 

확장성 문제

  • p1은 자신의 lock1을 잠그고 lock2를 검사
  • 만약 프로세스가 3개라면 lock3을 만들고 다른 프로세스들이 lock3을 검사해야 함
  • 이처럼 프로세스가 늘어나면 검사해야 하는 lock의 개수도 늘어나 비효율적

 

진행의 융통성 문제

상호 배제와 한정 대기 조건을 충족하는 코드

  • lock = 1 → p1이 임계구역 사용
  • lock = 2 → p2가 임계구역 사용

문제는 서로 번갈아가면서 실행된다는 점 → 한 프로세스가 두 번 연달아 임계구역에 진입하고 싶어도 그럴 수 없음

  • p1은 p2가 임계구역에 진입했다가 나온 다음에야 다시 진입 가능 → p2가 p1의 진행을 방해하는 구조
  • 경직된 동기화(lockstep synchronization) : 프로세스의 진행이 다른 프로세스로 인해 방해받는 현상
    → 진행의 융통성 조건 보장 X

 

하드웨어로 해결 방법

검사와 지정을 이용한 코드

  • while(lock==true) 구문과 lock=true 구문이 분리되어 실행되면(명령어 사이에 타임아웃이 걸리면) 문제 발생
  • 하드웨어로 두 명령어를 동시에 실행하면 임계구역 문제 해결 가능
    → 검사와 지정(test-and-set)

 

피터슨 알고리즘

  • 임계구역 문제를 해결하기 위해 게리 피터슨이 제안

피터슨 알고리즘

  1. p1은 임계구역에 진입하기 전에 먼저 잠금을 걸고(lock1=true) turn을 2로 설정
  2. 변수 turn은 두 프로세스가 동시에 lock을 설정하여 임계구역에 못 들어가는 상황에 대비하기 위한 장치
  3. 두 프로세스가 동시에 lock을 설정했더라도 turn을 turn을 사용하여 다른 프로세스에 양보
  4. 이어서 while(lock2==true && turn==2)문 실행
  5. 만약 p2가 잠금을 설정하지 않았거나 잠금을 설정했더라도 곧바로 turn=1로 바꾸면 p1은 임계구역에 진입하여 작업을 마친 후 잠금을 해제하고 임계구역을 빠져나옴.
  6. p2도 같은 방식으로 임계구역 진입

한계

  1. 2개의 프로세스만 사용 가능함
  2. 여러 프로세스가 하나의 임계구역을 사용하려면 공유 변수를 추가하고 코드 변경해야 함

 

데커 알고리즘

  • 기존의 알고리즘 → 하드웨어의 도움 필요
  • 데커 알고리즘은 하드웨어 도움 없이 임계구역 문제 해결 가능

데커 알고리즘

  1. p1은 우선 잠금을 건다(lock1=true)
  2. p2의 잠금이 걸렸는지 확인[while(lock2==true)]
  3. p2도 잠금을 걸었다면 누가 먼저인지 확인[if (turn==2)]
    1. p1의 차례(turn==1) : 임계구역 진입
    2. p2의 차례(turn==2) : 4번으로 이동
  4. p1은 잠금을 풀고(lock1=false) p2가 작업을 마칠 때까지 기다림[while(turn==2)]
    p2가 작업을 마치면 잠금을 걸고(lock1=true) 임계구역으로 진입
피터슨 알고리즘이나 데커 알고리즘
임계구역 문제 해결의 세 가지 조건 모두 만족 but 복잡함
프로세스가 늘어나면 변수도 늘어나고 전체 알고리즘도 복잡햊미
임계구역을 보호하기 위해 복잡한 알고리즘을 구현하는 것은 바람직한 접근 방법이 아님

 

세마포어(semaphore)

  • 기존의 임계구역 문제 해결 알고리즘은 바쁜 대기를 사용하여 자원을 낭비하거나 너무 복잡함
  • 이런 단점을 보완하고자 데이크스트라(Dijkstra)는 세마포어(semaphore)라는 알고리즘 제안

 

  1. 프로세스가 임계구역에 진입하기 전에 스위치를 사용 중으로 놓고 임계구역으로 들어감
  2. 이후 도착하는 프로세스는 앞의 프로세스가 작업을 마칠 때까지 대기
  3. 프로세스가 작업을 마치면 세마포어는 다음 프로세스에 임계구역을 사용하라는 동기화 신호 전송
  4. 다른 알고리즘과 달리 임계구역이 잠겼는지 직접 점검하거나, 바쁜 대기, 동기화 메시지를 보낼 필요가 없음

세마포어 코드

  1. Semaphore(n)
    • 사용 전 초기 설정, n은 공유 가능한 자원의 수
    • 전역 변수 RS를 n으로 초기화. RS에는 현재 사용 가능한 자원의 수 저장
  2. P()
    • 잠금 수행
    • RS가 0보다 크면(사용 가능한 자원 존재) 1만큼 감소시키고 임계구역 진입
    • RS가 0보다 작으면(사용 가능한 자원 없음) 0보다 커질 때까지 기다림
    • block()은 wake_up()이 신호를 보낼 때까지 기다리는 함수
  3. V()
    • 잠금 해제와 동기화를 같이 수행하는 코드
    • Rs 값을 1 증가시키고 세마포어에서 기다리는 프로세스에 임계구역에 진입해도 좋다는 wake_up() 신호를 보냄

 

  • 바쁜 대기를 하는 프로세스가 없음
  • 하지만 세마포어의 P()나 V() 내부 코드가 실행되는 도중에 다른 코드가 실행되면 상호 배제와 한정 대기 조건 보장 X
  • 그러므로 P()와 V()의 내부 코드는 검사와 지정을 사용하여 완전히 실행되도록 구현

공유 자원 1개, 두 개의 프로세스

세마포어 사용 예

  1. p1이 임계구역 진입. RS는 1이므로 이 값을 1 감소시키고 임계구역 진입
  2. p2는 현재 RS 값이 0이므로 p1이 임계구역을 빠져나올 때까지 세마포어 큐에서 대기
  3. p1은 현재 예금이 10만 원인 것을 확인하고 10만 원을 더해 20만 원으로 바꾼 다음 작업을 마침
  4. p1은 V()를 실행하여 RS 값을 1 증가시키고 wake_up() 신호를 p2에 보냄
  5. wake_up() 신호를 받은 p2가 작업 시작. p2는 현재 예금이 20만 원인 것을 확인하고 5만 원을 더해 25만 원으로 바꿈

공유 자원 2, 세 개의 프로세스

공유 자원 2, 세 개의 프로세스

공유 자원이 2개이면 Semaphore(n)의 초깃값은 2

  1. p1은 RS 값을 1 감소시키고 임계구역 진입
  2. p2도 RS 값을 1 감소시키고 임계구역 진입
  3. p3은 RS 값이 0이므로 다른 프로세스가 임계구역을 빠져나올 때까지(RS가 0보다 커질 때까지) 기다림
  4. p1이 작업을 마치고 V()를 실행하면 RS 값은 1이 되고 wake_up() 신호가 p3에 전달됨
  5. p3가 임계구역에 진입

 

모니터

  • 세마포어의 가장 큰 문제점 : 잘못된 사용으로 인해 임계구역이 보호받지 못한다는 것

세마포어의 잘못된 사용 예

  1. 프로세스가 세마포어를 사용하지 않고 바로 임계구역에 들어간 경우 임계구역을 보호할 수 없음
  2. P()를 두 번 사용하여 wake_up() 신호가 발생하지 않은 경우 프로세스 간의 동기화가 이뤄지지 않아 세마포어 큐에서 대기하고 있는 프로세스들이 무한 대기에 빠짐
  3. P()와 V()를 반대로 사용하여 상호 배제가 보장되지 않은 경우로 임계구역 보호할 수 없음
  • 공유 자원을 사용할 때 모든 프로세스가 세마포어 알고리즘을 따른다면 굳이 P()와 V()를 사용할 필요 없이 자동으로 처리하면 된다 → 이를 실제로 구현한 것이 모니터(monitor)
  • 모니터
    • 공유 자원을 내부적으로 숨기고 공유 자원에 접근하기 위한 인터페이스만 제공
      → 자원 보호, 프로세스 간 동기화
    • 시스템 호출과 같은 개념, 운영체제가 관리하는 자원을 사용자가 마음대로 사용하게 두면 실수 또는 악의적인 의도로 시스템 자원 망가뜨릴 수 있음
    • 이런 문제 예방을 위해 운영체제는 시스템 자원을 사용자로부터 숨기고 사용자 요구 사항을 처리할 수 있는 인터페이스만 제공 → 시스템 호출

모니터의 작동 원리

  1. 임계구역으로 지정된 변수나 자원에 접근하고자 하는 프로세스는 직접 P()나 V()를 사용하지 않고 모니터에 작업 요청
  2. 모니터는 요청받은 작업을 모니터 큐에 저장한 후 순서대로 처리, 결과만 해당 프로세스에 알려줌

 

사용법

예금 예시에서 사용자는 increase(10), increase(5)와 같이 넣어줄 돈을 인자로 넘겨주기만 하면 됨

→ 사용자는 복잡한 코드 실행하지 않아서 좋고, 시스템은 임계구역을 보호할 수 있어서 좋음

자바로 작성한 모니터 내부 코드

  • balance: 10만원이 들어있는 예금
  • busy: 잠금 역할
  • mon: 잠금 해제 역할

이 변수들은 public 영역에 있는 increase()만 사용하여 예금 데이터에 접근 가능하다.

increase()는 임계구역이 잠겼는지 확인하고 다른 프로세스가 사용하지 않으면 잠금을 건 후 예금액을 증가시킨다.

모니터를 사용하면 임계구역의 보호나 프로세스의 동기화가 모니터 내부에서 처리되므로 사용자는 increase()를 호출하기만 하면 된다.


파일, 파이프, 소켓 프로그래밍

파일

순차 파일

  • 파일 내의 데이터는 한 줄로 길게 저장되는데 이러한 파일을 순차 파일(sequential file)이라 함
  • 순차 파일에 접근하는 방식을 순차적 접근(sequential access)이라 함 ex) 카세트테이프

파일 기술자

  • open() 함수로 파일을 열면 파일 기술자(fd)를 얻음
  • 파일 기술자는 현재 파일에서 어느 위치(offset)를 읽고 있는지를 가리킴
  • 처음 파일이 열리면 offset은 맨 앞에 위치
  • 파일을 읽거나 쓰면 offset은 전진

파일을 이용한 통신

  • 부모 프로세스와 자식 프로세스가 파일을 이용하여 통신을 하는 코드

파일을 이용한 통신 코드

  • 부모프로세스가 자식 프로세스보다 먼저 실행되면 자식 프로세스는 아무 작업을 하지 않았음
    →  부모 프로세스와 자식 프로세스 간에 동기화를 해주어야 함
  • fork() 이전에 파일을 open()하면 생성된 파일 기술자가 자식 프로세스에도 상속된다는 것
    → 부모와 자식 프로세스 모두 com.txt를 읽거나 쓸 수 있음
    • 하지만 open()은 한 번이지만, close()는 자식 프로세스에서 한 번, 부모 프로세스에서 한 번, 총 두 번 발생한다.
  • 자식 프로세스에서 Test를 라고 쓸 때 파일 기술자는 5번으로 이동
    → 부모 프로세스는 lseek()을 사용해 0번 위치로 이동 후 값을 읽어야 함
  • lseek() : 파일 기술자 fd를 임의로 움직이는 명령어
    • SEEK_SET : 파일의 맨 처음 위치
    • SEEK_CUR : 파일기술자의 현재 위치
    • SEEK_END : 파일의 맨 마지막 위
    • lssek(fd,0,SEEK_SET) : 파일의 맨 처음 위치를 기준으로 0번째 위치에 fd를 옮기라는 의미
  • 파일을 이용한 통신이든, 전역 변수를 이용한 통신이든 프로세스간 동기화를 지원해주지 않음 → wait()

파이프

  • 동기화를 지원하는 단방향 통신 시스템
  • 일반적으로 이름 없는 파이프를 가리킴
  • 부모와 자식 프로세스 혹은 같은 부모의 자식 프로세스처럼 서로 관련 있는 프로세스 간 통신에 사용

파이프를 이용한 통신 코드

  • 파이프에서는 read()와 write()의 기술자가 따로 존재 → 동기화 가능(fd[0] : 읽기, fd[1] : 쓰기)
  • 위 코드에서 부모에서는 close(fd[1]), 자식에서는 close(fd[0])으로 해당 로직에서 쓰지 않는 파이프 종료
  • 파이프로 양방향 통신을 구현하려면 파이프를 2개 사용해야 함
  • 대기가 있는 통신 → wait()가 필요 없음

 

네트워킹

  • 소켓을 이용한 네트워킹
    • open(), read()/write(), close() 구조 사용
    • 양방향 통신과 동기화를 함께 지원

클라이언트/서버의 통신 절차

클라이언트

  1. 클라이언트는 소켓을 생성한 후 connect()를 사용하여 서버와의 접속 시도
  2. 서버와 접속되면 read() 혹은 write() 작업을 하며, 작업이 끝나면 사용한 소켓 기술자(socket descriptor)를 닫고 종료

서버

  1. 소켓을 생성한 후 bind()를 사용하여 생성한 소켓을 특정 포트에 등록
  2. 여러 컴퓨터가 연결된 네트워크 환경에서는 각 컴퓨터를 IP 주소로 구분
    • 한 컴퓨터 내에도 여러 프로세스가 존재하기 때문에 어떤 프로세스와 통신할지 구분해야 함.
    • 이때 사용하는 구분 번호를 포트 번호(port number)라고 함
  3. 서버는 동시에 여러 클라이언트에 서비스하기 위해 하나의 포트 번호에 여러 개의 소켓을 생성
    • bind()는 특정 포트에 새로운 소켓을 등록하겠다는 의미
  4. 소켓이 정상적으로 등록되면 listen()을 실행하여 클라이언트를 받을 준비를 함
    • accept()는 클라이언트의 connect(), 연결 요청을 기다림
    • 여러 명의 클라이언트가 동시에 connect()를 하는 경우에 하나를 골라 작업 시작
      (클라이언트가 accept() 되면 소켓 기술자가 생성되고 작업이 시작됨)
    • read() 혹은 write() 작업을 마치면 생성된 소켓 기술자를 닫고 다음 클라이언트를 기다림

클라이언트 코드

  • 처음에 socket() 문으로 서켓을 생성하고 변수 sp로 이 소켓에 접근
  • socket() 아래 4줄은 초기화하는 부분
  • 변수 ad가 주소와 관련된 정보를 가짐
  • 이 코드에 사용한 IP 주소는 루프백 주소, 서버의 포트 번호는 임의의 값인 11234
    루프백 주소 : 인터넷이 연결되어 있지 않아도 사용 가능한 IP 주소, 127.0.0.1로 정해짐
  • 통신이 초기화되면 → sp와 ad를 이용하여 서버와 connect() 시도
  • 연결이 이루어지면 →
    • 소켓으로부터 5B를 읽어 화면에 출력
    • 사용한 소켓 기술자를 닫은 후 클라이언트 프로그램 종료

 

서버 코드

  • 통신 초기화 부분은 클라이언트 코드와 거의 비슷
  • ad.sin_addr.s_addr의 경우에 클라이언트에서는 서버의 IP 주소를 지정
    but 서버에서는 클라이언트의 주소를 알 수 없음 → htonl(INADDR_ANY)라고 지정
  • 통신을 초기화한 후 → 소켓 생성, bind()를 이용하여 소켓 등록
  • 클라이언트의 요청이 언제 들어올 지 알 수 없음 → 서버으 ㅣ소켓 생성은 listen()으로 클라이언트의 접속을 확인한 후 accept()에서 이뤄짐 → write() 작업을 하려면 sp 대신 sa를 소켓 기술자로 사용해야 함
  • 작업이 끝나면 소켓 기술자를 닫고 무한 루프를 돈다.
  • 서버의 경우 계속 클라이언트를 받아 작업해야 하기 때문에 무한 루프를 돌며 작업 반복
    → 여러 클라이언트가 접속하더라도 Test를 계속 클라이언트에 전송할 수 있음
반응형
profile

나를 기록하다

@prao

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

profile on loading

Loading...