운영체제 스터디 (6) - 프로세스 동기화

운영체제 스터디 도중 쉽게 배우는 운영체제를 읽고 요약한 내용입니다. 자세한 내용은 책을 구매하여 확인 부탁드립니다.

5. 프로세스 동기화

프로세스 간 통신

프로세스 간 통신의 개념

  • 프로세스와 프로세스가 서로 데이터를 주고받으며 협업하는 과정
  • 프로세스 내부 데이터통신
    • 하나의 프로세스에 2개 이상의 스레드가 존재하는 경우의 통신
    • 전역변수 / 파일을 이용하여 데이터를 주고 받음
  • 프로세스간 데이터 통신
    • 같은 컴퓨터에 있는 여러 프로세스끼리 통신
    • 공용 파일 / 운영체제가 제공하는 파이프를 활용하여 데이터를 주고 받음
  • 네트워크를 이용한 데이터 통신
    • 여러 컴퓨터가 네트워크로 연결 되어 있을 때 프로세스간 통신 → 네트워킹
    • 소켓을 활용하여 데이터를 주고 받음
    • 원격 프로시저 호출 : 다른 컴퓨터에 있는 함수를 호출해주는 작업

프로세스 통신의 분류

  • 함수 호출도 데이터를 주고 받는데에서 통신으로 볼 수 있으나, 하나의 프로세스 내에서 순차적으로 실행되기 떄문에 통신이라 칭하지 않음
  • 프로세스간 통신 → 동시에 실행되는 프로세스 끼리 데이터를 주고받는 작업을 의미
  • 통신 방향에 따른 분류
    • 데이터가 전송되는 방향에 따라 분류
    • 양방향 통신
      • 데이터를 동시에 양쪽 방향으로 전송 할 수 있는 구조
      • 소켓 통신이 양방향 통신에 해당
    • 반양방향 통신
      • 데이터를 양쪽 방향으로 전송 할 수 있지만, 동시 전송은 불가능 하며 특정 시점에만 한쪽 방향으로 전송 가능한 구조
      • 무전기 통신이 반양방향 통신에 해당
    • 단방향 통신
      • 한쪽 방향으로만 데이터를 전송 할 수 있는 구조
      • 모스 신호 / 전역변수를 하나만 활용한 통신이 단방향 통신에 해당
  • 통신 구현 방식에 따른 분류
    • 전역 변수를 사용한 통신 시, 언제 전역변수가 변경 되었는지 알 수 없어 지속적인 변화 감지가 필요함
    • 무한루프를 돌며 이렇게 지속적으로 변수를 확인 하는 것 → 바쁜 대기 ( Busy waiting ) 으로 지칭
    • 성능 저하의 원인이 될 수 있으므로 지양 해야함
    • 데이터의 변화를 지속적으로 확인하는 것이 아니라, 데이터가 도착 했을 때 변화가 생겼음을 알려주는 작업 → 동기화
    • 동기화 기능의 유무에 따라 구분 가능
    • 동기화 기능을 지원하는 통신 ( 대기가 있는 통신, 동기화 통신 )
      • 데이터를 받는 쪽은 데이터가 도착할 때 까지 대기 상태에 머무름
      • 파이프 / 소켓을 활용한 통신이 이에 해당
    • 동기화 기능을 지원하지 않는 통신 ( 대기가 없는 통신, 비동기화 통신 )
      • 데이터를 받는 쪽은 Busy waiting을 사용하여 데이터가 도착했는지 여부를 지속적으로 직접 확인
      • 전역변수 / 파일을 이용한 통신이 이에 해당

