Real-Time Linux (PREEMPT_RT)
PREEMPT_RT는 Linux 커널을 낮은 지터의 실시간 시스템으로 운용하기 위한 핵심 기반입니다. 이 문서는 스핀락의 rtmutex 전환, IRQ 스레딩, 우선순위 상속, SCHED_FIFO/RR/DEADLINE 운용, 고해상도 타이머와 주기 태스크 설계, cyclictest/ftrace 계측, CPU 격리와 운영 안정성 점검 포인트까지 상세히 설명합니다.
핵심 요약
- Threaded IRQ — 인터럽트를 스레드화해 우선순위 제어 가능
- RT Mutex — priority inheritance로 inversion 완화
- Preemptible kernel — 비선점 구간 최소화
- Latency 측정 — cyclictest/ftrace 기반 검증 필수
- CPU isolation — 실시간 태스크 전용 코어 분리
단계별 이해
- 기준 측정
기존 커널에서 레이턴시 baseline을 먼저 측정합니다. - RT 커널 구성
PREEMPT_RT설정과 IRQ 스레딩을 활성화합니다. - 정책 튜닝
스케줄링 클래스와 CPU 격리, 메모리 잠금 정책을 조정합니다. - 회귀 검증
stress + trace 기반으로 worst-case latency를 반복 확인합니다.
개요
Real-Time Linux는 시간 제약이 있는 작업(산업 제어, 로봇, 오디오/비디오, 통신)을 위해 결정론적 응답 시간을 제공합니다.
실시간 시스템 특성
| 구분 | 설명 |
|---|---|
| Hard Real-Time | Deadline 위반 시 시스템 실패 (예: ABS 브레이크, 의료기기) |
| Soft Real-Time | Deadline 위반 시 성능 저하 (예: 비디오 스트리밍, VoIP) |
| Firm Real-Time | Deadline 위반 결과는 무용지물 (예: 주식 거래) |
Preemption 모드
| 모드 | 설명 | 레이턴시 |
|---|---|---|
PREEMPT_NONE |
서버/배치 워크로드. 명시적 preemption point만 | ~수 ms |
PREEMPT_VOLUNTARY |
데스크탑. 추가 preemption point | ~수백 μs |
PREEMPT |
저지연. 커널 대부분이 선점 가능 | ~수십 μs |
PREEMPT_RT |
Hard Real-Time. 인터럽트도 선점 가능 | ~수 μs |
PREEMPT_RT 패치셋
주요 기능
- Interrupt Threading — 하드웨어 인터럽트를 커널 스레드로 처리
- Priority Inheritance — spinlock을 rt_mutex로 대체하여 우선순위 역전 방지
- High-Resolution Timers — 나노초 정밀도 타이머
- Threaded IRQs — 인터럽트 핸들러가 스케줄 가능
- Fully Preemptible Kernel — 커널 전체가 선점 가능
PREEMPT_RT 설치
# 1. RT 패치 다운로드
$ wget https://cdn.kernel.org/pub/linux/kernel/projects/rt/6.6/patch-6.6-rt15.patch.xz
$ wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.tar.xz
# 2. 커널 소스 압축 해제 및 패치 적용
$ tar xf linux-6.6.tar.xz
$ cd linux-6.6
$ xzcat ../patch-6.6-rt15.patch.xz | patch -p1
# 3. 커널 설정
$ make menuconfig
# General setup → Preemption Model → Fully Preemptible Kernel (Real-Time)
# 4. 빌드 및 설치
$ make -j$(nproc)
$ sudo make modules_install install
Interrupt Threading
PREEMPT_RT는 하드웨어 인터럽트를 커널 스레드로 처리하여 우선순위 기반 스케줄링을 가능하게 합니다.
Threaded IRQ 구조
/* 일반 커널 */
Hardware IRQ → hardirq → softirq → process
/* PREEMPT_RT 커널 */
Hardware IRQ → 최소 hardirq → IRQ thread (스케줄 가능) → process
IRQ 스레드 확인
# IRQ 스레드 목록 (PREEMPT_RT에서)
$ ps aux | grep irq
root 3 0.0 0.0 0 0 ? S Jan01 0:00 [irq/9-acpi]
root 12 0.0 0.0 0 0 ? S Jan01 0:01 [irq/16-ehci_hcd]
root 14 0.0 0.0 0 0 ? S Jan01 0:05 [irq/19-eth0]
# IRQ 스레드 우선순위 설정
$ sudo chrt -f -p 90 $(pgrep -f "irq/19-eth0")
request_threaded_irq() API
#include <linux/interrupt.h>
/* Threaded IRQ 등록 */
int request_threaded_irq(
unsigned int irq,
irq_handler_t handler, /* hardirq handler (빠른 처리) */
irq_handler_t thread_fn, /* thread handler (스케줄 가능) */
unsigned long irqflags,
const char *devname,
void *dev_id
);
/* 예시: 네트워크 드라이버 */
static irqreturn_t eth_irq_handler(int irq, void *dev_id)
{
/* 최소한의 처리: IRQ 승인, 레지스터 읽기 */
u32 status = read_reg(STATUS_REG);
if (!(status & IRQ_PENDING))
return IRQ_NONE;
/* IRQ 승인 */
write_reg(STATUS_REG, status);
return IRQ_WAKE_THREAD; /* thread_fn 호출 요청 */
}
static irqreturn_t eth_irq_thread(int irq, void *dev_id)
{
/* 시간이 걸리는 처리: 패킷 수신, 메모리 할당 등 */
process_rx_packets();
return IRQ_HANDLED;
}
/* 등록 */
request_threaded_irq(dev->irq, eth_irq_handler, eth_irq_thread,
IRQF_SHARED, "eth0", dev);
RT Mutex (Priority Inheritance)
PREEMPT_RT는 spinlock을 rt_mutex로 대체하여 우선순위 역전(Priority Inversion)을 방지합니다.
Priority Inversion 문제
/* 시나리오: High(H), Medium(M), Low(L) 우선순위 태스크 */
1. L이 spinlock 획득
2. H가 같은 spinlock 대기 → L 완료까지 블록
3. M이 실행 가능 → M이 L을 선점
4. 결과: H가 M이 끝날 때까지 대기 (우선순위 역전!)
/* Priority Inheritance 해결책 */
1. L이 rt_mutex 획득
2. H가 같은 rt_mutex 대기
3. L의 우선순위가 일시적으로 H 수준으로 상승
4. L이 M을 선점하여 빠르게 완료
5. H가 mutex 획득, L은 원래 우선순위로 복귀
rt_mutex API
#include <linux/rtmutex.h>
struct rt_mutex {
raw_spinlock_t wait_lock;
struct rb_root_cached waiters; /* 우선순위 정렬 대기 큐 */
struct task_struct *owner;
};
/* rt_mutex 초기화 */
struct rt_mutex lock;
rt_mutex_init(&lock);
/* 획득/해제 */
rt_mutex_lock(&lock);
/* critical section */
rt_mutex_unlock(&lock);
/* trylock */
if (rt_mutex_trylock(&lock)) {
/* 성공 */
rt_mutex_unlock(&lock);
}
RT Mutex vs Spinlock 비교
| 특성 | Spinlock | RT Mutex |
|---|---|---|
| 대기 방식 | Busy-wait (CPU 점유) | Sleep (스케줄 아웃) |
| Context | Atomic (인터럽트 가능) | Non-atomic (sleep 가능) |
| Priority Inversion | 발생 가능 ❌ | Priority Inheritance로 방지 ✅ |
| Preemption | 비활성화 (spinlock_t) 또는 활성화 (raw_spinlock_t) | 활성화 (선점 가능) |
| 사용 시기 | 매우 짧은 임계 구간 (<수십 μs) | 긴 임계 구간, 실시간 요구사항 |
| 오버헤드 | 낮음 (단순 busy-wait) | 높음 (스케줄링 오버헤드) |
| PREEMPT_RT | 대부분 rt_mutex로 대체됨 | 기본 락 메커니즘 |
spinlock_t는 PREEMPT_RT에서 자동으로 rt_mutex로 대체됩니다.
진짜 spinlock이 필요한 경우(예: 인터럽트 핸들러)에만 raw_spinlock_t를 사용하세요.
실시간 스케줄링
실시간 스케줄링 정책
| 정책 | 설명 | 우선순위 |
|---|---|---|
SCHED_FIFO |
First-In First-Out. 같은 우선순위 내 순서대로 | 1~99 (99 최고) |
SCHED_RR |
Round-Robin. FIFO + Time Slice | 1~99 |
SCHED_DEADLINE |
EDF (Earliest Deadline First). CBS 기반 | 동적 |
SCHED_FIFO 예시
#include <sched.h>
#include <pthread.h>
void set_realtime_priority(int priority)
{
struct sched_param param;
param.sched_priority = priority; /* 1~99 */
if (sched_setscheduler(0, SCHED_FIFO, ¶m) == -1) {
perror("sched_setscheduler");
}
}
/* pthread 사용 */
pthread_t thread;
struct sched_param param = { .sched_priority = 80 };
pthread_setschedparam(thread, SCHED_FIFO, ¶m);
SCHED_DEADLINE
SCHED_DEADLINE은 EDF(Earliest Deadline First) 알고리즘과 CBS(Constant Bandwidth Server)를 결합한 스케줄러입니다.
#include <sched.h>
#include <linux/sched.h>
struct sched_attr {
u32 size;
u32 sched_policy; /* SCHED_DEADLINE */
u64 sched_flags;
s32 sched_nice;
u32 sched_priority;
/* SCHED_DEADLINE 파라미터 */
u64 sched_runtime; /* 나노초 단위 실행 시간 */
u64 sched_deadline; /* 상대 deadline */
u64 sched_period; /* 주기 */
};
/* 예: 10ms마다 3ms 실행 시간, 10ms deadline */
struct sched_attr attr = {
.size = sizeof(attr),
.sched_policy = SCHED_DEADLINE,
.sched_runtime = 3 * 1000000, /* 3ms */
.sched_deadline = 10 * 1000000, /* 10ms */
.sched_period = 10 * 1000000, /* 10ms */
};
sched_setattr(0, &attr, 0);
High-Resolution Timers
PREEMPT_RT는 나노초 정밀도의 고해상도 타이머를 제공합니다.
hrtimer API
#include <linux/hrtimer.h>
struct hrtimer {
struct timerqueue_node node;
ktime_t _softexpires;
enum hrtimer_restart (*function)(struct hrtimer *);
struct hrtimer_clock_base *base;
u8 state;
};
/* hrtimer 초기화 */
struct hrtimer timer;
hrtimer_init(&timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
timer.function = &my_hrtimer_callback;
/* 타이머 시작 (100μs 후) */
ktime_t ktime = ktime_set(0, 100000); /* 0초 + 100000ns */
hrtimer_start(&timer, ktime, HRTIMER_MODE_REL);
/* 콜백 함수 */
static enum hrtimer_restart my_hrtimer_callback(struct hrtimer *timer)
{
/* 실시간 작업 수행 */
return HRTIMER_NORESTART; /* or HRTIMER_RESTART */
}
/* 타이머 취소 */
hrtimer_cancel(&timer);
레이턴시 측정
cyclictest
cyclictest는 실시간 레이턴시 측정 표준 도구입니다.
# rt-tests 패키지 설치
$ sudo apt install rt-tests
# 기본 테스트 (10분)
$ sudo cyclictest -m -Sp99 -D 10m
# 상세 옵션
$ sudo cyclictest \
-m # mlockall() 메모리 고정
-Sp99 # SCHED_FIFO, 우선순위 99
-i 1000 # 1ms 간격
-h 100 # 히스토그램 100μs 범위
-D 1h # 1시간 테스트
-q # quiet 모드
--histfile=hist.txt
# 출력 예시
T: 0 (1234) P:99 I:1000 C: 600000 Min: 2 Act: 3 Avg: 3 Max: 12
# T: 스레드 ID
# Min: 최소 레이턴시 (μs)
# Act: 현재 레이턴시
# Avg: 평균 레이턴시
# Max: 최대 레이턴시 (중요!)
ftrace 레이턴시 추적
# irqsoff tracer: 인터럽트 비활성화 시간 측정
# echo irqsoff > /sys/kernel/debug/tracing/current_tracer
# echo 1 > /sys/kernel/debug/tracing/tracing_on
# sleep 10
# cat /sys/kernel/debug/tracing/trace_stat/function0
# preemptoff tracer: 선점 비활성화 시간 측정
# echo preemptoff > /sys/kernel/debug/tracing/current_tracer
# wakeup tracer: 스케줄링 레이턴시 측정
# echo wakeup > /sys/kernel/debug/tracing/current_tracer
# echo 1 > /sys/kernel/debug/tracing/tracing_on
성능 비교
PREEMPT_RT 커널과 일반 커널의 레이턴시 및 처리량 비교입니다. 실제 워크로드에 따라 결과는 달라질 수 있습니다.
레이턴시 벤치마크
| 워크로드 | 일반 커널 (PREEMPT) | PREEMPT_RT | 개선율 |
|---|---|---|---|
| Worst-case latency | ~200 μs | ~15 μs | 93% ↓ |
| Average latency | ~8 μs | ~5 μs | 37% ↓ |
| Jitter (표준편차) | ±25 μs | ±3 μs | 88% ↓ |
| 99.9% percentile | ~50 μs | ~10 μs | 80% ↓ |
워크로드별 성능 특성
| 워크로드 유형 | 일반 커널 | PREEMPT_RT | 권장사항 |
|---|---|---|---|
| 산업 제어 (PLC, 모션 컨트롤) |
❌ 불안정 레이턴시 스파이크 발생 |
✅ 안정적 <20 μs 보장 |
PREEMPT_RT 필수 |
| 오디오 처리 (Jack, PipeWire) |
⚠️ 가능 버퍼 크기 증가 필요 |
✅ 우수 64 샘플 버퍼 가능 |
PREEMPT_RT 권장 |
| 네트워크 (low-latency) (금융, HFT) |
⚠️ 제한적 P99 latency 높음 |
✅ 우수 일관된 레이턴시 |
PREEMPT_RT + CPU isolation |
| 비디오 스트리밍 | ✅ 충분 Soft RT로 처리 가능 |
✅ 더 안정적 프레임 드롭 감소 |
일반 커널로 충분 |
| 웹 서버 (처리량 중심) |
✅ 우수 높은 처리량 |
⚠️ 낮은 처리량 ~5-10% 감소 |
일반 커널 권장 |
| 데이터베이스 | ✅ 우수 높은 처리량 |
⚠️ 낮은 처리량 트랜잭션 속도 감소 |
일반 커널 권장 |
| 컴파일/빌드 | ✅ 빠름 | ⚠️ 느림 ~10-15% 증가 |
일반 커널 권장 |
Trade-offs 요약
| 항목 | PREEMPT_RT 장점 | PREEMPT_RT 단점 |
|---|---|---|
| 레이턴시 | Worst-case 극적 감소 (93% ↓) | Average 약간 증가 (스케줄링 오버헤드) |
| 처리량 | 예측 가능성 향상 | 전체 처리량 5-15% 감소 |
| 전력 소비 | - | 더 많은 context switch → 전력 증가 |
| 복잡도 | 표준 Linux API 사용 | 튜닝 복잡 (CPU isolation, IRQ affinity 등) |
| 디버깅 | ftrace/perf 도구 사용 가능 | 타이밍 버그 재현 어려움 |
- Hard RT 필요 (산업, 로봇, 의료) → PREEMPT_RT 필수
- Low-latency 선호 (오디오, 네트워크) → PREEMPT_RT 권장
- 처리량 중심 (웹, DB, 빌드) → 일반 커널 권장
CPU Isolation
실시간 워크로드를 방해 없이 실행하려면 CPU를 격리해야 합니다.
isolcpus 커널 파라미터
# GRUB 설정 (/etc/default/grub)
GRUB_CMDLINE_LINUX="isolcpus=2,3 nohz_full=2,3 rcu_nocbs=2,3"
# isolcpus=2,3 — CPU 2,3을 일반 스케줄러에서 제외
# nohz_full=2,3 — CPU 2,3에서 타이머 틱 비활성화 (adaptive-tick mode)
# rcu_nocbs=2,3 — CPU 2,3의 RCU 콜백을 다른 CPU로 오프로드
$ sudo update-grub
$ sudo reboot
# 격리된 CPU에 태스크 바인딩
$ taskset -c 2 ./realtime_app
IRQ Affinity 설정
# IRQ를 특정 CPU로 제한 (CPU 0,1만 사용)
# echo 3 > /proc/irq/19/smp_affinity # 0x03 = CPU 0,1
# irqbalance 비활성화 (수동 IRQ 관리 시)
$ sudo systemctl stop irqbalance
$ sudo systemctl disable irqbalance
실시간 시스템 Best Practices
실시간 애플리케이션 체크리스트
- 메모리 사전 할당 —
mlockall()로 모든 메모리 고정, page fault 방지 - 메모리 미리 접근 — 첫 실행 전 모든 페이지 touch하여 minor fault 제거
- 동적 메모리 할당 금지 —
malloc(),new사용 금지 (실시간 루프 내) - CPU 어피니티 설정 —
sched_setaffinity()로 격리된 CPU에 바인딩 - 스케줄링 정책 설정 — SCHED_FIFO 또는 SCHED_DEADLINE
- Stack 프리페칭 — 스택 메모리 미리 접근
실시간 애플리케이션 예시
#include <sched.h>
#include <sys/mman.h>
#include <string.h>
void setup_realtime(void)
{
/* 1. 메모리 고정 (swap 방지) */
mlockall(MCL_CURRENT | MCL_FUTURE);
/* 2. 스택 프리페칭 (page fault 방지) */
unsigned char dummy[8192];
memset(dummy, 0, sizeof(dummy));
/* 3. 실시간 스케줄링 설정 */
struct sched_param param = { .sched_priority = 90 };
sched_setscheduler(0, SCHED_FIFO, ¶m);
/* 4. CPU 어피니티 (격리된 CPU 2) */
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(2, &cpuset);
sched_setaffinity(0, sizeof(cpuset), &cpuset);
}
void realtime_loop(void)
{
struct timespec next, interval = { .tv_nsec = 1000000 }; /* 1ms */
clock_gettime(CLOCK_MONOTONIC, &next);
while (1) {
/* 실시간 작업 */
do_critical_work();
/* 다음 주기까지 대기 */
next.tv_nsec += interval.tv_nsec;
if (next.tv_nsec >= 1000000000) {
next.tv_sec++;
next.tv_nsec -= 1000000000;
}
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, NULL);
}
}
커널 설정
# Preemption Model
CONFIG_PREEMPT_RT=y # Fully Preemptible Kernel (RT)
CONFIG_PREEMPT_RT_FULL=y
# High-Resolution Timers
CONFIG_HIGH_RES_TIMERS=y
CONFIG_NO_HZ_FULL=y # Adaptive-tick mode
# CPU Isolation
CONFIG_RCU_NOCB_CPU=y # RCU callback offloading
CONFIG_NO_HZ_FULL_ALL=y
# IRQ Threading
CONFIG_IRQ_FORCED_THREADING=y
# Disable for RT
# CONFIG_DEBUG_PREEMPT is not set # 디버그 오버헤드 제거
# CONFIG_PROVE_LOCKING is not set
# CONFIG_DEBUG_LOCK_ALLOC is not set
디버깅
RT Throttling
SCHED_FIFO/RR 태스크가 CPU를 독점하지 못하도록 제한합니다.
# RT 태스크는 기본적으로 950ms/1초만 사용 가능
# cat /proc/sys/kernel/sched_rt_period_us
1000000
# cat /proc/sys/kernel/sched_rt_runtime_us
950000
# RT 제한 해제 (주의: 시스템 행 가능)
# echo -1 > /proc/sys/kernel/sched_rt_runtime_us
trace-cmd
# trace-cmd 설치
$ sudo apt install trace-cmd
# 레이턴시 추적
$ sudo trace-cmd record -p function_graph -P 1234 # PID 1234 추적
$ sudo trace-cmd report
트러블슈팅
일반적인 문제와 해결
| 문제 | 원인 | 해결 방법 |
|---|---|---|
| 레이턴시 스파이크 (100+ μs) |
SMI (System Management Interrupt) BIOS 레벨 인터럽트 |
• BIOS에서 SMI 비활성화 • hwlatdetect로 SMI 감지• 최신 펌웨어 업데이트 |
| 예상보다 높은 레이턴시 | CPU 전력 관리 (C-states) 주파수 스케일링 |
• C-states 비활성화: processor.max_cstate=1• 고정 주파수: intel_pstate=disable• Performance governor 설정 |
| RT 태스크가 실행 안 됨 | RT Throttling 제한 (기본 95% CPU) |
• sched_rt_runtime_us 증가• 또는 -1로 제한 해제 (주의) |
| 시스템 응답 없음 | RT 태스크 무한 루프 일반 태스크 CPU 못 받음 |
• Magic SysRq 사용: Alt+SysRq+k • Watchdog 활성화 • RT throttling 유지 |
| mlockall() 실패 (ENOMEM) |
RLIMIT_MEMLOCK 제한 | • ulimit -l unlimited• /etc/security/limits.conf 설정 • CAP_IPC_LOCK capability 부여 |
| IRQ 스레드 없음 | PREEMPT_RT 아닌 커널 또는 드라이버 미지원 |
• ps aux | grep irq 확인• RT 커널 재빌드 • 드라이버 request_threaded_irq() 사용 확인 |
| cyclictest 높은 latency | 백그라운드 프로세스 IRQ affinity 미설정 |
• 불필요한 서비스 중지 • IRQ affinity 설정 • CPU isolation 적용 |
레이턴시 스파이크 원인 분석
# 1. SMI (System Management Interrupt) 감지
$ sudo hwlatdetect --duration=60
Samples: 3600
Max latency: 127 us
SMI count: 12 ← SMI가 레이턴시 원인!
# 해결: BIOS에서 SMI 소스 비활성화 (USB legacy, thermal monitoring 등)
# 2. CPU 전력 관리 확인
$ cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
powersave ← 문제! performance로 변경 필요
$ sudo cpupower frequency-set -g performance
# 3. C-states 확인 및 비활성화
$ cat /sys/devices/system/cpu/cpu0/cpuidle/state*/disable
0
0 ← 모든 C-states 활성화 상태 (문제)
# GRUB 파라미터로 C-states 제한
GRUB_CMDLINE_LINUX="processor.max_cstate=1 intel_idle.max_cstate=0"
# 4. ftrace로 스파이크 원인 추적
$ sudo trace-cmd record -p function_graph \
-e irq -e sched -e timer \
-F cyclictest -m -Sp99 -D 1m
$ sudo trace-cmd report | grep "duration > 50" # 50μs 이상 찾기
# 5. 프로세스별 레이턴시 기여도 확인
$ ps -eLo pid,tid,class,rtprio,pri,nice,cmd | grep FF
1234 1234 FF 99 139 - ./realtime_app ← RT 태스크
5678 5678 FF 50 90 - [irq/19-eth0] ← IRQ 스레드
진단 체크리스트
# RT 시스템 상태 종합 점검 스크립트
## 1. 커널 버전 및 PREEMPT_RT 확인
$ uname -a | grep PREEMPT_RT
$ cat /sys/kernel/realtime # 1이면 RT 커널
## 2. CPU Governor 확인
$ cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor | sort -u
## 3. IRQ Threading 확인
$ ps aux | grep "\[irq/" | wc -l # 0이면 문제
## 4. RT Throttling 설정
$ cat /proc/sys/kernel/sched_rt_runtime_us
## 5. Isolated CPU 확인
$ cat /sys/devices/system/cpu/isolated
## 6. RCU callback offloading
$ cat /sys/devices/system/cpu/nohz_full
## 7. 메모리 잠금 한계
$ ulimit -l # unlimited 여야 함
## 8. SMI 카운트 (Intel)
$ sudo rdmsr -a 0x34 # SMI_COUNT MSR
참고자료
- Linux Foundation Real-Time Linux Wiki
- RT Application Best Practices
- PREEMPT_RT Patches
Documentation/timers/hrtimers.rst— High-Resolution Timerskernel/sched/deadline.c— SCHED_DEADLINE 구현kernel/locking/rtmutex.c— RT Mutex 구현
- 프로세스 스케줄러 — 스케줄링 정책 상세
- cpusets & CPU Isolation — CPU 격리 심화
- 타이머 — High-Resolution Timer 구조
- 인터럽트 — Interrupt Threading
PREEMPT_RT 아키텍처
PREEMPT_RT 패치셋은 리눅스 커널의 비선점 구간을 체계적으로 제거합니다. 핵심 변환은 세 가지입니다: (1) spinlock_t를 sleeping lock(rt_mutex 기반)으로 교체, (2) 하드웨어 인터럽트를 커널 스레드로 전환(threaded interrupts), (3) softirq를 스레드화하여 선점 가능하게 만듭니다.
Sleeping Spinlocks 상세
PREEMPT_RT에서 spinlock_t는 매크로 수준에서 rt_mutex 기반 sleeping lock으로 재정의됩니다. 이 변환은 커널 소스 수정 없이 include/linux/spinlock_types.h 헤더에서 이루어집니다.
/* include/linux/spinlock_types.h — PREEMPT_RT 빌드 시 */
#ifdef CONFIG_PREEMPT_RT
typedef struct spinlock {
struct rt_mutex_base lock; /* rt_mutex 기반 구현 */
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
} spinlock_t;
#else
typedef struct spinlock {
union {
struct raw_spinlock rlock; /* 전통적 busy-wait spinlock */
};
} spinlock_t;
#endif
raw_spinlock_t는 PREEMPT_RT에서도 변환되지 않는 진짜 spinlock입니다. 스케줄러 코어(rq->lock), 타이머 인터럽트 코어, 아키텍처 저수준 코드 등 sleep이 절대 불가능한 컨텍스트에서만 사용합니다. 일반 드라이버에서는 사용을 피하세요.
Threaded Interrupts 상세 메커니즘
PREEMPT_RT는 CONFIG_IRQ_FORCED_THREADING을 통해 거의 모든 인터럽트 핸들러를 자동으로 스레드화합니다. 드라이버가 request_irq()로 등록한 핸들러도 내부적으로 threaded IRQ로 전환됩니다.
/* kernel/irq/manage.c — forced threading 핵심 로직 */
static int irq_setup_forced_threading(struct irqaction *new)
{
/* IRQF_NO_THREAD 플래그가 설정되면 스레딩 건너뜀 */
if (new->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT))
return 0;
/* 기존 hardirq handler를 thread_fn으로 이동 */
new->thread_fn = new->handler;
new->handler = irq_default_primary_handler;
new->flags |= IRQF_ONESHOT;
return 0;
}
| 구분 | 일반 커널 | PREEMPT_RT |
|---|---|---|
| 인터럽트 핸들러 | hardirq 컨텍스트 (비선점) | 커널 스레드 (선점 가능) |
| softirq | 인터럽트 리턴 시 즉시 실행 | ksoftirqd 스레드에서 실행 |
| IRQ 우선순위 | 모든 IRQ 동일 (FIFO) | chrt로 개별 IRQ 스레드 우선순위 조정 |
| Lock 내 sleep | BUG (금지) | 허용 (rt_mutex 기반) |
| 비선점 구간 | spinlock 보유 시 전체 | raw_spinlock 보유 시만 |
# IRQ 스레드 우선순위 확인 및 조정
$ ps -eo pid,cls,rtprio,comm | grep irq/
12 FF 50 irq/9-acpi
14 FF 50 irq/16-ehci
18 FF 50 irq/19-eth0
# 네트워크 IRQ를 최고 우선순위로 설정
$ sudo chrt -f -p 90 18
# IRQF_NO_THREAD 핸들러 확인 (스레딩 제외 IRQ)
$ grep -r "IRQF_NO_THREAD" drivers/ | head -5
우선순위 상속 프로토콜
우선순위 상속(Priority Inheritance, PI)은 우선순위 역전 문제의 핵심 해결책입니다. PREEMPT_RT의 rt_mutex는 PI 체인을 통해 중첩된 락 보유 관계에서도 올바른 우선순위 전파를 보장합니다.
PI 체인 워크스루
커널 내부에서 PI 체인 전파는 rt_mutex_adjust_prio_chain() 함수가 담당합니다. 이 함수는 waiter → owner → owner의 waiter 관계를 재귀적으로 순회하며 우선순위를 전파합니다.
/* kernel/locking/rtmutex.c — PI 체인 조정 핵심 */
static int rt_mutex_adjust_prio_chain(
struct task_struct *task,
enum rtmutex_chainwalk chwalk,
struct rt_mutex_base *orig_lock,
struct rt_mutex_base *next_lock,
struct rt_mutex_waiter *orig_waiter,
struct task_struct *top_task)
{
struct rt_mutex_waiter *waiter, *top_waiter;
struct rt_mutex_base *lock;
/* 체인 워크 루프: owner를 따라 올라감 */
again:
/* 데드락 감지: 최대 깊이 제한 */
if (++depth > 1024) {
WARN(1, "PI chain too deep!");
return -EDEADLK;
}
/* task의 최고 우선순위 waiter 확인 */
waiter = rt_mutex_top_waiter(lock);
top_waiter = task_top_pi_waiter(task);
/* 우선순위 전파가 필요한지 확인 */
if (rt_waiter_node_less(&waiter->tree, &top_waiter->tree))
rt_mutex_adjust_prio(task); /* 태스크 우선순위 상승 */
/* 다음 lock owner로 체인 계속 */
task = rt_mutex_owner(lock);
if (task)
goto again;
return 0;
}
-EDEADLK를 반환합니다. 실제 시스템에서 3단계 이상의 PI 체인은 설계 결함을 의심해야 합니다.
| PI 프로토콜 | 설명 | Linux 구현 |
|---|---|---|
| Basic PI | 직접 owner의 우선순위만 상승 | 단일 rt_mutex lock/unlock |
| Transitive PI | 체인 전체 우선순위 전파 | rt_mutex_adjust_prio_chain() |
| Nested PI | 중첩 락 보유 시 다중 boosting | task->pi_waiters RB-tree 관리 |
cyclictest 방법론
cyclictest는 RT 커널의 worst-case latency를 정량적으로 측정하는 표준 도구입니다. 단순한 실행을 넘어 올바른 방법론으로 측정해야 의미 있는 결과를 얻을 수 있습니다.
체계적 측정 방법론
# ============================================
# 1단계: 환경 준비 (측정 전 필수)
# ============================================
# CPU governor를 performance로 고정
$ sudo cpupower frequency-set -g performance
# C-states 비활성화
$ sudo sh -c 'for f in /sys/devices/system/cpu/cpu*/cpuidle/state*/disable; do echo 1 > $f; done'
# irqbalance 중지
$ sudo systemctl stop irqbalance
# ============================================
# 2단계: 부하 생성 (worst-case 유도)
# ============================================
# stress-ng로 다양한 부하 생성
$ sudo stress-ng --cpu 4 --io 2 --vm 2 --vm-bytes 256M \
--fork 4 --timeout 0 &
# 네트워크 부하 (별도 머신에서)
$ iperf3 -c target_ip -t 3600 -P 4
# 디스크 I/O 부하
$ sudo fio --name=randwrite --ioengine=libaio --rw=randwrite \
--bs=4k --numjobs=4 --size=1G --runtime=3600
# ============================================
# 3단계: cyclictest 실행
# ============================================
# 프로덕션 레벨 측정 (최소 24시간 권장)
$ sudo cyclictest \
--mlockall \
--smp \
--priority=99 \
--interval=1000 \
--histogram=200 \
--duration=24h \
--histfile=latency_hist.txt \
--quiet
# ============================================
# 4단계: 결과 분석
# ============================================
# 히스토그램 결과 해석
$ head -20 latency_hist.txt
# 0 1234 5678 3456 ← latency 0us에서의 빈도
# 1 45678 43210 44567 ← latency 1us
# 2 98765 97654 96543 ← latency 2us (최다빈도 구간)
# 3 87654 86543 85432
# ...
# 15 2 0 1 ← 15us (이상치)
지터(Jitter) 분석
지터는 레이턴시의 변동폭으로, RT 시스템의 예측 가능성을 나타냅니다. 평균 레이턴시보다 지터가 더 중요합니다.
| 지표 | 계산 방법 | 양호 기준 (PREEMPT_RT) | 불량 징후 |
|---|---|---|---|
| Max Latency | cyclictest Max 값 | <20 us | >50 us (SMI, C-state 의심) |
| Avg Latency | cyclictest Avg 값 | <5 us | >10 us (설정 문제) |
| Jitter | Max - Min | <15 us | >30 us (비선점 구간 존재) |
| P99.9 | 99.9 백분위 값 | <10 us | >25 us (간헐적 간섭) |
| 표준편차 | 히스토그램 기반 계산 | <3 us | >8 us (불안정) |
gnuplot이나 python matplotlib로 시각화하면 문제 패턴을 빠르게 파악할 수 있습니다.
# gnuplot으로 히스토그램 시각화
$ gnuplot << 'PLOT'
set terminal png size 800,400
set output "latency_histogram.png"
set title "Latency Histogram"
set xlabel "Latency (us)"
set ylabel "Frequency"
set style fill solid 0.5
plot "latency_hist.txt" using 1:2 with boxes title "CPU 0", \
"" using 1:3 with boxes title "CPU 1"
PLOT
RT 스케줄러 내부
RT 스케줄러(kernel/sched/rt.c)는 고정 우선순위 기반의 결정론적 스케줄링을 제공합니다. 내부적으로 rt_prio_array 비트맵과 pushable_tasks 리스트를 사용하여 O(1) 시간 복잡도로 다음 실행 태스크를 선택합니다.
/* kernel/sched/rt.c — rt_prio_array 구조 */
struct rt_prio_array {
DECLARE_BITMAP(bitmap, MAX_RT_PRIO + 1); /* 100개 우선순위 비트맵 */
struct list_head queue[MAX_RT_PRIO]; /* 우선순위별 런큐 */
};
struct rt_rq {
struct rt_prio_array active; /* 활성 태스크 배열 */
unsigned int rt_nr_running; /* RT 태스크 수 */
unsigned int rr_nr_running; /* SCHED_RR 태스크 수 */
struct {
int curr; /* 현재 최고 우선순위 */
int next; /* 다음 최고 우선순위 */
} highest_prio;
unsigned int rt_nr_migratory; /* 이동 가능 태스크 수 */
int overloaded; /* CPU가 과부하 상태? */
struct plist_head pushable_tasks; /* push 가능 태스크 */
};
/* O(1) 태스크 선택: 최고 우선순위 비트 탐색 */
static struct task_struct *pick_next_task_rt(struct rq *rq)
{
struct rt_rq *rt_rq = &rq->rt;
struct rt_prio_array *array = &rt_rq->active;
int idx;
/* 비트맵에서 첫 번째 set bit 탐색 (O(1)) */
idx = sched_find_first_bit(array->bitmap);
if (idx >= MAX_RT_PRIO)
return NULL; /* RT 태스크 없음 */
/* 해당 우선순위 큐의 첫 번째 태스크 반환 */
return list_first_entry(&array->queue[idx],
struct sched_rt_entity, run_list);
}
Push/Pull 밸런싱
SMP 시스템에서 RT 스케줄러는 push/pull 메커니즘으로 RT 태스크를 CPU 간 이동시킵니다. 높은 우선순위 태스크가 낮은 우선순위 태스크가 실행 중인 CPU로 이동(push)하거나, idle CPU가 과부하 CPU에서 태스크를 가져옵니다(pull).
/* push_rt_task(): 현재 CPU에서 다른 CPU로 RT 태스크 이동 */
static int push_rt_task(struct rq *rq)
{
struct task_struct *next_task;
struct rq *lowest_rq;
/* pushable_tasks에서 가장 높은 우선순위 태스크 선택 */
next_task = plist_first_entry(&rq->rt.pushable_tasks,
struct task_struct, pushable_tasks);
/* 가장 낮은 우선순위 태스크가 실행 중인 CPU 탐색 */
lowest_rq = find_lowest_rq(next_task);
/* 해당 CPU의 현재 태스크보다 높은 우선순위면 이동 */
if (task_prio(lowest_rq->curr) > task_prio(next_task)) {
deactivate_task(rq, next_task, 0);
activate_task(lowest_rq, next_task, 0);
resched_curr(lowest_rq); /* 대상 CPU 재스케줄 */
}
return 1;
}
overloaded=1이 설정됩니다. 이 플래그가 설정된 CPU에서만 push 동작이 트리거되며, cpupri 자료구조를 통해 가장 낮은 우선순위 CPU를 O(1)로 찾습니다.
SCHED_DEADLINE 스케줄러
SCHED_DEADLINE은 Linux에서 가장 높은 우선순위의 스케줄링 클래스입니다. EDF(Earliest Deadline First) 알고리즘과 CBS(Constant Bandwidth Server) 메커니즘을 결합하여 주기적 실시간 태스크의 시간 격리(temporal isolation)를 보장합니다.
SCHED_DEADLINE API 상세
/* SCHED_DEADLINE 태스크 설정 — 커널 내부 구조 */
struct sched_dl_entity {
struct rb_node rb_node; /* 데드라인 정렬 RB-tree */
u64 dl_runtime; /* 최대 실행 시간 (ns) */
u64 dl_deadline; /* 상대 deadline (ns) */
u64 dl_period; /* 태스크 주기 (ns) */
u64 runtime; /* 남은 실행 시간 */
u64 deadline; /* 절대 deadline */
unsigned int dl_throttled : 1; /* budget 소진 시 throttle */
unsigned int dl_boosted : 1; /* PI로 boosted? */
};
/* 유저스페이스에서 SCHED_DEADLINE 태스크 설정 예시 */
#include <sched.h>
#include <sys/syscall.h>
#include <unistd.h>
#define SCHED_DEADLINE 6
struct sched_attr {
__u32 size;
__u32 sched_policy;
__u64 sched_flags;
__s32 sched_nice;
__u32 sched_priority;
__u64 sched_runtime;
__u64 sched_deadline;
__u64 sched_period;
};
void setup_deadline_task(void)
{
struct sched_attr attr = {
.size = sizeof(attr),
.sched_policy = SCHED_DEADLINE,
.sched_runtime = 5 * 1000000, /* 5ms runtime */
.sched_deadline = 20 * 1000000, /* 20ms deadline */
.sched_period = 20 * 1000000, /* 20ms period */
};
/* sched_setattr() 시스템 콜 (glibc wrapper 없음) */
int ret = syscall(SYS_sched_setattr, 0, &attr, 0);
if (ret < 0) {
perror("sched_setattr");
/* EBUSY: admission control 실패 (대역폭 초과) */
/* EPERM: CAP_SYS_NICE 권한 없음 */
}
/* 주기적 실행 루프 */
while (1) {
do_periodic_work();
sched_yield(); /* budget 반환, 다음 period까지 sleep */
}
}
| 항목 | SCHED_FIFO | SCHED_DEADLINE |
|---|---|---|
| 우선순위 | 정적 (1-99) | 동적 (EDF 기반 deadline) |
| CPU 대역폭 제어 | 없음 (독점 가능) | CBS로 자동 대역폭 제한 |
| Admission Control | 없음 | sched_setattr() 시 검증 |
| 시간 격리 | 없음 (높은 prio가 독점) | 보장 (budget 소진 시 throttle) |
| SMP 밸런싱 | push/pull (cpupri) | push/pull (cpudl) |
| 적합 용도 | 단일 critical 태스크 | 다중 주기적 RT 태스크 |
RT 스로틀링
RT 스로틀링은 실시간 태스크가 시스템을 독점하는 것을 방지하는 안전장치입니다. sched_rt_runtime_us와 sched_rt_period_us 두 파라미터로 RT 태스크의 CPU 사용 비율을 제한합니다.
스로틀링 상세 동작
# 현재 RT 스로틀링 설정 확인
$ cat /proc/sys/kernel/sched_rt_period_us
1000000 # 1초 주기
$ cat /proc/sys/kernel/sched_rt_runtime_us
950000 # 1초 중 950ms까지 RT 허용 (95%)
# 스로틀 발생 확인
$ dmesg | grep "RT throttling"
[ 120.456] sched: RT throttling activated
# RT 스로틀 통계
$ cat /proc/sched_debug | grep rt_throttled
.rt_throttled : 1
# ============================================
# cgroup v2 RT 대역폭 제어 (안전한 방법)
# ============================================
# RT 태스크 전용 cgroup 생성
$ sudo mkdir /sys/fs/cgroup/rt_tasks
# cpu.max로 대역폭 제한 (500ms per 1000ms = 50%)
$ echo "500000 1000000" | sudo tee /sys/fs/cgroup/rt_tasks/cpu.max
# RT 태스크를 cgroup에 배치
$ echo $PID | sudo tee /sys/fs/cgroup/rt_tasks/cgroup.procs
| 설정 | 값 | 의미 | 권장 용도 |
|---|---|---|---|
rt_runtime_us=950000 |
95% | 기본값. 5% CFS 보장 | 일반 서버 |
rt_runtime_us=980000 |
98% | 거의 전부 RT에 할당 | 전용 RT 시스템 |
rt_runtime_us=-1 |
100% | 스로틀 해제 (위험) | watchdog 필수 환경만 |
| cgroup cpu.max | 가변 | 태스크별 세밀한 제어 | 다중 RT 태스크 환경 |
지연시간 분석
실시간 시스템에서 지연시간(latency)은 이벤트 발생부터 태스크 실행까지의 전체 경로입니다. 이 경로를 세분화하여 병목을 식별해야 합니다.
ftrace Latency Tracers
# ============================================
# irqsoff tracer: 인터럽트 비활성화 최대 시간
# ============================================
$ cd /sys/kernel/debug/tracing
# tracer 설정
# echo irqsoff > current_tracer
# echo 0 > tracing_max_latency # 기존 최대값 초기화
# echo 1 > tracing_on
# 부하 실행 (10초)
$ sudo stress-ng --cpu 4 --io 2 --timeout 10s
# 최대 irq-off 시간 확인
# cat tracing_max_latency
42 # 42us 동안 인터럽트가 비활성화됨
# 해당 구간의 콜스택 확인
# cat trace
# irqsoff latency trace v1.1.5 on 6.6.0-rt15
# latency: 42 us, #4/4, CPU#2 | (M:preempt VP:0, KP:0, SP:0 HP:0)
# -----------------
# | task: stress-ng-1234
# -----------------
# _------=> CPU#
# / _-----=> irqs-off
# | / _----=> need-resched
# || / _---=> hardirq/softirq
# ||| / _--=> preempt-depth
# |||| /
# cmd pid ||||| time | caller
# \ / ||||| \ | /
stress 1234 2d..1 0us : _raw_spin_lock_irqsave
stress 1234 2d..1 42us : _raw_spin_unlock_irqrestore
# ============================================
# wakeup tracer: RT 태스크 wakeup → 실행 지연
# ============================================
# echo wakeup_rt > current_tracer
# echo 0 > tracing_max_latency
# echo 1 > tracing_on
# cyclictest 실행 (RT wakeup 생성)
$ sudo cyclictest -m -Sp99 -D 30s &
# 최대 wakeup latency 확인
# cat tracing_max_latency
8 # 8us: wakeup에서 실행까지 최대 지연
# ============================================
# preemptirqsoff: irq-off + preempt-off 결합
# ============================================
# echo preemptirqsoff > current_tracer
# echo 0 > tracing_max_latency
# echo 1 > tracing_on
irqsoff: 인터럽트 비활성화 구간 최대값 (하드웨어 인터럽트 차단 시간)preemptoff: 선점 비활성화 구간 최대값 (spinlock 보유 시간)preemptirqsoff: 위 두 가지 결합 (가장 포괄적)wakeup_rt: RT 태스크의 wakeup-to-running 지연 (end-to-end 관점)
rt_mutex 구조체 분석
rt_mutex는 PREEMPT_RT의 핵심 동기화 프리미티브입니다. 우선순위 정렬된 RB-tree 대기 큐와 PI(Priority Inheritance) 체인 관리를 통해 결정론적 락 동작을 보장합니다.
/* include/linux/rtmutex.h — 핵심 구조체 */
struct rt_mutex_base {
raw_spinlock_t wait_lock; /* waiter 리스트 보호 */
struct rb_root_cached waiters; /* 우선순위 정렬 RB-tree */
struct task_struct *owner; /* 현재 owner + 플래그 비트 */
};
struct rt_mutex {
struct rt_mutex_base rtmutex;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
};
/* 대기자(waiter) 구조체 */
struct rt_mutex_waiter {
struct rb_node tree; /* mutex의 waiters RB-tree 노드 */
struct rb_node pi_tree; /* owner의 pi_waiters RB-tree 노드 */
struct task_struct *task; /* 대기 중인 태스크 */
struct rt_mutex_base *lock; /* 대기 중인 mutex */
unsigned int wake_state; /* wakeup 상태 */
struct rt_waiter_node tree_entry; /* 정렬 키: prio, deadline */
struct rt_waiter_node pi_tree_entry;
};
/* task_struct의 PI 관련 필드 */
struct task_struct {
/* ... */
struct rb_root_cached pi_waiters; /* 이 태스크를 기다리는 waiter들 */
struct rt_mutex_waiter *pi_top_task; /* 최고 우선순위 waiter */
struct rt_mutex_base *pi_blocked_on; /* 이 태스크가 대기 중인 mutex */
/* ... */
};
trylock/lock/unlock 코드 워크스루
/* === rt_mutex_trylock() === */
int rt_mutex_trylock(struct rt_mutex *lock)
{
/* Fast path: owner가 NULL이면 atomic으로 현재 태스크 설정 */
if (cmpxchg_acquire(&lock->rtmutex.owner, NULL, current))
return 1; /* 성공: lock 획득 */
return 0; /* 실패: 이미 다른 태스크가 보유 */
}
/* === rt_mutex_lock() 전체 경로 === */
void rt_mutex_lock(struct rt_mutex *lock)
{
might_sleep(); /* atomic 컨텍스트에서 호출 시 경고 */
/* Fast path: 비경합 시 cmpxchg로 즉시 획득 */
if (likely(rt_mutex_cmpxchg_acquire(lock, NULL, current)))
return;
/* Slow path: 경합 발생 */
rt_mutex_slowlock(lock);
}
/* Slow path 상세 */
static int rt_mutex_slowlock_block(struct rt_mutex_base *lock,
struct rt_mutex_waiter *waiter)
{
for (;;) {
/* 1. waiter를 mutex의 waiters RB-tree에 삽입 */
/* 2. waiter를 owner의 pi_waiters RB-tree에 삽입 */
/* 3. owner의 우선순위 조정 (PI) */
rt_mutex_adjust_prio_chain(owner, ...);
/* 4. 현재 태스크를 sleep 상태로 전환 */
set_current_state(TASK_UNINTERRUPTIBLE);
/* 5. owner가 unlock할 때까지 대기 */
if (try_to_take_rt_mutex(lock, current, waiter))
break; /* lock 획득 성공 */
schedule(); /* CPU 양보 */
}
return 0;
}
/* === rt_mutex_unlock() === */
void rt_mutex_unlock(struct rt_mutex *lock)
{
/* Fast path: waiter 없으면 단순 해제 */
if (likely(rt_mutex_cmpxchg_release(lock, current, NULL)))
return;
/* Slow path: waiter가 있으면 */
/* 1. owner의 pi_waiters에서 이 mutex의 waiter 제거 */
/* 2. owner의 우선순위 복원 (de-boost) */
/* 3. 최고 우선순위 waiter를 깨움 */
rt_mutex_slowunlock(lock);
}
cmpxchg atomic 연산으로 lock/unlock이 완료됩니다. Slow path는 실제 경합이 발생했을 때만 실행되며, PI 체인 조정 비용이 추가됩니다.
ftrace를 이용한 RT 디버깅
ftrace는 PREEMPT_RT 커널의 레이턴시 문제를 진단하는 핵심 도구입니다. preemptirqsoff tracer와 trace-cmd, bpftrace를 조합하면 마이크로초 단위의 지연 원인을 정확히 파악할 수 있습니다.
preemptirqsoff Tracer 실전
# preemptirqsoff: 선점+인터럽트 비활성화 최대 구간 추적
# echo preemptirqsoff > /sys/kernel/debug/tracing/current_tracer
# echo 0 > /sys/kernel/debug/tracing/tracing_max_latency
# echo 1 > /sys/kernel/debug/tracing/tracing_on
# 부하 생성과 동시에 추적
$ sudo stress-ng --cpu 4 --io 2 --vm 2 --timeout 60s
# 최대 지연 확인
# cat /sys/kernel/debug/tracing/tracing_max_latency
23 # 23us: 최대 비선점/irq-off 구간
# 상세 트레이스 확인 (어떤 함수가 23us를 소비했는지)
# cat /sys/kernel/debug/tracing/trace | head -30
cyclictest + trace-cmd 연동
# trace-cmd로 cyclictest 실행 중 이벤트 캡처
$ sudo trace-cmd record \
-e sched:sched_switch \
-e sched:sched_wakeup \
-e irq:irq_handler_entry \
-e irq:irq_handler_exit \
-e preemptirq:irq_disable \
-e preemptirq:irq_enable \
-e preemptirq:preempt_disable \
-e preemptirq:preempt_enable \
-- cyclictest -m -Sp99 -i 1000 -D 30s
# 레이턴시 스파이크 원인 분석
$ sudo trace-cmd report --cpu 2 | grep -A5 "sched_wakeup.*cyclictest"
# wakeup에서 switch까지 시간 차이 계산
$ sudo trace-cmd report | awk '
/sched_wakeup.*cyclictest/ { wakeup_ts = $4 }
/sched_switch.*==> cyclictest/ {
switch_ts = $4
latency = switch_ts - wakeup_ts
if (latency > 0.010) printf("HIGH LATENCY: %.3f ms\n", latency*1000)
}'
# function_graph으로 특정 함수 실행 시간 분석
$ sudo trace-cmd record -p function_graph \
-g schedule \
-g try_to_wake_up \
-P $(pgrep cyclictest) \
-- sleep 10
$ sudo trace-cmd report | grep -E "^[^|]*[0-9]+\.[0-9]+ us" | sort -rn -k2 | head -20
bpftrace RT 진단 예제
# RT 태스크의 wakeup-to-run 지연 히스토그램 (bpftrace)
$ sudo bpftrace -e '
tracepoint:sched:sched_wakeup
/args->prio < 100/ /* RT 태스크만 (prio 0-99) */
{
@wakeup[args->pid] = nsecs;
}
tracepoint:sched:sched_switch
/args->next_prio < 100 && @wakeup[args->next_pid]/
{
$latency = (nsecs - @wakeup[args->next_pid]) / 1000; /* ns → us */
@us_hist = hist($latency);
if ($latency > 50) {
printf("HIGH LATENCY: pid=%d comm=%s lat=%d us\n",
args->next_pid, args->next_comm, $latency);
}
delete(@wakeup[args->next_pid]);
}
END {
printf("\nRT Wakeup-to-Run Latency Histogram (us):\n");
print(@us_hist);
}
'
# IRQ 스레드 실행 시간 측정
$ sudo bpftrace -e '
tracepoint:irq:irq_handler_entry {
@start[args->irq] = nsecs;
}
tracepoint:irq:irq_handler_exit
/@start[args->irq]/ {
$dur = (nsecs - @start[args->irq]) / 1000;
@irq_duration[args->irq] = hist($dur);
delete(@start[args->irq]);
}
'
# preempt_disable 구간 추적
$ sudo bpftrace -e '
tracepoint:preemptirq:preempt_disable {
@pd_start[tid] = nsecs;
}
tracepoint:preemptirq:preempt_enable
/@pd_start[tid]/ {
$dur = (nsecs - @pd_start[tid]) / 1000;
if ($dur > 20) {
printf("Long preempt-off: %d us, caller=%s\n", $dur, kstack);
}
@pd_hist = hist($dur);
delete(@pd_start[tid]);
}
'
- ftrace tracer: 커널 빌트인, 오버헤드 최소, 최대값 추적에 최적
- trace-cmd: ftrace의 사용자 친화적 래퍼, 이벤트 기반 분석
- bpftrace: 프로그래밍 가능한 필터, 히스토그램, 조건부 출력에 강력
- perf: 하드웨어 PMU 카운터 기반, 캐시 미스/분기 예측 분석
실시간 튜닝 체크리스트
RT 시스템 튜닝은 커널 빌드 → 부트 파라미터 → 런타임 설정 → 애플리케이션 설정 순서로 체계적으로 진행해야 합니다. 각 단계에서 cyclictest로 개선 효과를 측정하세요.
커널 부트 파라미터
| 파라미터 | 값 | 설명 | 효과 |
|---|---|---|---|
isolcpus |
2,3 | CPU 2,3을 일반 스케줄러에서 제외 | RT 태스크 전용 CPU 확보 |
nohz_full |
2,3 | adaptive-tick 모드 (타이머 틱 제거) | 주기적 인터럽트 제거 |
rcu_nocbs |
2,3 | RCU 콜백을 다른 CPU로 오프로드 | RCU 처리 지연 제거 |
irqaffinity |
0,1 | IRQ를 CPU 0,1로 제한 | 격리 CPU의 IRQ 간섭 제거 |
processor.max_cstate |
1 | 깊은 C-state 진입 방지 | wakeup 지연 제거 |
intel_idle.max_cstate |
0 | Intel idle 드라이버 C-state 제한 | C-state 전환 오버헤드 제거 |
idle=poll |
- | idle 시 polling 모드 (C0 유지) | 최저 지연 (전력 소비 증가) |
nosoftlockup |
- | soft lockup 감지 비활성화 | 긴 RT 태스크 실행 허용 |
tsc=reliable |
- | TSC를 신뢰할 수 있는 클럭으로 표시 | 타이머 소스 안정화 |
nowatchdog |
- | NMI watchdog 비활성화 | NMI 지연 제거 |
nmi_watchdog |
0 | NMI watchdog 비활성화 (대안) | NMI 지연 제거 |
skew_tick |
1 | CPU간 타이머 틱 분산 | 동시 타이머 처리 경합 감소 |
# /etc/default/grub — 프로덕션 RT 설정 예시
GRUB_CMDLINE_LINUX="isolcpus=2,3 nohz_full=2,3 rcu_nocbs=2,3 \
irqaffinity=0,1 processor.max_cstate=1 intel_idle.max_cstate=0 \
nosoftlockup tsc=reliable nmi_watchdog=0 skew_tick=1 \
default_hugepagesz=2M hugepagesz=2M hugepages=512"
$ sudo update-grub && sudo reboot
런타임 튜닝
# ============================================
# 1. CPU Governor 고정
# ============================================
$ sudo cpupower frequency-set -g performance
# 또는 모든 CPU에 개별 적용
$ for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
echo performance | sudo tee $cpu
done
# ============================================
# 2. IRQ Affinity 설정
# ============================================
# irqbalance 비활성화
$ sudo systemctl stop irqbalance
$ sudo systemctl disable irqbalance
# 모든 IRQ를 CPU 0,1로 제한 (격리 CPU 2,3에서 제외)
$ for irq in /proc/irq/*/smp_affinity; do
echo 3 | sudo tee $irq 2>/dev/null
done
# ============================================
# 3. 메모리 관리
# ============================================
# Transparent Huge Pages 비활성화 (예측 불가능 지연 발생)
$ echo never | sudo tee /sys/kernel/mm/transparent_hugepage/enabled
# swap 비활성화 (page fault 방지)
$ sudo swapoff -a
# vm.stat_interval 증가 (vmstat 업데이트 빈도 감소)
$ sudo sysctl -w vm.stat_interval=120
# ============================================
# 4. 네트워크 튜닝
# ============================================
# Busy polling (네트워크 지연 감소)
$ sudo sysctl -w net.core.busy_read=50
$ sudo sysctl -w net.core.busy_poll=50
# ============================================
# 5. 실시간 그룹 스케줄링
# ============================================
# RT bandwidth 조정
$ sudo sysctl -w kernel.sched_rt_runtime_us=980000 # 98%
# ============================================
# 6. 검증: 튜닝 후 cyclictest
# ============================================
$ sudo cyclictest -m -Sp99 -i 1000 -D 1h \
--affinity=2 --histogram=200 --histfile=tuned_hist.txt
최종 튜닝 체크리스트
| 단계 | 항목 | 확인 명령 | 기대 결과 |
|---|---|---|---|
| 1 | RT 커널 확인 | uname -a | grep PREEMPT_RT |
PREEMPT_RT 문자열 포함 |
| 2 | CPU 격리 확인 | cat /sys/devices/system/cpu/isolated |
2-3 (격리 CPU 번호) |
| 3 | nohz_full 확인 | cat /sys/devices/system/cpu/nohz_full |
2-3 |
| 4 | RCU nocbs 확인 | cat /sys/kernel/rcu_nocbs |
2-3 |
| 5 | CPU Governor | cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor |
performance |
| 6 | IRQ affinity | cat /proc/irq/*/smp_affinity |
격리 CPU 제외 |
| 7 | irqbalance 중지 | systemctl is-active irqbalance |
inactive |
| 8 | THP 비활성화 | cat /sys/.../transparent_hugepage/enabled |
[never] |
| 9 | swap 비활성화 | swapon --show |
출력 없음 |
| 10 | SMI 확인 | hwlatdetect --duration=60 |
Max latency <10 us |
| 11 | cyclictest 최종 | cyclictest -m -Sp99 -D 24h |
Max <20 us |
관련 문서
- Kernel Threads (커널 스레드) — kthreadd와 kthread API(create/run/stop/park), kthre
- 시그널 처리 (Signal Handling) — signal delivery 규칙, pending 큐/우선순위, sigaction과 sig