Preempt RT 커널에서 실시간 스레드 프로그래밍은 시스템의 실시간 성능을 극대화하는 데 필수적이다. 이 섹션에서는 실시간 스레드를 생성하고 관리하는 방법을 설명한다.

실시간 스레드의 개념

실시간 스레드는 특정 시간 내에 작업을 수행해야 하는 스레드이다. 이러한 스레드는 일반적으로 시스템의 우선순위 스케줄러에 의해 관리되며, 타임 크리티컬 작업을 수행할 수 있도록 설계되어 있다. 실시간 스레드의 주요 목표는 작업이 미리 정해진 기한 내에 완료되도록 하는 것이다.

스레드 생성

Preempt RT에서 실시간 스레드를 생성하는 방법은 일반적인 POSIX 스레드(Pthreads) 생성 방식과 유사하지만, 스레드의 속성을 설정할 때 추가적인 설정이 필요하다. 이를 위해 pthread_attr_t 구조체를 사용하며, 실시간 스케줄링 정책과 우선순위를 설정한다.

#include <pthread.h>
#include <sched.h>

pthread_t thread;
pthread_attr_t attr;
struct sched_param param;

// 스레드 속성 초기화
pthread_attr_init(&attr);

// 스케줄링 정책 설정 (예: SCHED_FIFO)
pthread_attr_setschedpolicy(&attr, SCHED_FIFO);

// 우선순위 설정
param.sched_priority = 80;
pthread_attr_setschedparam(&attr, &param);

// 스레드 생성
pthread_create(&thread, &attr, &thread_function, NULL);

위 코드에서 SCHED_FIFO는 비선점형 스케줄링 정책으로, 우선순위가 높은 스레드가 실행 중인 동안에는 다른 스레드가 개입할 수 없다. 이는 실시간 스레드가 작업을 중단 없이 수행하도록 보장한다.

스레드 우선순위

실시간 스레드의 우선순위는 작업의 중요도에 따라 설정된다. Preempt RT에서는 일반적으로 높은 우선순위를 가진 스레드가 먼저 실행된다. 이때 우선순위 역전 문제를 방지하기 위해 적절한 우선순위 계층을 설계하는 것이 중요하다.

우선순위는 sched_param 구조체의 sched_priority 필드로 설정되며, 우선순위 범위는 시스템에 따라 다를 수 있지만 일반적으로 1에서 99까지의 값을 갖는다. 높은 값일수록 높은 우선순위를 의미한다.

우선순위 결정은 시스템의 실시간 요구사항에 따라 신중하게 이루어져야 한다. 우선순위가 너무 낮으면 기한 내에 작업을 완료하지 못할 수 있으며, 너무 높으면 다른 중요한 작업을 방해할 수 있다.

스레드 관리

실시간 스레드를 효율적으로 관리하기 위해서는 스레드의 수명 주기와 자원 관리를 신경 써야 한다. 스레드의 수명 주기는 일반적으로 다음과 같은 단계를 포함한다:

  1. 생성: 스레드가 생성되고 스케줄러에 등록된다.
  2. 실행: 스레드가 활성화되어 작업을 수행한다.
  3. 대기: 스레드가 작업 완료 후 대기 상태에 들어간다. 대기 상태에서는 CPU 자원을 사용하지 않으며, 특정 이벤트나 신호를 기다린다.
  4. 종료: 스레드가 모든 작업을 완료하고 종료된다. 이때, 사용된 자원은 시스템에 반환된다.

스레드 관리에서 중요한 것은 각 스레드의 우선순위를 고려한 스케줄링과 자원 할당이다. 실시간 시스템에서는 각 스레드가 기한 내에 작업을 완료하도록 관리하는 것이 핵심이다. 이를 위해 적절한 스케줄링 정책과 우선순위 설정, 동기화 메커니즘을 활용하여 스레드 간 간섭을 최소화해야 한다.

스레드의 속성 설정

