운영체제(5) : 프로세스 동기화

김두현·2024년 11월 14일
1
post-thumbnail

📍목차


  1. 프로세스 간 통신
  2. 공유 자원과 임계구역
  3. 임계구역 문제 해결 방법

1️⃣ 프로세스 간 통신


프로세스 혹은 스레드는 독립적으로 실행되며, 프로세스 간/스레드 간의 협업을 위해선 서로 데이터를 주고받아야 한다.
스레드는 동일 프로세스 안에서 자원을 공유하는 반면, 서로 다른 프로세스는 사용하는 메모리 영역이 달라 데이터를 주고받기 쉽지 않다.

운영체제는 프로세스끼리 쉽게 데이터를 주고받을 수 있는 통신 방법을 제공하는데,
이를 프로세스 간 통신(IPC : Inter Process Communication)이라고 한다.

✔️ IPC 종류

대표적인 IPC 종류는 아래와 같다.

1. 공유 메모리/공유 파일을 이용한 통신

일정한 메모리 영역이나 파일을 공유하고, 이를 통해 데이터를 주고받는다.
데이터를 주고받는 방법을 프로세스끼리 정해야 한다.

단방향 통신이며, 양방향 통신을 위해선 공유 메모리 혹은 공유 파일을 2개 만들어야 한다.
공유 메모리가 하나일 경우 프로세스 A와 B가 통신하는 상황에서,
A가 공유 메모리에 데이터를 쓴 이후 B도 공유 메모리에 데이터를 쓰면 이전 데이터가 사라지기 때문이다.

이때 파일을 이용한 통신의 경우,
open()을 통해 권한을 얻고, read() / write()를 통해 데이터 입출력을 마치면 close()로 파일을 닫는다.

2. 파이프를 이용한 통신

하나의 컴퓨터 내에서 많이 사용되는 IPC 방식으로, fork()를 통해 만들어진 부모 - 자식 프로세스 간 통신에서 사용된다.

단방향 통신이며, 마찬가지로 양방향 통신을 위해선 파이프를 2개 만들어야 한다.

파일과 마찬가지로 open(), read() / write(), close() 함수를 통해 통신을 진행한다.

3. 소켓을 이용한 통신

컴퓨터와 컴퓨터가 네트워크로 연결된 경우에 사용하는 IPC 방식이다.
파이프에 비해 구현 방식이 복잡하고 자원 소비가 크기 때문에, 동일 컴퓨터 내에서 사용하는 것은 비효율적이다.

양방향 통신으로, 하나의 포트에 여러 소켓을 생성해 여러 컴퓨터와 통신하는 구조가 가능하다.
위 문장을 이해하기 위해 포트 번호소켓의 개념을 알아야하는데, 간단하게 정리해보자.

  • 포트 번호
    인터넷을 사용하려면 TCP/IP를 사용하는데, 인터넷에서 IP의 역할은 목적지까지 데이터를 전송하는 것이다.
    그러나 데이터는 특정 프로세스로 전달되어야 하고, IP만으로는 목적지 컴퓨터만 찾을 수 있을 뿐 컴퓨터 내의 어떤 프로세스에 전달되어야 하는지 식별할 수 없다.

    따라서 하나의 컴퓨터 내에서 각 프로세스를 구분할 주소가 필요한데, 이것이 포트 번호이다.

  • 소켓
    많은 사람들이 이용하는 구글 웹 페이지를 떠올려보자.
    구글 프로세스를 제공하는 포트 번호를 한 명의 클라이언트가 장악한다면, 나머지는 모두 대기해야 한다.

    따라서 하나의 포트에 여러 개의 소켓을 연결하여 클라이언트에게 서비스를 제공한다.

✔️ 동기화

바쁜 대기

공유 메모리/공유 파일을 사용한 통신 방식의 가장 큰 문제점은, 상대방이 데이터를 언제 보낼지 받는 쪽에서 모른다는 것이다.
따라서 받는 쪽에서 반복적으로 공유 메모리/공유 파일을 점검해야만 하고, 무한 반복문을 통해 구현되는데 이를 바쁜 대기라고 한다.

이러한 바쁜 대기 문제를 해결하기 위해, 데이터가 도착했음을 알려주는 것을 동기화라고 한다.

IPC는 이러한 동기화 기능의 유무에 따라 동기화가 있는 "대기가 있는 통신", 동기화가 없는 "대기가 없는 통신"으로 나뉜다.
즉, 대기가 있는 통신은 데이터를 받는 쪽이 데이터가 도착할 때까지 대기하며,
대기가 없는 통신은 데이터를 받는 쪽이 직접 도착 여부를 확인한다.

