본문 바로가기

CS

쉽게 배우는 운영체제 | 공유 자원과 임계구역

공유 자원의 접근

공유 자원(shared resource)은 여러 프로세스가 공동으로 이용하는 변수, 메모리, 파일 등을 말한다. 공유 자원은 공동으로 이용되기 때문에 누가 언제 데이터를 읽거나 쓰느냐에 따라 그 결과가 달라질 수 있다. 따라서 프로세스들의 공유자원 접근 순서를 정하여 예상치 못한 문제가 발생하지 않도록 해야한다. 

 

2개 이상의 프로세스가 공유 자원을 병행적으로 읽거나 쓰는 상황을 경쟁 조건(race condition)이 발생했다고 한다.

 

임계 구역과 관련된 전통적인 문제로 생산자-소비자 문제가 있다. 생산자-소비자 문제에서는 생산자 프로세스와 소비자 프로세스가 독립적으로 작업을 한다. 생산자는 물건을 계속 생산해서 버퍼에 넣고 소비자는 계속 버퍼에서 물건을 가져온다. 또한 버퍼가 비어이는지 가득 찼는지 확인하기 위해 sum이라는 전역 변수를 사용하는데, sum에는 현재 버퍼에 있는 상품의 총수가 저장된다.

 

임계 구역 문제를 해결하는 방법은 다음 세가지 조건을 만족해야한다.

 

- 상호 배제: 한 프로세스가 임계구역에 들어가면 다른 프로세스는 임계구역에 들어갈 수 없다. 이것이 지켜지지 않으면 임계구역을 설정한 의미가 없다. 

- 한정 대기: 어떤 프로세스도 무한 대기를 하지 않아야한다. 즉 특정 프로세스가 임계구역에 진입하지 못하면 안된다.

- 진행의 융통성: 한 프로세스가 다른 프로세스를 방해하지 않음.

 

임계구역 해결 방법

임계 구역 문제를 해결하는 단순한 방법은 잠금을 이용하는 것이다. 예를 들어 임계구역이 화장실이라면 사용자는 화장실 문을 잠그고 사용한 후 나올 때 잠금을 해제한다. 잠금 해제와 동시에 동기화신호를 보내는 것이다. 

 

피터슨 알고리즘

피터슨 알고리즘은 임계구역 문제를 해결하기 위해 게리 피터슨(Gary Peterson)이 제안한 것이다.

 

세마 포어

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

 

세마포어에서 잠금이 해제되기를 기다리는 프로세스는 세마포어 큐에 저장되어 있다가 wake_up 신호를 받으면 큐에서 나와 임계구역에 진입한다. 따라서 바쁜 대기를 하는 프로세스가 없다. 그러나 세마포어의 P()나 V() 내부코드가 실행되는 도중에 다른 코드가 실행되면 상호배제와 한정 대기 조건을 보장하지 못한다. 그러므로 P()와 V()의 내부 코드는 검사와 지정을 사용하여 분리 실행하지 않고 완전히 실행되게 해야 한다.

모니터

공유 자원을 사용할 때 모든 프로세스가 세마포어 알고리즘을 따른다면 굳이 P()와 V()를 사용할 필요 없이 자동으로 처리하면 된다. 이를 실제로 구현한 것이 모니터(monitor)이다. 모니터는 공유 자원을 내부적으로 숨기고 공유 자원에 접근하기 위한 인터페이스만 제공함으로써 자원을 보호하고 프로세스 간에 동기화를 시킨다.

 

모니터는 시스템 호출과 같은 개념이다. 커피머신을 사용하면 직접 만지면 고장 날 가능성이 높아지는 것처럼,  운영체제가 관리하는 자원을 사용자가 마음대로 사용하게 두면 실수나 악의적인 의도로 시스템 자원을 망가뜨릴 수 있다.  이러한 문제를 예방하기 위해 운영체제는 시스템 자원을 사용자로부터 숨기고 사용자의 요구 사항을 처리할 수 있는 인터페이스만 제공하는데, 이를 시스템 호출이라고 한다.

 