Preempt RT에서 실시간 스레드를 생성할 때, 스레드의 속성을 세밀하게 설정하는 것이 중요하다. 속성은 pthread_attr_t 구조체를 사용해 정의하며, 주요 속성으로는 스케줄링 정책, 스케줄링 파라미터(우선순위), 스레드 스택 크기 등이 있다.

스케줄링 정책

Preempt RT에서 사용할 수 있는 스케줄링 정책은 다음과 같다:

각 스케줄링 정책은 pthread_attr_setschedpolicy() 함수를 사용해 설정할 수 있다.

pthread_attr_setschedpolicy(&attr, SCHED_RR);

스케줄링 파라미터

스레드의 우선순위는 sched_param 구조체를 통해 설정된다. 이 구조체는 스레드의 우선순위를 정의하는 sched_priority 필드를 포함한다. 우선순위는 해당 시스템에서 사용할 수 있는 범위 내에서 설정되며, 일반적으로 1에서 99까지의 값이 사용된다.

struct sched_param param;
param.sched_priority = 50;
pthread_attr_setschedparam(&attr, &param);

스택 크기 설정

실시간 스레드는 고유의 스택 공간을 필요로 하며, 이 크기는 작업의 복잡성에 따라 달라질 수 있다. 기본 스택 크기는 대부분의 경우 충분하지만, 특수한 경우에는 스택 크기를 수동으로 설정해야 할 수도 있다. 스택 크기는 pthread_attr_setstacksize() 함수를 사용해 설정할 수 있다.

size_t stack_size = 1024 * 1024; // 1MB 스택 크기
pthread_attr_setstacksize(&attr, stack_size);

실시간 스레드의 실행 및 종료

스레드가 생성되면, 해당 스레드는 스케줄러에 의해 자동으로 실행되며, 설정된 우선순위와 스케줄링 정책에 따라 CPU 시간을 할당받는다. 스레드의 실행 주기 동안, 실시간 시스템의 요구사항에 따라 다양한 작업을 수행한다.

스레드가 작업을 완료하면 pthread_exit() 함수를 호출하여 정상적으로 종료한다. 종료된 스레드는 시스템 자원을 해제하고, 스레드를 생성한 쪽에서는 pthread_join()을 통해 스레드가 종료될 때까지 기다릴 수 있다.

void *thread_function(void *arg) {
    // 작업 수행
    pthread_exit(NULL);
}

int main() {
    pthread_t thread;
    pthread_attr_t attr;
    pthread_attr_init(&attr);

    // 스레드 생성
    pthread_create(&thread, &attr, &thread_function, NULL);

    // 스레드 종료 대기
    pthread_join(thread, NULL);

    return 0;
}

실시간 스레드 관리의 고려 사항

실시간 스레드 관리에서 중요한 고려 사항 중 하나는 우선순위 역전(priority inversion) 문제이다. 이는 낮은 우선순위를 가진 스레드가 높은 우선순위를 가진 스레드보다 먼저 실행되어야 하는 상황에서 발생할 수 있다. 우선순위 역전 문제를 해결하기 위해 Preempt RT는 우선순위 상속(priority inheritance)우선순위 천장(priority ceiling) 등의 메커니즘을 제공한다.

스레드 동기화 기법

실시간 스레드 프로그래밍에서는 스레드 간의 동기화가 매우 중요하다. 동기화가 제대로 이루어지지 않으면 우선순위 역전, 데드락, 자원 경쟁 등의 문제가 발생할 수 있다. Preempt RT 환경에서는 이러한 문제를 최소화하기 위해 다양한 동기화 기법을 제공한다.

뮤텍스(Mutex)

뮤텍스는 상호 배제를 보장하는 가장 일반적인 동기화 도구이다. 여러 스레드가 공유 자원에 접근할 때, 뮤텍스를 사용하여 한 번에 하나의 스레드만 자원에 접근하도록 한다. Preempt RT에서는 pthread_mutex_t와 같은 POSIX 뮤텍스가 사용되며, 실시간 환경에 최적화된 설정을 추가로 지원한다.

pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);