위의 3가지 통신 방식을 분류하면 공유 메모리/공유 파일을 이용한 통신은 대기가 없는 통신이고,
파이프를 이용한 통신과 소켓을 이용한 통신은 대기가 있는 통신이다.

2️⃣ 공유 자원과 임계구역


프로세스는 공유된 자원을 이용해 공동으로 작업할 수도 있다.
이러한 공유 자원은 누가 언제 입출력하느냐에 따라 결과가 달라질 수 있는데,
아래는 공유 자원 접근 순서에 따른 문제가 발생한 상황에 대한 예시이다.

✔️ 경쟁 조건

두 프로세스가 동일한 예금에 대해 입금을 하는데,
프로세스 P1은 10만원이 있는 것을 확인하여 10만원을 입금하고, P2도 10만원이 있는 것을 확인하고 5만원을 입금한다.
그러나 두 프로세스가 서로 간의 작업을 무시하고 덮어쓰는 바람에 25만원이어야 할 결과값이 20만원이 되어 문제가 발생했다.

이처럼 2개 이상의 프로세스가 공유 자원을 동시에 읽거나 쓰는 상황경쟁 조건이라고 한다.

✔️ 임계구역

위 예시처럼,

공유 자원 접근 순서에 따라 실행 결과가 달라지는 프로그램의 영역을 임계구역이라고 한다.

쉬운 예시를 들자면, 믹서기에 수프를 만들기 위한 소고기와 주스를 만들기 위한 오렌지를 같이 넣을 수 없는 것과 같다.

즉, 한 프로세스가 임계구역에 들어가면 다른 프로세스는 임계구역 밖에서 기다려야 한다.

생산자 - 소비자 문제

임계구역과 관련된 전통적인 문제로, 공유 자원의 수를 증가시키는 생산자와 자원을 소비하는 소비자가 동시에 실행되어 문제가 발생하는 것을 이야기한다.

sum = 3이라는 공유 자원이 있을 때, 생산자의 sum = sum + 1과 소비자의 sum = sum - 1의 미세한 시간 차에 의해 sum = 4 혹은 sum = 2가 되는 상황이다.

✔️ 임계구역 문제 해결 조건

3️⃣에서 살펴볼 임계구역 해결 방법에서 만족해야 할 3가지 조건의 개념에 대해 알아보자.

상호 배제 : Mutual Exclusion

상호 배제란, 한 프로세스가 임계구역에 들어가면 다른 프로세스는 임계구역에 들어갈 수 없음을 의미한다.

중요한 개념으로, 자주 언급되니 꼭 기억하자.

한정 대기 : Bounded Waiting

한정 대기란, 다른 프로세스가 임계구역에서 나오는 것을 무한히 대기하면 안 된다는 것을 의미한다.

진행의 융통성 : Progress Flexibility

진행의 융통성이란, 한 프로세스가 다른 프로세스의 진행을 방해해서는 안 된다는 것을 의미한다.

쉬운 예로, 두 프로세스 A와 B가 임계구역을 번갈아 사용하기로 약속했다고 하자.
그러나 B는 임계구역을 사용할 필요가 없음에도, B가 임계구역을 사용하지 않아 A가 B의 임계구역 사용을 기다리는 상황을 들 수 있다.

3️⃣ 임계구역 문제 해결 방법


임계구역 문제를 해결하기 위해 운영체제는 잠금(Lock)을 이용한다.
화장실에 들어가면 문을 잠궈 타인에게 사용 중임을 알리듯, 프로세스가 임계구역에 들어가면 잠금을 통해 다른 프로세스의 임계구역 진입을 막는 것이다.

위에서 살펴본 상호 배제, 한정 대기, 진행의 융통성을 만족하는 잠금, 잠금 해제, 동기화 구현 방법에 대해 알아보자.

상호 배제 조건을 충족하지 못하는 경우

우선 boolean lock = false라는 전역 변수로 잠금 상태를 지정하자. false는 잠금 해제를 의미한다.

프로세스 P1과 P2는 잠금이 해제될 때까지 대기한다. while(lock == true)
임계구역에 있는 프로세스가 작업을 마치면 잠금을 해제하고, lock = false
다른 프로세스는 임계구역에 진입하며 잠금을 설정한다. lock = true