프로세스 간 통신의 종류

  • 통신은 데이터를 주거나 ( Send / write ) / 받는 동작 ( Receive / read )으로 구분됨
  • 동기화 로직에 집중하여 통신을 살펴 보는것이 중요
    1. 전역변수를 이용한 통신
    • 공동으로 관리하는 메모리를 사용하여 데이터를 주고 받는 통신
    • 보내는쪽 : 전역변수에 값을 씀 / 받는 쪽 : 전역변수 값을 읽음
    • 주로 직접적으로 관련이 있는 프로세스 간에 사용
    • 데이터의 동기화가 기능이 존재 하지 않아 busy waiting이 지속됨 → 성능 저하
      1. 파일을 이용한 통신
    • 파일 열기 → 쓰기 / 읽기 → 파일 닫기 로 구성 되어 있음
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
      #include <stdio.h>
      #include <unistd.h>
      #include <fcntl.h>
    
      int main () {
      	int fd;
      	char buf[5];
      	fd = open("com.txt", O_RDWR) // 읽기 / 쓰기 전용으로 파일 open 
      	write(fd, "Test", 5); // 파일에 Test 문자열을 씀 
      	read(fd, buf, 5); // 파일에서 5B를 읽어 변수 buf에 저장
      	close(fd); // 파일 닫기 
      	exit(0);
      }
    
    • fd 변수는, file descriptor ( 파일 기술자 ) 이고, 이 변수를 통해서만 파일 읽기 / 쓰기에 접근 할 수 있음
    • 파일 입출력도 데이터를 가져오고, 저장하는 명령으로 볼 수 있으므로 파일 입출력도 프로세스간 통신에 해당
    • 동기화를 따로 지원하지 않아 직접 구현 해 주어야 함 ( 주로 부모 프로세스에서 wait으로 많이 구현 )
      1. 파이프를 이용한 통신
    • 운영체제가 제공하는 동기화 통신 방식
    • 파일 입출력에서와 마찬가지로 open / close를 활용하여 작업을 시작 / 마무리
    • 파이프 하나당 단방향 통신 → 두개를 사용하면 양방향 통신 가능
    • 운영체제 동기화 지원 → busy wating x
    • 데이터가 없는데, 데이터를 읽으려고 한 경우 프로세스는 대기 상태에 들어가고, 데이터가 쓰여지면 대기상태는 자동적으로 풀려 동기화가 이루어짐
      1. 소켓을 이용한 통신
    • 여러 컴퓨터에 있는 프로세스간 통신 ( 네트워킹 )
    • 원격 프로시저 호출 / 소켓을 활용함
    • 운영체제 동기화 지원 → busy wating x
    • 소켓 하나만 사용해도 양방향 통신 가능

공유 자원과 임계 구역

공유자원의 접근

  • 공유 자원 : 프로세스가 동동으로 이용하는 변수 / 메모리 / 파일 등을 지칭
  • 아래의 경우와 같이 데이터를 읽고 쓰는 순간이 공유 자원의 상태에 영향을 미칠 수 있기 때문에, 문제 발생을 막아야 함

pic1.png

  • 위와 같은 상황을 경쟁 조건( race condition )이 발생했다고 지칭
  • 공유 자원 접근 순서에 따라 실행 결과가 달라 질 수 있음

임계 구역

  • 공유 자원 접근 순서에 따라 실행 결과가 달라지는 프로그램의 영역을 지칭
  • 위 경우에서는, 전역 변수를 사용하는 부분인 예금을 확인하고, 입금을 한 후 예금을 저장하는 부분이 임계 구역
  • 공유 할 수 없는 자원을 건드리는 부분에 해당 됨
    • 믹서기를 사용할 때 순서를 지켜주지 않고 여러명이 동시에 사용한다면 엉망인 결과가 도출 될 것
  • 임계 구역에서는 프로세스들이 동시에 작업하는것이 금지됨
  • 어떤 프로세스가 임계구역에 있다면, 다른 프로세스는 임계구역의 바깥에서 대기를 해야함
  • 해결을 위해 지켜야 할 원칙
    • 상호 배제 ( mutual exclusion )
      • 한 프로세스가 임계 구역에 들어가면, 다른 프로세스는 임계 구역에 들어 갈 수 없다는 원칙
      • 임계구역 내에는 한 번에 하나의 프로세스만 있어야 함
    • 한정 대기 ( bounded waiting )
      • 어떤 프로세스도 무한 대기 상태에 있어선 안된다는 원칙
      • 특정 프로세스가 임계구역에 진입하지 못하면 안됨
    • 진행의 융퉁성 ( progress flexibility )
      • 한 프로세스가 다른 프로세스의 진행을 방해해서는 안된다는 원칙
      • 특정 프로세스의 대기상태가 다른 프로세스의 진입을 막아서는 안됨

임계구역 해결 방법

