본문 바로가기

Information Technology/OS

[운영체제] Process Synchronization(1)

이 글은 개인의 학습을 목적으로 정리한 글입니다. 이점 참고하고 읽어주세요;)


컴퓨터 시스템 안에서 데에터가 접근되는 패턴

1) 데이터가 저장된 위치

2) 저장된 데이터를 읽어와서 연산

3) 연산된 결과를 다시 원래 위치에 저장

 

보통 데이터를 읽기만 한다면, 누가 먼저 읽든 별로 상관이 없음. 하지만 데이터를 가져와서 연산하고 다시 저장하는 프로세스에서는 누가 먼저 데이터를 읽었느냐에 따라 연산의 결과가 변할 수 있음. 이를 해결하기 위한 방법이 Process Synchronization


하나의 저장 공간과 두 개의 연산 장치

여러 (연산)주체가 하나의 데이터에 접근하는 걸 race condition이라고 함. 경쟁 상태라고 보면 될 것 같습니다.

이러한 경쟁 상태에서 하나의 주체가 데이터를 가져와 연산하는 동안 다른 주체도 데이터를 가져가서 연산을 하게 되면 사용자의 의도에서 벗어나는 연산 결과가 반영되는 문제가 발생합니다.

 

연산을 하는 주체는 CPU, 데이터를 저장하는 주체는 메모리입니다. 또는 연산을 하는 건 프로세스이고, 프로세스는 프로세스가 가리키는 주소공간에 저장된 값을 계산합니다. 

 

하나의 CPU만 존재하는 환경에서는 CPU가 한 번의 연산에 메모리에 한 번씩 접근하기 때문에 이런 문제가 발생할 가능성이 적습니다. 반면 CPU가 여러 개인 환경에서 하나의 저장 공간을 공유한다면 이러한 race conditiond이 발생할 수 있습니다. 

하지만 하나의 CPU가 존재하는 환경이라도, 사용자 프로그램에서 커널에 시스템 콜을 요청해서 커널에서 자신이 속한 커널 데이터를 다루던 도중, 또 다른 사용자 프로그램의 시스템 콜이 인터럽트로 들어와서 작업중이던 커널의 데이터를 사용한다면, 단일 CPU 환경에서도 race condition의 문제가 발생할 가능함을 알 수 있습니다. 즉, 유저 레벨에서는 별 문제가 발생하지 않았지만 커널은 여러 데이터들이 동시에 사용할 수 있는 특성 때문에 race condition 문제가 발생할 수 있습니다.


1) 

보통의 고급 언어로 변수의 크기를 증가시키는 연산은 커널에서 3가지의 단계로 나뉩니다.

1. 메모리에 존재하는 count라는 변수의 값을 CPU 내부의 레지스터로 불러 들이고

2. 레지스터 값을 1 증가시킨 뒤

3. 레지스터 값을 다시 메모리의 count 값에 저장

 

문제는 count 변수의 값을 CPU의 레지스터로 이미 불러들인 상태에서 인터럽트가 걸렸을 때 발생합니다. 이때에는 count를 1 증가시키는 작업을 잠시 멈춘 후, interrupt handler를 작동합니다.

그런데 interrupt handler 역시 커널의 코드입니다. 그렇기 때문에 커널에 저장된 count의 데이터를 1 감소합니다. 그렇게 인터럽트 처리가 끝나면 다시 원래의 덧셈 연산으로 돌아오는데, 여기서 중간에 인터럽트로 실행한 count-- 연산의 값은 커널에 저장된 count의 값에 반영되지 않습니다. Count++ 연산에서 이미 레지스터에 변수의 값을 저장한 문맥(context)이 저장되고, 인터럽트로 인한 count-- 연산이 끝난 뒤에는 아까 저장된 그 context부터 다시 연산을 진행하기 때문입니다.

 

이러한 결과는 사용자의 의도에서 명백히 벗어나는 문제이기 때문에, 이를 해결하기 위해 커널 모드로 작동하는 중에는 인터럽트 요청이 들어오더라도 인터럽트를 실행하지 않습니다. 그리고 먼저 하던 일이 종료되고 인터럽트를 실행합니다.

 