이때, 임계구역에 있던 프로세스가 나오며 잠금을 해제하는 부분이 다른 프로세스에게 임계구역을 사용해도 좋다고 알리는 동기화 신호다.

그러나, 위 코드에는 문제가 있다. 아래와 같이 while문을 통과한 이후에 타임 슬라이스가 끝나 준비 상태로 옮겨지는 상황을 떠올려보자.

  1. 프로세스 P1이 while문을 통과한다.
    그러나 lock = true를 실행하기 전에 타임아웃으로 인해 프로세스 P2가 실행된다.

  2. 프로세스 P1이 잠금을 걸지 않아 프로세스 P2는 while문을 통과하게 되고, 임계구역에도 진입할 수 있게된다.

  3. 프로세스 P1이 임계구역에 진입하고, lock = true를 실행한다.

  4. 프로세스 P2도 lock = true를 실행해 임계구역에 진입한다.

이와 같이 while문을 통과함과 동시에 lock = true를 설정해야 다른 프로세스의 임계구역 진입을 막을 수 있으나, 그렇지 못해 상호 배제 조건을 충족하지 못한다.

추가로 while(lock == true)를 통해 잠금이 풀렸는지 계속 확인하게 되는데,
이는 바쁜 대기가 실행되고 있는 것이다.

한정 대기 조건을 충족하지 못하는 경우

이번엔 lock 변수를 두 개 선언하여 구현한 방식이다.

이 방식에서 프로세스 P1은 임계구역에 진입하기 전 잠금을 설정하고, lock1 = true
프로세스 P2가 잠금을 설정했는지 확인한다. while(lock2 == true)
잠금이 해제되어 있다면 임계구역에서 작업을 마친 후 잠금을 해제한다. lock1 = false

이 방식은 잠금을 설정한 후에 다른 프로세스도 잠금을 설정했는지 확인하기 때문에 상호 배제가 보장된다.

그러나 무한 대기 현상이 발생할 수 있는데, 아래 예시를 살펴보자.

프로세스 P1도 잠금을 설정한 후 준비 상태로 옮겨지고, 프로세스 P2도 잠금을 설정한 후 준비 상태로 옮겨진 상황이다.
모든 프로세스가 잠금을 설정하여 while문을 통과하지 못하게되고, 무한 루프에 빠져 임계구역에 진입하지 못한다.

이와 같이 한정 대기 조건을 보장하지 못하는 상황을 교착 상태라고 한다.

교착 상태는 프로세스가 살아 있으나 작업이 진행되지 못하는 상태다.
교착 상태에 대해서는 다음 장에서 자세히 알아보자.

추가로, 이 방식은 프로세스가 늘어날 때마다 lock의 개수를 늘려야하는 확장성 문제도 있다.

진행의 융통성 조건을 충족하지 못하는 경우

lock 변수를 boolean 타입이 아닌 특정 프로세스를 가리키도록 구현한 방식이다.

lock의 값을 통해 다른 프로세스가 임계구역에 있는지 확인하고, 없으면 임계구역에 진입한다.
이 방식은 잠금을 확인하는 문장이 하나이므로 상호 배제와 한정 대기를 보장하지만,
프로세스가 번갈아 실행되어 하나의 프로세스가 연달아 실행될 수 없는 구조이다.

이와 같이 프로세스의 진행이 다른 프로세스에 의해 방해받는 현상을 경직된 동기화라고 한다.
이 방식은 진행의 융통성 조건을 보장하지 못한다.

하드웨어로 해결하는 방법

앞서 살펴본 코드는 알고리즘을 통한 소프트웨어 방법이었으나, 하드웨어 방법으로도 해결할 수 있다.

잠금이 걸렸는지 검사하는 코드while(lock == true)와 검사 후 잠금을 설정하는 부분lock = true가 분리되어 타임아웃으로 인한 임계구역 동시 접근하는 것이 반복되는 문제점이었다.

이 경우 하드웨어로 두 명령어를 동시에 실행할 수 있고, 이를 통해 명령어 시행 중간에 타임아웃이 걸려 임계구역을 보호하지 못하는 문제를 방지할 수 있다.

그러나 여전히 바쁜 대기를 사용하여 자원낭비가 발생하기 때문에, 지금까지 소개한 알고리즘은 임계구역 문제 해결 조건을 완전히 충족하지 못했다.

피터슨 알고리즘, 데커 알고리즘

turn 공유 변수를 도입하여 임계구역 문제를 해결하는 피터슨 알고리즘데커 알고리즘이 등장했다.