공통

  • 위 예금 문제 상황을 푼다는 가정 하에 해결 방법 서술 ( 임계 구역 : 예금 더하기 / 저장 부분 )
  • while(true); 명령으로 무한 대기상태에 들어가 다른 프로세스의 진입을 막을 수 있음

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

  • 상호 배제 문제

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
      // 공유 변수
      boolean lock = false;
    
      // Process 1 
      while(lock==true); 
      lock=true;
      // 임계 구역 코드 
      lock=false;
    
      // Proccess 2
      while(lock==true);
      lock=true;
      // 임계 구역 코드 
      lock=false;
    
    • while(lock==true); 다른 프로세스에서 임계 구역에 들어가 있는 상태라면, 무한 대기상태로 들어감
    • 임계구역 코드 작업이 끝나면, lock=false로 변경되어 무한 대기상태( 잠금 상태 )가 풀림
    • 문제점
      • Process 1에서, whilte(lock==true); 를 빠져 나오고, lock=true를 실행 하려는 순간 주어진 CPU 시간이 다 소모되어 ( Timeout ) 준비 상태로 옮겨지는 경우를 상정
      • Context switching이 발생하고, Process 2가 실행 상태로 옮겨짐
      • P2가 임계구역에 진입 가능 ( Process 1에서 lock=true 코드가 작동하지 않았기 때문에 )
      • 두 프로세스 모두 임계구역에 접근 가능
      • 상호 배제 조건을 보장하지 못함
      • busy wating 때문에 성능 저하 가능
  • 한정 대기 문제

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
      // 공유 변수
      boolean lock1 = false;
      boolean lock2 = false;
    
      // Process 1 
      lock1=true;
      while(lock2==true); 
      // 임계 구역 코드 
      lock1=false;
    
      // Proccess 2
      lock2=true;
      while(lock1==true);
      // 임계 구역 코드 
      lock2=false;
    
    • 잠금 설정 뒤 잠금 확인 과정을 수행
    • 잠금을 하고 다른 프로세스가 잠겼는지 확인하므로 상호 배제가 보장됨
    • 문제점
      • Process 1에서, lock1=true 를 실행 후 Timeout 인터럽트 발생 하는 상황을 가정
      • Context switcing 이 발생하고, Process 2가 실행 상태로 옮겨짐
      • Process 2도 lock2=true 를 실행 후 Timeout이 발생
      • Process 1 / Process 2 둘 다 무한루프에 빠져 임계구역에 진입 불가
      • 프로세스가 살아 있으나, 작업이 진행 되지 못하는 상태로 진입 ( dead lock 상태 )
      • 확장성 문제도 존재 → 현재는 프로세스가 두개지만, 프로세스가 많아지면 구현 복잡 및 성능 저하 문제 발생
  • 진행의 융퉁성 문제

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
      // 공유 변수
      int lock=1
    
      // Process 1 
      while(lock==2); 
      // 임계 구역 코드 
      lock=2;
    
      // Proccess 2
      while(lock==1);
      // 임계 구역 코드 
      lock=1;
    
    • 다른 프로세스가 임계구역에 들어 와 있는지 먼저 확인하고, 임계 구역 작업 진행
    • 문제점
      • 상호 배제 / 한정 대기를 보장 하지만, 서로 번갈아 가면서 실행되는 문제점이 있음
      • 두번 연달아 임계구역에 진입이 불가능 → 융퉁성이 없음
      • 경직된 동기화 현상이 발생
  • 하드웨어적인 해결 방법

    1
    2
    3
      while(testandset(&lock)==true);
      // 임계구역
      lock=false;
    
    • testandset → while(lock==true)와 lock=true 명령을 한꺼번에 실행 할 수 있도록 해줌
    • 명령어 실행 중간에 타임아웃이 걸려 임계 구역을 보호하지 못하는 문제가 발생하지 않음
    • 문제점
      • busy waiting이 여전히 존재

피터슨 알고리즘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 공유 변수
boolean lock1 = false;
boolean lock2 = false;
int turn = 1;

// Process 1 
lock1=true;
turn=2;
while(lock2==true && turn==2); 
// 임계 구역 코드 
lock1=false;

// Proccess 2
lock2=true;
turn=1;
while(lock1==true && turn==1);
// 임계 구역 코드 
lock2=false;
  • num을 추가하여, 두 프로세스 모두 임계구역에 못들어가는 상황에 대비
  • 문제점
    • 위 코드에선 2개의 프로세스만 사용 가능
    • 여러개의 프로세스 사용하기 위해선, 공유변수 추가 및 코드 수정 필요

데커 알고리즘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 공유 변수
boolean lock1 = false;
boolean lock2 = false;
int turn = 1;

// Process 1 
lock1=true;
while(lock2==true) {
	if (turn==2) {
		lock1=false;
		while(turn==2);
		lock1=true;
	}
}
// 임계 구역 코드 
turn=2;
lock1=false;

