프로세스 동기화(synchronization)
동기화
가 필수임프로세스들 사이의 수행 시기를 맞추는 것 즉, 프로세스 동기화란 크게 아래 두 가지를 일컫는다.
실행 순서 제어
를 위한 동기화상호 배제
를 위한 동기화상호 배제(mutual exclusion)
: 공유가 불가능한 자원의 동시 사용을 피하기 위해 사용하는 알고리즘상호 배제를 위한 동기화와 관련된 고전적이고 유명한 문제로, 생산자와 소비자 문제가 있다.물건을 계속해서 생산하는 프로세스인 생산자
와 물건을 계속해서 소비하는 프로세스인 소비자
로 이루어져 있다.
#include <iostream>
#include <queue>
#include <thread>
void produce();
void consume();
int sum = 0;
int main() {
std::cout << "초기 합계: " << sum << std::endl;
std::thread producer(produce);
std::thread consumer(consume);
producer.join();
consumer.join();
std::cout << "producer, consumer 스레드 실행 이후 합계: " << sum << std::endl;
return 0;
}
void produce() {
for(int i = 0; i < 100000; i++) {
sum++;
}
}
void consume() {
for(int i = 0; i < 100000; i++) {
sum--;
}
}
위 코드를 실행했을 때 결과는 아래와 같다.
초기 합계: 0
producer, consumer 스레드 실행 이후 합계: 63078
초기 합계: 0
producer, consumer 스레드 실행 이후 합계: -13750
매번 실행 결과가 다르게 나오는 것은 생산자 프로세스와 소비자 프로세스가 제대로 동기화되지 않았기 때문이다. 즉, 동시에 접근해서는 안 되는 자원에 동시에 접근했기에 발생한 문제다.
공유 자원(shared resource)
임계 구역(critical section)
레이스 컨디션(race condition)
상호 배제를 위한 동기화
레이스 컨디션
과 같은 상황이 발생하지 않도록 두 개 이상의 프로세스가 임계 구역에 동시에 접근하지 못하도록 관리하는 것상호 배제(mutual exclusion)
: 한 프로세스가 임계 구역에 진입했다면 다른 프로세스는 임계 구역에 들어올 수 없음진행(progress)
: 임계 구역에 어떤 프로세스도 진입하지 않았다면 임계 구역에 진입하고자 하는 프로세스는 들어갈 수 있어야 함유한 대기(bonuded waiting)
: 한 프로세스가 임계 구역에 진입하고 싶다면 그 프로세스는 언젠가는 임계 구역에 들어올 수 있어야 함 (임계 구역에 들어오기 위해 무한정 대기해서는 안 됨)뮤텍스 락(Mutex lock; MUTual Exclusion Lock)
바쁜 대기(busy wait)
: acquire 함수에서 lock 변수를 확인하는 작업을 반복해서 확인 및 대기하는 상태세마포(semaphore)
이진 세마포(binary semaphore)
와 카운팅 세마포(counting semaphore)
가 있지만, 이진 세마포
는 뮤텍스 락과 비슷한 개념이므로 아래 내용은 카운팅 세마포
에 관한 것임바쁜 대기
바쁜 대기
문제 방안모니터(monitor)
조건 변수(condition variable)
레이스 컨디션 (Race Condition)
은 병렬 프로그래밍에서 발생하는 문제로, 여러 프로세스 또는 스레드가 동시에 같은 자원을 수정하려고 할 때 예상치 못한 결과가 발생하는 상황이다. 이로 인해 다음과 같은 문제가 발생할 수 있다.
여러 스레드가 동시에 같은 변수를 수정하는 경우, 변수의 값이 일관성 없이 변경될 수 있다. 예를 들어 은행 계좌 잔액을 동시에 감소시키는 경우, 잔액이 정확하지 않게 갱신될 수 있다.
하나의 스레드가 데이터를 수정하는 동안 다른 스레드가 같은 데이터를 읽으려고 할 때 충돌이 발생합니다. 이로 인해 잘못된 데이터를 읽을 수 있다.
두 개 이상의 스레드가 동시에 같은 데이터를 수정하려고 할 때 충돌이 발생한다. 이로 인해 데이터가 무효화될 수 있다.
두 개 이상의 스레드가 서로의 작업이 끝나기를 기다리며 멈춰있는 상태다. 데드락이 발생하면 프로그램이 더 이상 진행되지 않는다. 교착 상태가 발생할 수 있는 조건은 다음과 같다.
레이스 컨디션의 문제를 해결하고자 상호 배제 조건을 두어 동시에 공유 자원에 접근할 수 없도록 하여 한 자원에 한 스레드만 접근 할 수 있게 하는 것을 의미한다.
스레드가 자원을 독점적으로 사용하고 있어 다른 스레드가 자원에 접근하려고 락을 획득하기 위해 무한 대기할 수 있는 상황이 발생할 수 있다.
공유 자원에 락을 획득하여 점유하고 있는 상태인데 다른 자원의 락을 획득하기 위해 대기하고 있는 상황을 의미한다.
조금 더 이해하기 쉽게 풀어서 말해본다면, 발생할 수 있는 상황이 여러 개의 자원을 동시에 써야하는 경우 락을 획득한 자원에 대한 처리는 끝났는데 남은 자원의 락을 다른 스레드가 가지고 해제를 하지 않아 무한정 대기하는 상태다.
또한 자신이 점유하고 있는 락도 해제를 해주어야 그 락을 획득하기 위해 대기하고 있는 다른 스레드들도 자원에 접근하여 처리를 할 수 있는데 락을 획득할 수 없어 다른 스레드 마저 무한정 대기할 수 밖에 없는 상황이 발생한다.
다른 스레드가 자원을 선점하고 있어서 자원을 뺏어올 방법이 없는 것을 의미한다. 만일 어느 스레드가 공유 자원의 락을 획득하여 선점하고 있다면 그 공유 자원에 접근해야 하는 다른 스레드들은 아무리 잠깐 처리하면 끝나는 상황이라도 락을 빼앗을 방법이 없기 때문에 무한 대기할 상황이 발생할 수 있다.
프로세스가 어느 자원을 점유하고 있고 다른 자원을 요청하여 대기하고 있는데 순환적인 구조를 가지는 경우를 순환성 대기라고 한다.
즉, 점유 상태로 대기
가 순환적으로 발생한 상황으로 볼 수 있다. 락을 해제하고 락을 획득하는게 순차적으로 잘 돌아가면 좋겠지만, 어느 순간 모든 프로세스가 락을 획득하려고 대기하여 모든 프로세스가 무한 대기에 놓인 상황이 발생할 수 있다.
리눅스 환경에서 레이스 컨디션 상태를 이용해 root 권한을 얻어내는 공격 방식이다.
사용자가 프로그램을 실행했을 때 SETUID를 통한 권한 상승으로 관리자 권한의 임시 파일이 생성된다. 공격자는 이 임시 파일의 이름을 파악해놓고, 임시 파일이 생성되면 이 파일의 심볼릭 링크를 생성한다.
다음 프로그램 실행 시 기존의 임시 파일이 삭제되고 재생성되면 공격자는 심볼릭 링크를 이용해 파일의 내용을 변경한다. 이후 시스템은 변경된 파일을 자신이 생성한 임시 파일이라 인식해 프로세스를 진행시키고, 공격자는 관리자 권한으로 실행되는 프로그램에서 자신이 원하는 동작을 실행할 수 있게 된다.
이를 위한 필요조건은 다음과 같다.
1. 공격자는 생성되는 임시 파일의 이름을 사전에 알고 있어야 한다.
2. 파일의 소유자가 root이며 SETUID 비트를 가져야 한다.