2)

이번에는 A와 B라는 두 개의 프로세스에서 race condition이 발생하는 경우입니다. 프로세스 A가 사용자 프로그램을 진행하다 자신이 해결할 수 없는 작업을 시스템 콜을 통해 커널에 넘겨 커널이 작업을 하던 도중 A의 할당 시간이 종료되어 프로세스 B가 작업을 실행합니다. 그런데 프로세스 B 역시 A와 마찬가지로 시스템 콜을 통해 커널에서 count++ 작업을 진행한 후, 다시 프로세스 A에게 CPU 제어권이 돌아와 아까 진행하던 커널 작업을 실행합니다. 그러면 프로세스 A의 커널은 아까 종료되기 직전에 저장했던 문맥(context)의 변수 값을 가지고 오기 때문에 프로세스 B에서 작업한 count++의 값은 반영되지 않습니다.

이러한 문제는 어떤 프로세스가 커널 모드에 있을 때에는 할당된 시간이 끝나더라도 CPU를 빼앗기지 않도록 설정함으로서 해결합니다. 이렇게 되면 할당 시간이 정확하게 지켜지지는 않겠지만, 그럼에도 real-time 시스템이 아니라 할당 시간이 조금 지켜지지 않는다고 해서 엄청난 문제가 발생하지는 않는 시스템이기 때문에 이러한 방법은 race condition 문제를 비교적 쉽게 해결할 수 있는 방법입니다.

 

3)

CPU가 여러 개인 상황에서 발생하는 race condition은 위에서 다룬 두 개의 케이스로는 해결할 수 없습니다.

1)번과 2)번은 CPU가 하나인 상황에서 작업을 하던 도중 CPU가 넘어가면서 발생하는 문제인 반면, 3)번은 근본적으로 작업 주체가 여럿이기 때문에 발생하는 문제이기 때문입니다.

 

다양한 프로세스들이 공유하고 사용하는 것이 커널이기에 이러한 race condition 문제가 발생하는데, 이를 해결하는 방법에도 두 가지 방법이 존재합니다.

 

첫 번째 방법은 커널에 단 하나의 CPU만 접근할 수 있게 하는 방법입니다. 이렇게 하면 CPU가 여럿이더라도 커널에 접근할 수 있는 CPU는 단 하나이기 때문에 대단히 비효율적인 상황이 됩니다.

 

때문에 3번의 문제는 어떤 작업 주체가 연산을 위해 데이터를 가져올 때, 다른 작업 주체는 그 작업이 끝나기 전까지 데이터에 접근할 수 없게 lock을 걸어주는 방식으로 해결합니다. 작업이 끝나기 전까지는 다른 작업 주체가 그 데이터에 접근할 수 없도록 설정하고, 작업이 끝난 뒤에야 그 데이터에 접근할 수 있게 해줍니다. 두 번째 방법은 CPU들이 중복되는 데이터를 다루는 것이 아니라면 여러 개의 CPU들이 모두 커널에 접근할 수 있기 때문에 CPU가 여럿인 상황의 효용을 발휘할 수 있게 해줍니다.


결국, Race condition이란 여러 프로세스들이 동시에 공유 데이터에 접근함으로서 발생하는 문제입니다. 여기서 데이터의 불일치 문제가 발생할 수 있고, 이러한 문제로부터 벗어나 데이터의 일관성을 유지하기 위해서 협력 프로세스 간의 실행 순서를 정해주는 메커니즘이 필요합니다. 


Critical section: 공유데이터에 접근하는 코드

 

P1 프로세스가 critical section에서 작업을 하는 도중 CPU 제어권이 P2에게 넘어가더라도, P2 프로세스는 P1 프로세스가 작업중인 critical section에 접근할 수 없게 해줍니다.

 

다음 포스팅에서는 이러한 cirtical section으로 발생하는 문제들을 해결하는 알고리즘 및 그 방법에 대해 알아보겠습니다.