// Process 2
lock2=true;
while(lock1==true) {
	if (turn==1) {
		lock2=false;
		while(turn==1);
		lock2=true;
	}
}
// 임계 구역 코드 
turn=1;
lock2=false;
  1. Process 1에서 Process 1을 lock
  2. Process 2에서 lock을 걸었는지 확인
  3. 걸려 있다면, 차례를 확인 ( turn )
  4. Process 1의 차례라면, 임계 구역 작업 진행 아니라면, process1의 잠금을 풀고 process 2의 작업이 끝날때 까지 대기
  5. process 2의 작업이 끝나면, process 1이 임계구역 작업 시작
    • 문제점
    • 매우 복잡 → 성능 저하 일으킬 수 있으며, 여러개 프로세스가 돌아가는 경우 제어가 힘듦

세마포어

  • lock을 거는 것이 아닌, 임계구역에 진입하기 전에 스위치를 사용 중으로 놓고 임계 구역으로 접근
  • 이후에 도착하는 프로세스는, 앞의 프로세스가 작업을 마칠 때 까지 대기
  • 앞의 프로세스가 작업을 마치면, 다음 프로세스에 임계 구역을 사용하라는 동기화 신호를 보냄
1
2
3
4
5
6
7
8
9
10
// n : 현재 사용 가능한 자원의 수 
Semaphore(n); 
// 잠금을 수행하는 코드, RS가 0보다 크면 1만큼 감소, 아니면 0보다 커질때 까지 대기 
P(); 

// 임계구역 코드

// 잠금 해제와 동기화를 같이 수행하는 코드
// RS 값을 1만큼 증가시키고, 임계구역에 접근해도 좋다는 wake up 신호를 보냄 
V(); 
1
2
3
4
5
6
7
8
// Semaphore(n)
RS=n;
// P();
if RS > 0 then RS = RS - 1;
else block();
// V();
RS = RS + 1;
wake_up();
  • 세마포어에서 잠금이 해제되기를 기다리는 프로세스는, 세마포어 큐에 저장되어 있다가 wake_up 신호를 받으면 큐에서 나와 임계구역에 진입
  • busy waiting이 존재 x
  • P() / V() 가 실행 되는 도중에, 다른 코드가 실행되면 상호 배제와 한정 대기 조건을 보장 하지 못함 → testandset 함수를 활용하여 분리 실행되지 않고 완전시 실행되도록 구현 해야 함
  • 공유 자원이 여러개 일때도 똑같은 구조로 사용 가능
  • 문제점 : 사용자가 P / V 함수를 잘못 사용하면, 임계구역이 보호받지 못할 수 있음
    • P / V 함수를 사용하지 않는 경우 / 둘 중 하나를 사용하지 않는 경우 / 역순 순서로 사용하는 경우 등 ….

모니터

  • 특징
    • 모든 프로세스가 세마포어 알고리즘을 따르도록 구현 한 것
    • 공유 자원을 내부적으로 숨기고, 공유자원에 접근하기 위한 인터페이스만 제공 → 자원 보호 및 프로세스간 동기화
    • 시스템 호출과 같은 개념
  • 작업 순서
    • 프로세스는 P / V를 사용하지 않고 모니터에 작업 요청을 진행
    • 요청받은 작업을 모니터 큐에 저장 한 후 순서대로 처리 및 결과만 프로세스에 알려 줌
    • 위 은행의 예에서는, increase() 인터페이스를 사용자에게 주고, 이를 사용하도록만 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class shared_balance() {
	private int balance = 10;
	private boolean busy = false;
	private condition mon;

	public void increase(int amount) {
		if (busy == true ) {
			mon.wait();
		}
		busy = true;
		balance += amount;
		mon.signal();
	}
}
  • balance / busy 등은 외부에서 접근 불가, increase 함수를 통해서만 변경 가능
  • 따라서 increase(5) / increase(10) 과 같은 형식의 호출로 위에서 나온 문제들 해결 가능
  • p / v는 내부에 구현 되어 있으므로 사용자가 구현 안해도 됨
  • 상태변수로 mon을 사용하는데, 상태변수를 통해 wait() / signal() 함수 호출
  • wait() : 모니터 큐에서 자신의 차례가 올 때까지 대기 ( 세마포어의 P에 해당 )
  • signal() : 모니터 큐에서 기다리는 다음 프로세스에 순서를 넘겨 줌 ( 세마포어의 V에 해당 )