POSIX 스레드는 유닉스 계열 운영체제에서 실시간 프로그래밍을 구현하는 데 있어 중요한 구성 요소이다. 실시간 시스템에서의 스레드 프로그래밍은 매우 중요한데, 이는 시스템의 응답성과 효율성을 보장하기 위해 반드시 필요하다. Preempt RT 패치가 적용된 리눅스 커널에서는 실시간 요구 사항을 충족할 수 있도록 스레드 관리와 스케줄링이 강화된다. 이 장에서는 POSIX 스레드를 활용한 실시간 프로그래밍의 주요 개념과 기법을 살펴본다.
POSIX 스레드 개요
POSIX(Portable Operating System Interface)는 IEEE(Institute of Electrical and Electronics Engineers)에서 정의한 표준 인터페이스로, 이 표준을 따르는 운영체제에서 스레드 프로그래밍을 수행할 수 있다. POSIX 스레드는 "pthread"라는 라이브러리로 구현되며, 스레드 생성, 종료, 동기화, 스케줄링 등의 기능을 제공한다.
POSIX 스레드는 다음과 같은 특징을 가지고 있다.
- 경량화된 프로세스: 스레드는 경량 프로세스로, 같은 프로세스 내에서 자원을 공유하면서도 독립적인 실행 흐름을 갖는다.
- 공유 메모리: 같은 프로세스의 스레드들은 동일한 메모리 공간을 공유한다. 이를 통해 스레드 간 데이터 교환이 빠르게 이루어진다.
- 병렬 실행: 멀티코어 시스템에서는 여러 스레드가 물리적으로 동시에 실행될 수 있다.
실시간 프로그래밍에서의 POSIX 스레드
실시간 시스템에서는 작업이 시간 제약 내에 완료되어야 하며, 이를 위해 스레드의 스케줄링과 우선순위 관리가 매우 중요하다. 실시간 스레드는 일반적인 스레드와 달리 정해진 시간 안에 반드시 실행되어야 하므로, 엄격한 우선순위 및 스케줄링 정책이 필요하다.
실시간 스레드의 우선순위
POSIX 스레드에서는 스레드의 우선순위를 설정하여 스케줄링에 영향을 미칠 수 있다. 우선순위는 일반적으로 정수 값으로 표현되며, 높은 값일수록 높은 우선순위를 나타낸다.
실시간 스레드는 두 가지 주요 스케줄링 정책에 따라 관리된다:
- SCHED_FIFO (First-In, First-Out): 이 스케줄링 정책에서는 같은 우선순위를 가진 스레드들이 순서대로 실행된다. 먼저 준비된 스레드가 실행되며, 실행이 완료되거나 블록 상태가 되기 전까지는 CPU를 점유한다.
여기서 T_i와 T_j는 각각 스레드를 나타내며, \text{Priority}(T)는 해당 스레드의 우선순위를 의미한다.
- SCHED_RR (Round Robin): 이 정책은 SCHED_FIFO와 유사하지만, 타임 슬라이스(time slice) 개념이 추가된다. 각 스레드는 정해진 시간 동안만 CPU를 점유하며, 이후에는 대기열의 다음 스레드로 넘어간다. 이는 같은 우선순위를 가진 스레드들 간의 공정성을 보장한다.
실시간 스레드 생성
POSIX 스레드를 생성하기 위해서는 pthread_create()
함수를 사용한다. 이 함수는 다음과 같이 정의된다:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg);
thread
: 생성된 스레드의 ID를 저장할 변수.attr
: 스레드 속성을 정의하는 구조체. 실시간 스레드의 경우 우선순위, 스케줄링 정책 등을 설정할 수 있다.start_routine
: 스레드가 실행할 함수 포인터.arg
: 스레드 함수에 전달할 인자.
스레드가 생성되면, 스레드는 독립적으로 지정된 함수를 실행하며, 메인 프로그램과 병렬로 동작한다.
스레드 속성 설정
POSIX 스레드의 속성은 pthread_attr_t
구조체를 통해 설정할 수 있다. 이 구조체는 스레드의 스케줄링 정책, 우선순위, 스택 크기 등을 정의한다. 실시간 프로그래밍에서는 스레드의 우선순위와 스케줄링 정책 설정이 중요한데, 이를 통해 실시간 요구 사항을 충족할 수 있다.
다음은 실시간 스레드를 생성할 때 스레드 속성을 설정하는 방법이다:
- 속성 초기화:
pthread_attr_init()
함수를 사용하여pthread_attr_t
구조체를 초기화한다.
c
pthread_attr_t attr;
pthread_attr_init(&attr);
- 스케줄링 정책 설정:
pthread_attr_setschedpolicy()
함수를 사용하여 스레드의 스케줄링 정책을 설정할 수 있다. 예를 들어, SCHED_FIFO 정책을 설정하려면 다음과 같이 작성한다:
c
pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
- 우선순위 설정: 스레드 우선순위는
sched_param
구조체를 사용하여 설정한다. 이 구조체의sched_priority
필드를 원하는 우선순위로 설정한 후,pthread_attr_setschedparam()
함수를 사용하여 속성에 반영한다.
c
struct sched_param param;
param.sched_priority = 20;
pthread_attr_setschedparam(&attr, ¶m);
- 스레드 생성: 속성 설정이 완료되면,
pthread_create()
함수를 호출하여 스레드를 생성한다.
c
pthread_t thread;
pthread_create(&thread, &attr, start_routine, NULL);
- 속성 해제: 스레드 속성 사용이 끝나면,
pthread_attr_destroy()
함수를 사용하여 속성을 해제한다.
c
pthread_attr_destroy(&attr);
실시간 스레드의 실행 제어
실시간 스레드는 실행 중에 특정한 상황에서 제어가 필요할 수 있다. 예를 들어, 특정 이벤트가 발생할 때까지 대기하거나, 일정 시간이 지난 후 실행을 종료하는 등의 상황에서 스레드를 적절히 제어해야 한다.
스레드 대기 및 동기화
스레드 동기화는 실시간 시스템에서 매우 중요한 부분이다. 이를 위해 POSIX 스레드는 뮤텍스(Mutex), 세마포어(Semaphore), 조건 변수(Condition Variable) 등의 기법을 제공한다. 이들 동기화 기법은 스레드 간의 자원 경쟁을 조정하고, 데이터의 일관성을 유지하는 데 사용된다.
- 뮤텍스(Mutex): 뮤텍스는 상호 배제를 위해 사용되며, 한 번에 하나의 스레드만이 특정 자원에 접근하도록 한다. 뮤텍스는
pthread_mutex_t
타입으로 선언되며, 다음과 같이 초기화하고 사용할 수 있다:
```c pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mutex); // 임계 구역 pthread_mutex_unlock(&mutex); ```
- 세마포어(Semaphore): 세마포어는 일정한 수의 스레드가 자원에 접근할 수 있도록 허용한다. POSIX 세마포어는
sem_t
타입으로 선언되며, 초기화 후sem_wait()
와sem_post()
함수로 제어한다.
```c sem_t sem; sem_init(&sem, 0, 3); // 최대 3개의 스레드 허용
sem_wait(&sem); // 임계 구역 sem_post(&sem); ```
- 조건 변수(Condition Variable): 조건 변수는 특정 조건이 충족될 때까지 스레드가 대기하도록 한다. 조건 변수는
pthread_cond_t
타입으로 선언되며,pthread_cond_wait()
와pthread_cond_signal()
함수를 통해 동작한다.
c
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex);
// 조건 충족 후 실행
pthread_mutex_unlock(&mutex);
이와 같은 동기화 기법을 통해 실시간 시스템에서 스레드 간의 협업을 효과적으로 관리할 수 있다.
우선순위 역전 문제
실시간 시스템에서는 우선순위 역전(priority inversion) 현상이 발생할 수 있다. 이는 낮은 우선순위를 가진 스레드가 높은 우선순위를 가진 스레드보다 먼저 자원을 점유하게 되어, 높은 우선순위 스레드가 실행되지 못하는 상황을 의미한다. 이러한 문제는 실시간 시스템의 성능과 신뢰성에 심각한 영향을 줄 수 있다.
우선순위 역전은 다음과 같은 상황에서 발생할 수 있다:
- 낮은 우선순위 스레드가 자원을 점유: 낮은 우선순위 스레드가 뮤텍스와 같은 자원을 점유하고 있는 동안, 높은 우선순위 스레드가 해당 자원을 필요로 할 때 우선순위 역전이 발생한다.
- 중간 우선순위 스레드의 방해: 낮은 우선순위 스레드가 자원을 점유하고 있을 때, 중간 우선순위 스레드가 CPU를 점유함으로써 높은 우선순위 스레드의 실행이 더욱 지연된다.
우선순위 역전의 해결 방법
우선순위 역전 문제를 해결하기 위해 다음과 같은 기법이 사용될 수 있다:
- 우선순위 상속(priority inheritance): 자원을 점유한 낮은 우선순위 스레드가 해당 자원을 필요로 하는 높은 우선순위 스레드의 우선순위를 임시로 상속받아 실행된다. 자원을 해제한 후, 낮은 우선순위 스레드는 원래의 우선순위로 돌아간다.
여기서 T_L은 낮은 우선순위 스레드, T_H는 높은 우선순위 스레드이다.
- 우선순위 천장(priority ceiling): 자원을 관리하는 뮤텍스에 대해 최대 우선순위를 미리 설정해 두고, 자원을 점유하는 스레드가 이 우선순위를 자동으로 가지게 한다. 이를 통해 우선순위 역전을 방지할 수 있다.
여기서 \text{Ceiling}은 자원의 우선순위 천장을 의미한다.
이와 같은 기법을 통해 실시간 시스템에서의 우선순위 역전 문제를 효과적으로 해결할 수 있다.