void *thread_function(void *arg) {
    pthread_mutex_lock(&mutex);
    // 공유 자원에 대한 작업 수행
    pthread_mutex_unlock(&mutex);
    return NULL;
}

실시간 시스템에서는 pthread_mutexattr_t를 사용하여 뮤텍스의 특성을 설정할 수 있다. 예를 들어, 우선순위 상속을 활성화할 수 있다.

pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT);
pthread_mutex_init(&mutex, &attr);

세마포어(Semaphore)

세마포어는 카운터를 기반으로 한 동기화 도구로, 지정된 수의 스레드만 자원에 접근할 수 있도록 제한한다. 실시간 시스템에서는 바이너리 세마포어(값이 0 또는 1로 제한됨)를 사용하여 이진 동기화 문제를 해결하거나, 카운팅 세마포어를 사용하여 제한된 자원을 관리할 수 있다.

#include <semaphore.h>

sem_t semaphore;
sem_init(&semaphore, 0, 1);

void *thread_function(void *arg) {
    sem_wait(&semaphore);
    // 공유 자원에 대한 작업 수행
    sem_post(&semaphore);
    return NULL;
}

스핀락(Spinlock)

스핀락은 짧은 기간 동안 자원에 대한 독점적인 접근을 보장하기 위해 사용된다. 스핀락은 자원을 사용할 수 있을 때까지 바쁜 대기를 하며, 특히 멀티프로세서 환경에서 짧은 대기 시간을 가진 상황에 적합한다.

#include <pthread.h>

pthread_spinlock_t spinlock;
pthread_spin_init(&spinlock, PTHREAD_PROCESS_PRIVATE);

void *thread_function(void *arg) {
    pthread_spin_lock(&spinlock);
    // 공유 자원에 대한 작업 수행
    pthread_spin_unlock(&spinlock);
    return NULL;
}

스핀락은 대기 상태에서 CPU 자원을 지속적으로 소비하기 때문에, 사용 시에는 매우 신중해야 한다. 실시간 시스템에서 필요 이상으로 자원을 소비하지 않도록 최소한의 대기 시간을 가지도록 설계해야 한다.

스레드 취소(Cancellation)

실시간 시스템에서는 특정 조건에서 실행 중인 스레드를 취소해야 하는 상황이 발생할 수 있다. Preempt RT에서 스레드를 취소하려면 pthread_cancel() 함수를 사용한다. 이 함수는 지정된 스레드에게 취소 요청을 보내며, 스레드는 설정된 취소 유형에 따라 즉시 또는 안전하게 취소된다.

pthread_t thread;

// 스레드 생성 및 실행
pthread_create(&thread, NULL, &thread_function, NULL);

// 스레드 취소 요청
pthread_cancel(thread);

스레드의 취소 상태는 pthread_setcancelstate() 함수를 사용하여 설정할 수 있으며, PTHREAD_CANCEL_ENABLE 또는 PTHREAD_CANCEL_DISABLE로 스레드의 취소 가능 여부를 제어할 수 있다. 또한, pthread_setcanceltype() 함수를 사용하여 취소 요청이 즉시 처리되는지(PTHREAD_CANCEL_ASYNCHRONOUS) 아니면 안전한 지점에서 처리되는지(PTHREAD_CANCEL_DEFERRED)를 설정할 수 있다.

pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);

스레드 취소는 신중하게 다루어야 하며, 특히 실시간 시스템에서는 데이터 일관성과 시스템 안정성을 보장하는 방법으로 구현되어야 한다.

스레드 간 통신

실시간 스레드 간의 통신은 시스템의 성능과 신뢰성에 큰 영향을 미친다. 일반적으로 메시지 큐, 파이프, 공유 메모리, 신호(signal) 등을 사용하여 스레드 간 데이터를 교환한다. 이러한 기법을 적절히 사용하면 실시간 시스템의 요구사항을 충족하는 효과적인 통신 방법을 구축할 수 있다.

이들 기술을 결합하여 실시간 시스템의 특성에 맞는 최적의 스레드 통신 방법을 설계할 수 있다.