시스템 호출과 같은 방법으로 모니터도 보호할 자원을 임계구역으로 숨기고 임계구역에서 작업할 수 있는 인터페이스만 제공하여 자원을 보호한다. 

 

1. 임계 구역으로 지정된 변수나 자원에 접근하고자 하는 프로세스는 직접  P()나 V()를 사용하지 않고 모니터에 작업 요청을 한다.

2. 모니터는 요청받은 작업을 모니터 큐에 저장한 후 순서대로 처리하고 그 결과만 해당 프로세스에 알려준다.

 

모니터를 사용하면 세마포어의 P()와 V()를 사용할 필요가 없다. 임계 구역의 보호나 프로세스의 동기화가 모니터 내부에서 처리된다. 모니터는 임계 구역 보호와 동기화를 위해 내부적으로 상태 변수(condition variable)를 사용한다. 상태 변수에는 wait()과 signal()이 있다.

 

- wait(): 모니터 큐에서 자신의 차례가 올 때까지 기다린다. 세마포어의 P()에 해당한다.

- signal(): 모니터 큐에서 기다리는 다음 프로세스에 순서를 넘겨준다. 세마포어의 V()에 해당한다.

 

불필요한 정보를 숨기고 공유 자원에 대한 인터페이스만 제공하는 모니터는 오늘날의 객체 지향 언어와 매우 닮았다.

 

네트워킹

여러 컴퓨터에 있는 프로세스에 데이터를 전달하는 방법중 가장 대중화된 방법은 소켓을 이용하는 방법이다. 소켓을 이용한 네트워킹에서도 open(), read()/write(), close() 구조를 사용한다.

 

소켓은 파이프와 달리 양방향 통신을 지원하고 동기화도 지원한다. 클라이언트 쪽의 통신 절차를 살펴보자. 클라이언트는 소켓을 생성한 후 connect() 작업을 사용하여 서버와의 접속을 시도한다. 서버와 접속되면 read() 혹은 write() 작업을 하며, 작업이 끝나면 사용한 소켓 기술자(socket descriptor)를 닫고 종료한다. 클라이언트의 통신 절차는 파일이나 파이프를 이용한 통신 절차에 비해 특별히 추가되는 것이 없다. 서버쪽의 통신 절차는 클라이언트 쪽 통신 절차보다 좀 더 복잡하다. 서버는 소켓을 생성한 후 bind() 를 사용하여 생성한 소켓을 특정 포트에 등록한다.

 

여러 컴퓨터가 연결된 네트워크 환경에서는 각 컴퓨터를 IP 주소로 구분한다. 그런데 한 컴퓨터 내에도 여러 프로세스가 존재하기 때문에 어떤 프로세스와 통신할지 구분해야한다. 이때 사용하는 구분 번호를 포트 번호(port number)이라고 한다. IP 주소가 아파트의 동 번호라면 포트 번호는 호수에 해당한다.

 

하나의 포트 번호에 소켓이 하나만 생성되는 것은 아니다. 소켓을 하나만 생성할 수 있다면 단 한사람에게 서비스할 수 있을 것이다. 서버는 동시에 여러 클라이언트에 서비스를 하기 위해 하나의 포트 번호에 여러개의 소켓을 생성한다. 따라서 bind()는 특정 포트에 새로운 소켓을 등록하겠다는 의미이다. 

 

bind()로 소켓이 정상적으로 등록되면 listen()을 실행하여 클라이언트를 받을 준비를 한다. accept()는 클라이언트의 connect(), 즉 연결 요청을 기다리다가 여러명의 클라이언트가 동시에 connect() 하는 경우 그 중 하나를 골라 작업을 시작하게 해준다. 따라서 클라이언트가 accept() 되면 소켓 기술자가 생성되고 작업이 시작된다. read() 혹은 write() 작업을 마치면 생성된 소켓 기술자를 닫고 다음 클라이언트를 기다린다.