두 방식은 임계구역 문제 해결 조건을 모두 충족하지만, 프로세스 수의 증가에 따라 변수 또한 증가하며 알고리즘이 매우 복잡해 현재는 잘 사용되지 않는다.

✔️ 세마포어

지금까지 살펴본 알고리즘의 단점을 해결하기 위해 제안된 알고리즘으로 세마포어가 등장했다.
세마포어에서는 프로세스가 임계구역에 진입하기 전에 사용 중임을 표시하고, 작업을 마친 후 다음 프로세스에게 임계구역을 사용하라는 동기화 신호를 보낸다.
이 방식에서 프로세스는 임계구역이 잠겼는지 직접 점검하거나, 바쁜 대기를 하거나, 다른 프로세스에 동기화 메시지를 보낼 필요가 없다.

세마포어 내부 코드에 대해 알아보자.

  • Semaphore(n) : 초기화 작업으로, 전역 변수 RS를 nn으로 초기화한다.
    여기서 nn사용 가능한 공유 자원의 수이다.

  • P() : 잠금을 수행하는 코드이다.
    RS가 0보다 크면 RS를 감소시키고, RS가 0 이하라면 기다린다.
    block()wake_up()이 신호를 보낼 때까지 기다리는 함수다.

  • V() : 잠금 해제와 동기화를 수행한다.
    RS 값을 증가시키고, wake_up()를 실행해 다른 프로세스에게 임계구역에 진입해도 좋다는 동기화 신호를 보낸다.

세마포어에서 잠금이 해제되기를 기다리는 프로세스는 세마포어 큐에 저장된다.
wake_up() 신호를 받으면 큐에서 나와 임계구역에 진입하기 때문에 바쁜 대기가 없다.

또한 P()V() 내부 코드 실행 도중에 타임아웃이 발생하지 않도록 P()와 V()의 내부 코드는 검사와 지정을 사용하여 완전히 실행되도록 구현한다.

세마포어 문제점

세마포어 알고리즘은 단순하고 편하지만, 문제점이 존재한다.
세마포어를 잘못 사용하는 경우인데, 사용자가 고의 혹은 실수로 세마포어를 제대로 사용하지 않은 경우이다.

  1. 프로세스가 세마포어를 사용하지 않아 임계구역을 보호할 수 없다.
  2. P()를 2번 사용하여 wake_up() 신호가 발생하지 않는다.
  3. P()V()를 반대로 사용하여 상호 배제 조건이 충족되지 않는다.

✔️ 모니터

모든 프로세스가 공유 자원을 사용할 때 세마포어 알고리즘을 따르도록 P()V()를 자동으로 처리하면 된다.
이를 구현한 것이 모니터다.

모니터는 공유 자원을 내부적으로 숨기고, 공유 자원에 접근하기 위한 인터페이스만 제공함으로써 자원을 보호하고 프로세스 간에 동기화한다. 이는 시스템 호출과 같은 개념이다.

  1. 공유 자원에 접근하고자 하는 프로세스는 직접 P()V()를 사용하지 않고 모니터에 작업 요청을 한다.
  2. 모니터는 요청받은 작업을 모니터 큐에 저장하여 순서대로 처리하고, 결과만 해당 프로세스에게 알려준다.

아래는 모니터 사용법을 설명하기 위한 Java 예시 코드이다.

공유 자원인 balance, 잠금을 나타내는 busy, 잠금 해제 역할의 mon 변수는 private으로 선언한다.
이 변수들은 public으로 선언된 increase()를 통해서만 변경할 수 있다.

즉, 입금하려는 프로세스는 increase()만 사용하여 예금 데이터에 접근할 수 있고,
increase() 내부에서 임계구역이 잠겼는지 확인하고 다른 프로세스가 사용하지 않으면 잠금을 건 후 예금액을 증가시킨다.
이를 통해 세마포어의 P()V()를 사용할 필요없이 모니터 내부에서 임계구역을 보호할 수 있다.

👏 마무리


IPC 방식과 임계구역을 보호하기 위한 알고리즘에 대해 자세히 알아보았다.
다음 포스팅에서는 본 포스팅에서 언급한 교착 상태의 필요조건과 해결 방법에 대해 알아보자.


참고 자료

쉽게 배우는 운영체제


💕오류 지적 및 피드백은 언제든 환영입니다. 복제시 출처 남겨주세요!💕
💕좋아요와 댓글은 큰 힘이 됩니다.💕
profile
I AM WHO I AM

0개의 댓글

관련 채용 정보