Threaded IRQ (스레드화 인터럽트(Interrupt))
request_threaded_irq() 기반 스레드화 인터럽트 핸들러(Handler)를 심층 분석합니다. 전통적 Top/Bottom Half 모델의 한계를 극복하기 위해 도입된 threaded IRQ의 설계 동기, hardirq handler와 thread_fn의 역할 분리, IRQ_ONESHOT 시맨틱, force_irqthreads 메커니즘, devm managed API, nested IRQ, PREEMPT_RT 통합을 커널 소스 기반으로 분석하고, I2C/SPI/GPIO/PCIe 실전 드라이버 패턴과 기존 드라이버 마이그레이션 가이드를 제공합니다.
핵심 요약
- request_threaded_irq() — hardirq handler와 thread_fn을 분리하여 등록하는 API. handler는 빠른 ACK/확인만, thread_fn은 프로세스 컨텍스트에서 실행됩니다.
- IRQ_WAKE_THREAD — hardirq handler가 반환하는 값으로, "커널 스레드를 깨워서 나머지 작업을 처리하라"는 신호입니다.
- IRQ_ONESHOT — thread_fn 완료까지 인터럽트 라인을 마스킹 상태로 유지합니다. Level-triggered 인터럽트에서 IRQ storm 방지에 필수입니다.
- force_irqthreads — 커널 cmdline
threadirqs또는 PREEMPT_RT에서 모든 인터럽트를 강제로 스레드화합니다. - devm_request_threaded_irq() — 디바이스 생명주기에 맞춰 자동으로 IRQ를 해제하는 managed API입니다.
단계별 이해
- 전통 모델의 한계 파악
hardirq에서 모든 작업을 처리하면 인터럽트 비활성화 시간이 길어지고, Bottom Half(tasklet/workqueue)는 우선순위(Priority) 제어가 어렵습니다. - 스레드화 분리 모델 이해
hardirq handler는 최소한의 ACK만 수행하고 IRQ_WAKE_THREAD를 반환하면, 전용 커널 스레드(irq/N-name)가 나머지를 처리합니다. - ONESHOT 시맨틱 학습
Level-triggered 인터럽트에서 왜 thread_fn 완료까지 인터럽트를 마스킹해야 하는지 이해합니다. - 실전 API 적용
devm_request_threaded_irq()로 드라이버를 작성하고, regmap_irq 등 프레임워크와 통합하는 패턴을 익힙니다. - PREEMPT_RT 통합 확인
force_irqthreads가 기존 request_irq() 기반 드라이버에 미치는 영향을 확인하고, RT 환경에서의 우선순위 튜닝을 학습합니다.
kernel/irq/manage.c, kernel/irq/irqdesc.c, include/linux/interrupt.h.
Threaded IRQ 개요와 동기
전통적인 리눅스 인터럽트 처리 모델은 Top Half(hardirq)와 Bottom Half(softirq, tasklet, workqueue)로 나뉩니다. hardirq는 인터럽트를 비활성화한 상태에서 실행되므로 가능한 짧아야 하고, 나머지 작업은 Bottom Half로 위임합니다. 이 모델은 수십 년간 잘 작동했지만, 몇 가지 근본적인 한계가 있습니다.
전통 모델의 한계
| 문제 | 설명 | 영향 |
|---|---|---|
| 긴 hardirq 시간 | I2C/SPI 등 슬로우 버스(Bus) 디바이스는 hardirq에서 레지스터(Register) 접근에 수백 us 소요 | 다른 인터럽트 지연(Latency), 시스템 응답성 저하 |
| 우선순위 역전(Priority Inversion) | softirq/tasklet은 고정 우선순위, 중요도와 무관하게 실행 순서 결정 | 실시간(Real-time) 워크로드에서 예측 불가능한 지연 |
| 슬립(Sleep) 불가 | hardirq/softirq/tasklet 컨텍스트에서 슬립 가능 함수 호출 금지 | mutex, kmalloc(GFP_KERNEL), I2C 전송 등 사용 불가 |
| PREEMPT_RT 비호환 | hardirq 컨텍스트는 선점(Preemption) 불가, 실시간 스케줄링 방해 | 결정론적 지연 시간 보장 불가능 |
Threaded IRQ가 해결하는 문제
Thomas Gleixner가 설계한 Threaded IRQ 모델은 인터럽트 처리를 두 단계로 명확히 분리합니다:
- hardirq handler: 인터럽트 비활성 상태에서 최소한의 작업(ACK, 상태 읽기, mask)만 수행하고
IRQ_WAKE_THREAD를 반환 - thread_fn: 전용 커널 스레드(
irq/N-name)에서 프로세스 컨텍스트로 실행. 슬립 가능, 스케줄링 가능, 우선순위 설정 가능
역사
| 버전 | 연도 | 변경 사항 |
|---|---|---|
| 2.6.30 | 2009 | request_threaded_irq() 도입 (Thomas Gleixner) |
| 2.6.35 | 2010 | devm_request_threaded_irq() 추가 |
| 3.1 | 2011 | threadirqs 커널 cmdline 파라미터 도입 |
| 4.x | 2015+ | PREEMPT_RT 기본 force threading 통합 |
| 5.15 | 2021 | PREEMPT_RT 메인라인 머지 시작 |
| 6.x | 2023+ | PREEMPT_RT 완전 메인라인 통합 |
request_threaded_irq() API 상세
request_threaded_irq()는 threaded IRQ 등록의 핵심 함수입니다. kernel/irq/manage.c에 구현되어 있으며, 기존 request_irq()는 이 함수의 thread_fn=NULL 래퍼입니다.
함수 시그니처
/* include/linux/interrupt.h */
extern int request_threaded_irq(
unsigned int irq, /* IRQ 번호 */
irq_handler_t handler, /* hardirq 핸들러 (NULL 가능) */
irq_handler_t thread_fn, /* 스레드 함수 (NULL 가능) */
unsigned long irqflags, /* IRQF_* 플래그 */
const char *devname, /* /proc/interrupts에 표시될 이름 */
void *dev_id /* 핸들러에 전달될 인자 (공유 IRQ 시 식별자) */
);
/* handler 반환 값 */
typedef enum irqreturn {
IRQ_NONE = (0 << 0), /* 이 디바이스의 인터럽트가 아님 */
IRQ_HANDLED = (1 << 0), /* 처리 완료, 스레드 깨우지 않음 */
IRQ_WAKE_THREAD = (1 << 1), /* 스레드를 깨워서 thread_fn 실행 */
} irqreturn_t;
파라미터 상세 설명
| 파라미터 | 설명 | 주의 사항 |
|---|---|---|
irq | IRQ 번호. platform_get_irq(), gpiod_to_irq(), pci_irq_vector() 등으로 획득 | 유효하지 않은 번호 시 -EINVAL |
handler | hardirq 컨텍스트에서 실행. 빠른 ACK, 상태 확인 수행 | NULL이면 기본 핸들러(irq_default_primary_handler) 사용. IRQF_SHARED 시 NULL 불가 |
thread_fn | 커널 스레드(프로세스 컨텍스트)에서 실행. 슬립 가능 | NULL이면 일반 request_irq()와 동일. handler와 thread_fn 둘 다 NULL이면 에러 |
irqflags | IRQF_ONESHOT, IRQF_SHARED, IRQF_NO_THREAD 등 조합 | handler=NULL + IRQF_ONESHOT 미설정 시 경고/에러 |
devname | /proc/interrupts에 표시되는 이름, 스레드(Thread) 이름에도 사용 | NULL 불가, 고유한 이름 권장 |
dev_id | 핸들러에 전달되는 쿠키. IRQF_SHARED 시 고유 비NULL 값 필수 | 공유 IRQ에서 free_irq() 시 이 값으로 핸들러 식별 |
handler가 NULL인 경우
handler를 NULL로 전달하면, 커널은 자동으로 irq_default_primary_handler()를 사용합니다. 이 기본 핸들러는 단순히 IRQ_WAKE_THREAD를 반환합니다:
/* kernel/irq/manage.c */
static irqreturn_t irq_default_primary_handler(int irq, void *dev_id)
{
return IRQ_WAKE_THREAD;
}
/* 주의: handler=NULL + thread_fn만 사용할 때
* 반드시 IRQF_ONESHOT을 설정해야 합니다!
* 그렇지 않으면 커널이 경고를 출력하고 -EINVAL 반환 */
IRQF_ONESHOT을 설정해야 합니다. ONESHOT 없이 기본 핸들러를 사용하면 hardirq가 즉시 인터럽트를 재활성화하는데, thread_fn이 원인을 제거하기 전에 같은 인터럽트가 다시 발생하여 IRQ storm이 발생합니다.
기본 사용 패턴
/* 패턴 1: hardirq handler + thread_fn */
static irqreturn_t my_hardirq(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
/* 이 디바이스의 인터럽트인지 확인 (공유 IRQ 시 필수) */
if (!(ioread32(dev->regs + STATUS) & IRQ_PENDING))
return IRQ_NONE;
/* 인터럽트 ACK (하드웨어에 처리 시작 알림) */
iowrite32(IRQ_ACK, dev->regs + STATUS);
return IRQ_WAKE_THREAD;
}
static irqreturn_t my_thread_fn(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
/* 프로세스 컨텍스트: 슬립 가능! */
mutex_lock(&dev->lock);
process_data(dev);
mutex_unlock(&dev->lock);
return IRQ_HANDLED;
}
/* probe에서 등록 */
ret = request_threaded_irq(irq, my_hardirq, my_thread_fn,
IRQF_ONESHOT, "my-device", dev);
/* 패턴 2: handler=NULL (기본 핸들러 사용) */
ret = request_threaded_irq(irq, NULL, my_thread_fn,
IRQF_ONESHOT, "my-sensor", dev);
반환값과 에러 코드
| 반환값 | 의미 |
|---|---|
0 | 성공 |
-EINVAL | 잘못된 파라미터 (handler=NULL + thread_fn=NULL, ONESHOT 미설정 등) |
-EBUSY | IRQ 이미 사용 중 (비공유) |
-ENOMEM | 메모리 할당 실패 |
IRQF 플래그 상세
인터럽트 플래그는 include/linux/interrupt.h에 정의되어 있으며, threaded IRQ의 동작을 세밀하게 제어합니다. 올바른 플래그 조합은 안정적인 인터럽트 처리의 핵심입니다.
| 플래그 | 값 | 설명 | threaded IRQ 영향 |
|---|---|---|---|
IRQF_SHARED | 0x00000080 | 여러 디바이스가 같은 IRQ 라인 공유 | handler 필수 (NULL 불가). 자기 디바이스 인터럽트인지 확인 필요 |
IRQF_ONESHOT | 0x00002000 | thread_fn 완료까지 IRQ 라인 마스킹 유지 | handler=NULL 시 필수. Level-triggered에서 IRQ storm 방지 |
IRQF_NO_THREAD | 0x00010000 | force_irqthreads에서도 스레드화 금지 | 타이머(Timer), IPI 등 반드시 hardirq에서 실행해야 하는 인터럽트용 |
IRQF_TRIGGER_RISING | 0x00000001 | 상승 에지 트리거 | GPIO 인터럽트에서 주로 사용 |
IRQF_TRIGGER_FALLING | 0x00000002 | 하강 에지 트리거 | TRIGGER_RISING과 OR 조합 가능 |
IRQF_TRIGGER_HIGH | 0x00000004 | 하이 레벨 트리거 | ONESHOT 필수 (level-triggered) |
IRQF_TRIGGER_LOW | 0x00000008 | 로우 레벨 트리거 | ONESHOT 필수 (level-triggered) |
IRQF_NO_AUTOEN | 0x00004000 | 등록 후 자동 활성화 안 함 | 초기화 완료 후 수동으로 enable_irq() 호출 |
IRQF_NOBALANCING | 0x00000800 | IRQ 밸런싱에서 제외 | 특정 CPU에 고정해야 하는 인터럽트 |
IRQF_NO_SUSPEND | 0x00004000 | 시스템 서스펜드 시에도 활성 유지 | 웨이크업 소스 인터럽트 |
플래그 조합 규칙
/* 규칙 1: handler=NULL이면 ONESHOT 필수 */
request_threaded_irq(irq, NULL, thread_fn,
IRQF_ONESHOT, name, dev); /* OK */
request_threaded_irq(irq, NULL, thread_fn,
0, name, dev); /* FAIL: -EINVAL */
/* 규칙 2: IRQF_SHARED이면 handler 필수 */
request_threaded_irq(irq, my_handler, thread_fn,
IRQF_SHARED | IRQF_ONESHOT, name, dev); /* OK */
request_threaded_irq(irq, NULL, thread_fn,
IRQF_SHARED | IRQF_ONESHOT, name, dev); /* 주의: handler=NULL + SHARED 시 문제 */
/* 규칙 3: Level-triggered이면 ONESHOT 강력 권장 */
request_threaded_irq(irq, handler, thread_fn,
IRQF_TRIGGER_LOW | IRQF_ONESHOT, name, dev); /* OK */
IRQ_ONESHOT
IRQF_ONESHOT은 threaded IRQ에서 가장 중요한 플래그입니다. 이 플래그가 설정되면 hardirq handler 진입 시 인터럽트 라인이 마스킹되고, thread_fn이 완료된 후에야 언마스킹됩니다.
Level-Triggered에서 ONESHOT이 필수인 이유
Level-triggered 인터럽트는 인터럽트 라인이 활성 상태를 유지하는 한 계속 인터럽트를 발생시킵니다. 디바이스가 인터럽트를 발생시키면 CPU가 이를 감지하고 핸들러를 실행하지만, 핸들러가 끝나고 인터럽트가 재활성화되는 시점에 디바이스가 여전히 인터럽트 라인을 assert하고 있으면 즉시 다시 인터럽트가 발생합니다.
Threaded IRQ에서 hardirq handler는 단순히 IRQ_WAKE_THREAD만 반환하고, 실제 인터럽트 원인 제거(데이터 읽기, 상태 클리어 등)는 thread_fn에서 합니다. 따라서 ONESHOT 없이는:
- hardirq handler 실행 (IRQ_WAKE_THREAD 반환)
- 인터럽트 재활성화 (thread_fn 시작 전!)
- 디바이스가 여전히 assert 중 -> 즉시 재발생
- 1-3 반복 -> IRQ storm
Edge-Triggered에서의 ONESHOT
Edge-triggered 인터럽트에서는 ONESHOT이 필수는 아닙니다. 에지 트리거는 신호 전이(상승/하강) 시에만 인터럽트가 발생하므로, 라인이 활성 상태를 유지해도 반복 발생하지 않습니다. 그러나 thread_fn 실행 중에 발생한 새 인터럽트를 놓치지 않기 위해 ONESHOT을 사용하는 것이 안전한 관행입니다.
irq_thread 커널 스레드 내부
threaded IRQ를 등록하면 커널은 전용 커널 스레드를 생성합니다. 이 스레드의 메인 루프와 스케줄링 특성을 분석합니다.
스레드 생성 과정
/* kernel/irq/manage.c — __setup_irq() 내부 */
if (new->thread_fn && !nested) {
struct task_struct *t;
t = kthread_create(irq_thread, new,
"irq/%d-%s", irq, new->name);
if (IS_ERR(t))
return PTR_ERR(t);
/* RT 우선순위 50으로 설정 (SCHED_FIFO) */
sched_set_fifo(t);
/* IRQ affinity와 동일하게 스레드 affinity 설정 */
get_task_struct(t);
new->thread = t;
set_bit(IRQTF_AFFINITY, &new->thread_flags);
}
스레드 이름 규칙
생성된 스레드는 irq/N-name 형식의 이름을 가집니다:
$ ps -eo pid,cls,rtprio,comm | grep irq/
85 FF 50 irq/9-acpi
132 FF 50 irq/16-ahci
207 FF 50 irq/27-i2c-touch
341 FF 50 irq/136-xhci_hcd
irq_thread() 메인 루프
/* kernel/irq/manage.c — irq_thread() */
static int irq_thread(void *data)
{
struct irqaction *action = data;
struct irq_desc *desc = irq_to_desc(action->irq);
irq_thread_check_affinity(desc, action);
while (!irq_wait_for_interrupt(action)) {
irqreturn_t action_ret;
irq_thread_check_affinity(desc, action);
action_ret = irq_thread_fn(desc, action);
if (action_ret == IRQ_HANDLED)
atomic_inc(&desc->threads_handled);
wake_threads_waitq(desc);
}
return 0;
}
/* irq_wait_for_interrupt(): 스레드를 재운 뒤 IRQTF_RUNTHREAD가
* 설정될 때까지 대기. hardirq handler가 IRQ_WAKE_THREAD를 반환하면
* 이 플래그를 설정하고 스레드를 깨움 */
irq_thread_fn() 래퍼 동작
/* kernel/irq/manage.c */
static irqreturn_t irq_thread_fn(struct irq_desc *desc,
struct irqaction *action)
{
irqreturn_t ret;
ret = action->thread_fn(action->irq, action->dev_id);
if (ret == IRQ_HANDLED)
atomic_inc(&desc->threads_handled);
irq_finalize_oneshot(desc, action);
return ret;
}
/* irq_finalize_oneshot()가 ONESHOT 인터럽트의 unmask를 담당 */
force_irqthreads 메커니즘
force_irqthreads는 기존 request_irq()로 등록된 인터럽트도 강제로 스레드화하는 메커니즘입니다. PREEMPT_RT 커널의 핵심 기능이며, 비RT 커널에서도 threadirqs 부트 파라미터로 활성화할 수 있습니다.
활성화 방법
# 방법 1: 커널 cmdline 파라미터
GRUB_CMDLINE_LINUX="threadirqs"
# 방법 2: PREEMPT_RT 커널 (기본 활성화)
# CONFIG_PREEMPT_RT=y이면 force_irqthreads=true
# 확인 방법
cat /proc/cmdline | grep threadirqs
cat /sys/kernel/realtime # 1이면 RT 커널
내부 동작: irq_setup_forced_threading()
/* kernel/irq/manage.c */
static void irq_setup_forced_threading(struct irqaction *new)
{
if (!force_irqthreads)
return;
/* IRQF_NO_THREAD가 설정된 인터럽트는 스레드화하지 않음 */
if (new->flags & IRQF_NO_THREAD)
return;
/* 이미 threaded_irq이면 건너뜀 */
if (new->handler == irq_default_primary_handler)
return;
/* handler를 thread_fn으로 이동하고,
* handler를 irq_default_primary_handler로 교체 */
new->thread_fn = new->handler;
new->handler = irq_default_primary_handler;
/* ONESHOT 설정 (안전한 unmask 보장) */
new->flags |= IRQF_ONESHOT;
}
스레드화에서 제외되는 인터럽트
| 인터럽트 유형 | 플래그 | 제외 이유 |
|---|---|---|
| 타이머 인터럽트 | IRQF_NO_THREAD | 스케줄러(Scheduler) tick, 시간 유지에 필수 |
| IPI (Inter-Processor Interrupt) | IRQF_NO_THREAD | CPU간 즉각 통신 필요 |
| NMI | 별도 경로 | 마스킹 불가 인터럽트 |
| IRQF_PERCPU | 내부 로직 | per-CPU 인터럽트는 이미 경합(Contention) 없음 |
성능 영향
Managed IRQ (devm API)
devm_request_threaded_irq()는 디바이스 리소스 관리(devres) 프레임워크와 통합된 IRQ 등록 API입니다. 드라이버 probe에서 등록하면 remove 시 자동으로 해제됩니다.
API 시그니처
/* include/linux/interrupt.h */
extern int devm_request_threaded_irq(
struct device *dev, /* 디바이스 구조체 */
unsigned int irq,
irq_handler_t handler,
irq_handler_t thread_fn,
unsigned long irqflags,
const char *devname,
void *dev_id
);
/* 편의 래퍼: thread_fn=NULL */
static inline int devm_request_irq(
struct device *dev, unsigned int irq,
irq_handler_t handler, unsigned long irqflags,
const char *devname, void *dev_id)
{
return devm_request_threaded_irq(dev, irq, handler,
NULL, irqflags, devname, dev_id);
}
자동 해제 순서
devres는 LIFO(Last-In-First-Out) 순서로 리소스를 해제합니다. 따라서 probe에서 IRQ를 먼저 등록하고 다른 리소스를 나중에 등록하면, remove 시 다른 리소스가 먼저 해제된 후 IRQ가 해제됩니다:
static int my_probe(struct platform_device *pdev)
{
struct my_device *dev;
int irq, ret;
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
/* devm로 등록: remove 시 자동 해제 */
ret = devm_request_threaded_irq(&pdev->dev, irq,
my_hardirq, my_thread_fn,
IRQF_ONESHOT,
dev_name(&pdev->dev), dev);
if (ret)
return ret;
/* 추가 초기화 (devm 기반이므로 에러 시 자동 정리) */
dev->regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(dev->regs))
return PTR_ERR(dev->regs); /* IRQ도 자동 해제됨 */
platform_set_drvdata(pdev, dev);
return 0;
}
/* remove 함수: devm 사용 시 비어있을 수 있음 */
static void my_remove(struct platform_device *pdev)
{
/* devm이 모든 리소스를 자동 해제 */
}
devm_request_threaded_irq()를 사용하세요. 에러 경로에서의 수동 정리 코드를 제거하고, 리소스 누수 버그를 방지합니다. 기존 드라이버의 request_irq()/free_irq() 쌍도 가능하면 devm으로 전환하는 것을 권장합니다.
공유 인터럽트에서의 Threaded IRQ
PCI 레거시 인터럽트(INTx)는 여러 디바이스가 같은 IRQ 라인을 공유합니다. 공유 인터럽트에서 threaded IRQ를 사용할 때는 추가 규칙이 적용됩니다.
핵심 규칙
- handler 필수: IRQF_SHARED에서 handler=NULL은 허용되지 않습니다. 자기 디바이스의 인터럽트인지 확인해야 하기 때문입니다.
- IRQ_NONE 반환: 자기 디바이스의 인터럽트가 아니면 반드시 IRQ_NONE을 반환해야 합니다.
- ONESHOT 주의: 공유 IRQ에서 ONESHOT을 사용하면, 한 핸들러의 thread_fn이 완료될 때까지 같은 라인의 다른 디바이스 인터럽트도 지연됩니다.
- thread_fn 병렬 실행: 여러 핸들러가 IRQ_WAKE_THREAD를 반환하면, 각각의 thread_fn은 독립적으로 스케줄링되어 병렬 실행 가능합니다.
/* 공유 인터럽트 threaded IRQ 예제 */
static irqreturn_t shared_hardirq(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
u32 status = ioread32(dev->regs + IRQ_STATUS);
/* 자기 디바이스의 인터럽트가 아니면 IRQ_NONE */
if (!(status & MY_IRQ_PENDING))
return IRQ_NONE;
/* 인터럽트 ACK (다른 디바이스에 영향 없도록) */
iowrite32(MY_IRQ_ACK, dev->regs + IRQ_STATUS);
/* 상태 저장 후 스레드로 위임 */
dev->irq_status = status;
return IRQ_WAKE_THREAD;
}
static irqreturn_t shared_thread_fn(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
mutex_lock(&dev->lock);
handle_interrupt(dev, dev->irq_status);
mutex_unlock(&dev->lock);
return IRQ_HANDLED;
}
ret = devm_request_threaded_irq(&pdev->dev, irq,
shared_hardirq, shared_thread_fn,
IRQF_SHARED | IRQF_ONESHOT,
"my-pci-dev", dev);
Nested Threaded IRQ
Nested threaded IRQ는 I2C/SPI와 같은 슬로우 버스에 연결된 디바이스의 인터럽트를 처리하는 특수한 패턴입니다. 이 경우 부모 IRQ 컨트롤러 자체가 이미 threaded IRQ로 동작하므로, 자식 IRQ도 부모의 스레드 컨텍스트에서 실행됩니다.
Nested IRQ의 동작 원리
GPIO expander가 I2C 버스에 연결된 경우를 예로 들면:
- GPIO expander의 인터럽트 출력이 SoC의 GPIO 핀에 연결
- SoC GPIO 인터럽트는 threaded IRQ로 처리 (I2C 접근이 필요하므로)
- threaded handler 내에서 I2C를 통해 expander의 인터럽트 상태를 읽음
handle_nested_irq()를 호출하여 자식 IRQ를 부모 스레드에서 처리
/* I2C GPIO expander의 threaded handler (부모) */
static irqreturn_t expander_irq_thread(int irq, void *dev_id)
{
struct expander *exp = dev_id;
unsigned long pending;
int child_irq;
/* I2C로 인터럽트 상태 레지스터 읽기 (슬립 가능) */
pending = i2c_smbus_read_byte_data(exp->client, REG_INT_STATUS);
for_each_set_bit(child_irq, &pending, exp->ngpio) {
/* 자식 IRQ를 부모 스레드 컨텍스트에서 직접 호출 */
handle_nested_irq(irq_find_mapping(exp->domain, child_irq));
}
return IRQ_HANDLED;
}
/* 자식 IRQ의 nested 속성 설정 */
static void expander_irq_setup(struct expander *exp)
{
int i;
for (i = 0; i < exp->ngpio; i++) {
int virq = irq_create_mapping(exp->domain, i);
irq_set_nested_thread(virq, 1);
irq_set_noprobe(virq);
}
}
Threaded IRQ vs 다른 Bottom Half 비교
인터럽트 처리의 지연 작업(deferred work)을 구현하는 여러 메커니즘이 있습니다. 각각의 특성과 threaded IRQ가 최선인 상황을 비교합니다.
| 기준 | Threaded IRQ | Workqueue | Tasklet | Softirq |
|---|---|---|---|---|
| 실행 컨텍스트 | 프로세스 (커널 스레드) | 프로세스 (kworker) | Softirq (인터럽트) | Softirq (인터럽트) |
| 슬립 가능 | 가능 | 가능 | 불가 | 불가 |
| 우선순위 제어 | RT 우선순위 (chrt) | nice 또는 WQ_HIGHPRI | 불가 (고정) | 불가 (고정) |
| PREEMPT_RT 호환 | 완벽 | 양호 | 제거 예정 | 스레드화됨 |
| 지연 시간 | 스레드 웨이크업 (2~5us) | kworker 스케줄링 | softirq 지연 | 즉시 (softirq) |
| IRQ와의 결합 | 자동 (request_threaded_irq) | 수동 (queue_work) | 수동 (tasklet_schedule) | 수동 (raise_softirq) |
| 구현 복잡도 | 낮음 (API가 통합) | 중간 | 낮음 | 높음 |
결정 기준
- 인터럽트 처리에서 슬립이 필요한 경우 (I2C/SPI 전송, mutex, GFP_KERNEL 할당)
- 인터럽트 처리의 RT 우선순위를 제어해야 할 경우
- PREEMPT_RT 커널 호환이 필요한 경우
- 기존 hardirq + workqueue 패턴을 단순화하고 싶은 경우
- 인터럽트별 전용 스레드가 필요한 경우 (workqueue는 공유 스레드풀)
PREEMPT_RT에서의 Threaded IRQ
PREEMPT_RT 커널에서 threaded IRQ는 핵심적인 역할을 합니다. RT 커널은 force_irqthreads=true를 기본으로 설정하여, IRQF_NO_THREAD가 아닌 모든 인터럽트를 스레드화합니다.
RT 커널에서의 인터럽트 처리 구조
우선순위 역전 방지와 PI
RT 커널에서 spinlock_t는 rt_mutex로 변환되며, rt_mutex는 Priority Inheritance(PI)를 지원합니다. IRQ 스레드가 rt_mutex를 대기할 때, 보유자의 우선순위가 자동으로 부스팅되어 우선순위 역전을 방지합니다.
# RT 커널에서 IRQ 스레드 우선순위 확인 및 변경
$ ps -eo pid,cls,rtprio,comm | grep "irq/"
85 FF 50 irq/9-acpi
132 FF 50 irq/16-ahci
207 FF 50 irq/27-i2c-touch
# 중요한 인터럽트의 우선순위 올리기
$ chrt -f -p 80 207
# IRQ affinity 설정 (특정 CPU에 바인딩)
$ echo 2 > /proc/irq/27/smp_affinity
# cyclictest로 인터럽트 지연 측정
$ cyclictest -t1 -p 98 -i 1000 -l 10000
T: 0 Min: 1 Act: 1 Avg: 1 Max: 12
RT 커널 튜닝 가이드
| 항목 | 설정 | 효과 |
|---|---|---|
| IRQ 스레드 우선순위 | chrt -f -p PRIO PID | 중요한 인터럽트 우선 처리 |
| IRQ affinity | /proc/irq/N/smp_affinity | 특정 CPU에 바인딩으로 캐시(Cache) 효율 향상 |
| CPU 격리(Isolation) | isolcpus=2,3 | RT 전용 CPU 확보 |
| 커널 타이머 | nohz_full=2,3 | 격리된 CPU에서 tick 제거 |
| RCU 콜백(Callback) | rcu_nocbs=2,3 | RCU 콜백 오프로딩(Offloading) |
기존 드라이버 마이그레이션 가이드
기존 드라이버를 threaded IRQ 패턴으로 전환하는 세 가지 주요 마이그레이션 시나리오를 다룹니다.
패턴 1: request_irq() -> request_threaded_irq()
/* === 변경 전: hardirq에서 모든 작업 수행 === */
static irqreturn_t old_handler(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
u32 status = ioread32(dev->regs + STATUS);
if (!(status & IRQ_PENDING))
return IRQ_NONE;
iowrite32(IRQ_ACK, dev->regs + STATUS);
process_data(dev); /* 시간이 걸리는 작업 */
return IRQ_HANDLED;
}
request_irq(irq, old_handler, 0, "my-dev", dev);
/* === 변경 후: hardirq + thread_fn 분리 === */
static irqreturn_t new_hardirq(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
u32 status = ioread32(dev->regs + STATUS);
if (!(status & IRQ_PENDING))
return IRQ_NONE;
iowrite32(IRQ_ACK, dev->regs + STATUS);
dev->saved_status = status;
return IRQ_WAKE_THREAD;
}
static irqreturn_t new_thread_fn(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
process_data(dev);
return IRQ_HANDLED;
}
request_threaded_irq(irq, new_hardirq, new_thread_fn,
IRQF_ONESHOT, "my-dev", dev);
패턴 2: hardirq + workqueue -> Threaded IRQ
/* === 변경 전: hardirq에서 workqueue 스케줄링 === */
static irqreturn_t old_handler(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
iowrite32(IRQ_ACK, dev->regs + STATUS);
queue_work(dev->wq, &dev->work); /* workqueue에 위임 */
return IRQ_HANDLED;
}
static void old_work_fn(struct work_struct *work)
{
struct my_device *dev = container_of(work, struct my_device, work);
mutex_lock(&dev->lock);
slow_operation(dev);
mutex_unlock(&dev->lock);
}
/* === 변경 후: 단일 threaded IRQ로 통합 === */
static irqreturn_t new_hardirq(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
iowrite32(IRQ_ACK, dev->regs + STATUS);
return IRQ_WAKE_THREAD;
}
static irqreturn_t new_thread_fn(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
mutex_lock(&dev->lock);
slow_operation(dev);
mutex_unlock(&dev->lock);
return IRQ_HANDLED;
}
/* workqueue, work_struct, INIT_WORK 모두 제거 가능 */
패턴 3: Tasklet -> Threaded IRQ
/* === 변경 전: tasklet 사용 === */
static void old_tasklet_fn(struct tasklet_struct *t)
{
struct my_device *dev = from_tasklet(dev, t, tasklet);
process_data(dev); /* 주의: 슬립 불가! */
}
static irqreturn_t old_handler(int irq, void *dev_id)
{
iowrite32(IRQ_ACK, dev->regs + STATUS);
tasklet_schedule(&dev->tasklet);
return IRQ_HANDLED;
}
/* === 변경 후: threaded IRQ (tasklet, DECLARE_TASKLET 제거) === */
static irqreturn_t new_thread_fn(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
process_data(dev); /* 이제 슬립 가능! */
return IRQ_HANDLED;
}
마이그레이션 체크리스트
| # | 확인 항목 | 설명 |
|---|---|---|
| 1 | hardirq handler에서 슬립 함수 호출이 없는가? | ACK, 상태 읽기, 마스킹만 수행 |
| 2 | IRQF_ONESHOT이 설정되어 있는가? | handler=NULL 시 필수, level-triggered 시 강력 권장 |
| 3 | 공유 IRQ에서 handler가 자기 디바이스를 확인하는가? | IRQ_NONE 반환 경로 확인 |
| 4 | hardirq handler와 thread_fn 간 데이터 동기화가 안전한가? | volatile/atomic 또는 spin_lock 사용 |
| 5 | devm API로 전환했는가? | 에러 경로 자동 정리 |
| 6 | workqueue/tasklet 관련 코드를 제거했는가? | 불필요한 구조체(Struct), 초기화 코드 정리 |
| 7 | PREEMPT_RT에서 테스트했는가? | force_irqthreads 환경에서 검증 |
I2C/SPI 디바이스 드라이버 실전
I2C/SPI 디바이스는 threaded IRQ의 가장 대표적인 사용 사례입니다. 슬로우 버스를 통한 레지스터 접근은 수백 us에서 수 ms가 소요되므로 hardirq에서 처리할 수 없습니다.
regmap_irq: 자동 IRQ chip 생성
regmap_irq 프레임워크는 레지스터 기반 인터럽트 컨트롤러(Interrupt Controller)를 자동으로 구성합니다. PMIC, 코덱, 센서 허브 등 복잡한 인터럽트 구조를 가진 디바이스에 매우 유용합니다.
/* PMIC 드라이버의 regmap_irq 설정 예 */
static const struct regmap_irq pmic_irqs[] = {
REGMAP_IRQ_REG(0, 0, BIT(0)), /* VBUS detect */
REGMAP_IRQ_REG(1, 0, BIT(1)), /* Overtemp */
REGMAP_IRQ_REG(2, 0, BIT(2)), /* Low battery */
REGMAP_IRQ_REG(3, 1, BIT(0)), /* Button press */
};
static const struct regmap_irq_chip pmic_irq_chip = {
.name = "pmic",
.irqs = pmic_irqs,
.num_irqs = ARRAY_SIZE(pmic_irqs),
.num_regs = 2,
.status_base = PMIC_INT_STATUS,
.mask_base = PMIC_INT_MASK,
.ack_base = PMIC_INT_ACK,
};
static int pmic_probe(struct i2c_client *client)
{
struct pmic_data *pmic;
int ret;
pmic->regmap = devm_regmap_init_i2c(client, &pmic_regmap_config);
/* regmap_irq가 threaded IRQ를 자동 등록 */
ret = devm_regmap_add_irq_chip(&client->dev, pmic->regmap,
client->irq,
IRQF_ONESHOT | IRQF_TRIGGER_LOW,
0, &pmic_irq_chip,
&pmic->irq_data);
if (ret)
return ret;
/* 개별 IRQ를 자식 디바이스에 전달 */
pmic->charger_irq = regmap_irq_get_virq(pmic->irq_data, 0);
pmic->thermal_irq = regmap_irq_get_virq(pmic->irq_data, 1);
return 0;
}
터치스크린 드라이버 예제
/* I2C 터치스크린: handler=NULL, thread_fn만 사용 */
static irqreturn_t touch_thread_fn(int irq, void *dev_id)
{
struct touch_device *ts = dev_id;
u8 buf[10];
/* I2C 전송 (슬립 가능, 수백 us) */
i2c_smbus_read_i2c_block_data(ts->client, REG_TOUCH, 10, buf);
input_report_abs(ts->input, ABS_X, get_x(buf));
input_report_abs(ts->input, ABS_Y, get_y(buf));
input_report_key(ts->input, BTN_TOUCH, get_touch(buf));
input_sync(ts->input);
return IRQ_HANDLED;
}
ret = devm_request_threaded_irq(&client->dev, client->irq,
NULL, touch_thread_fn,
IRQF_ONESHOT | IRQF_TRIGGER_LOW,
"touch-irq", ts);
GPIO Interrupt와 Threaded IRQ
GPIO 인터럽트는 임베디드 시스템에서 버튼, 센서, 외부 디바이스 연결에 광범위하게 사용됩니다. GPIO subsystem과 threaded IRQ의 결합 패턴을 살펴봅니다.
/* GPIO 인터럽트 + threaded IRQ 예제 */
static int sensor_probe(struct platform_device *pdev)
{
struct sensor_data *data;
struct gpio_desc *irq_gpio;
int irq, ret;
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
/* GPIO 디스크립터 가져오기 */
irq_gpio = devm_gpiod_get(&pdev->dev, "irq", GPIOD_IN);
if (IS_ERR(irq_gpio))
return PTR_ERR(irq_gpio);
/* GPIO -> IRQ 번호 변환 */
irq = gpiod_to_irq(irq_gpio);
if (irq < 0)
return irq;
/* threaded IRQ 등록 (하강 에지에서 트리거) */
ret = devm_request_threaded_irq(&pdev->dev, irq,
NULL, sensor_irq_thread,
IRQF_ONESHOT | IRQF_TRIGGER_FALLING,
"sensor-data-ready", data);
if (ret)
return ret;
data->irq = irq;
platform_set_drvdata(pdev, data);
return 0;
}
static irqreturn_t sensor_irq_thread(int irq, void *dev_id)
{
struct sensor_data *data = dev_id;
int val;
/* 프로세스 컨텍스트: I2C/SPI 접근 가능 */
val = i2c_smbus_read_word_data(data->client, REG_DATA);
if (val < 0)
return IRQ_HANDLED;
data->last_value = val;
complete(&data->data_ready);
return IRQ_HANDLED;
}
Debounce와 Threaded IRQ
GPIO 버튼의 바운싱 문제는 소프트웨어 디바운싱으로 해결하며, threaded IRQ에서 msleep()을 사용하여 안정화 시간을 기다릴 수 있습니다:
static irqreturn_t button_thread_fn(int irq, void *dev_id)
{
struct button_data *btn = dev_id;
/* 디바운스: 20ms 대기 후 상태 확인 */
msleep(20);
int state = gpiod_get_value_cansleep(btn->gpio);
input_report_key(btn->input, btn->keycode, state);
input_sync(btn->input);
return IRQ_HANDLED;
}
PCIe 디바이스의 Threaded IRQ
PCIe 디바이스에서 threaded IRQ를 사용하는 패턴은 MSI/MSI-X 사용 여부에 따라 달라집니다.
MSI/MSI-X + Threaded IRQ
static int pci_dev_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
struct my_pci_dev *dev;
int ret;
ret = pcim_enable_device(pdev);
if (ret)
return ret;
/* MSI 벡터 할당 */
ret = pci_alloc_irq_vectors(pdev, 1, 4, PCI_IRQ_MSI | PCI_IRQ_MSIX);
if (ret < 0)
return ret;
/* MSI는 edge-triggered이므로 ONESHOT 불필요하지만
* 안전을 위해 설정하는 것도 괜찮음 */
ret = devm_request_threaded_irq(&pdev->dev,
pci_irq_vector(pdev, 0),
my_pci_hardirq, my_pci_thread,
0, /* MSI: ONESHOT 선택적, SHARED 불필요 */
"my-pci", dev);
return ret;
}
static irqreturn_t my_pci_hardirq(int irq, void *dev_id)
{
struct my_pci_dev *dev = dev_id;
/* DMA 완료 상태 확인 */
if (!(ioread32(dev->mmio + DMA_STATUS) & DMA_DONE))
return IRQ_NONE;
/* 인터럽트 비활성화 (재발생 방지) */
iowrite32(0, dev->mmio + IRQ_ENABLE);
return IRQ_WAKE_THREAD;
}
static irqreturn_t my_pci_thread(int irq, void *dev_id)
{
struct my_pci_dev *dev = dev_id;
/* DMA 버퍼 처리 (프로세스 컨텍스트) */
process_dma_buffer(dev);
/* 인터럽트 재활성화 */
iowrite32(IRQ_ENABLE_ALL, dev->mmio + IRQ_ENABLE);
return IRQ_HANDLED;
}
PCI INTx 공유 인터럽트
레거시 PCI INTx를 사용하는 경우, 반드시 IRQF_SHARED를 설정하고 handler에서 자기 디바이스의 인터럽트인지 확인해야 합니다. MSI/MSI-X로 전환하면 이 제약이 없어집니다.
동기화와 잠금(Lock) 패턴
threaded IRQ에서는 hardirq handler와 thread_fn이 서로 다른 컨텍스트에서 실행되므로, 공유 데이터 접근 시 적절한 동기화가 필요합니다.
hardirq handler와 thread_fn 간 동기화
| 시나리오 | 동기화 방법 | 설명 |
|---|---|---|
| hardirq -> thread_fn 데이터 전달 | 구조체 필드 + WRITE_ONCE/READ_ONCE | hardirq에서 저장, thread_fn에서 읽기 (순서 보장(Ordering)) |
| thread_fn 간 공유 데이터 | mutex | thread_fn은 프로세스 컨텍스트이므로 mutex 사용 가능 |
| hardirq + thread_fn 동시 접근 | spin_lock_irqsave() | hardirq 컨텍스트에서도 안전 |
| 카운터 업데이트 | atomic_t | 락 없이 원자적 연산(Atomic Operation) |
synchronize_irq() vs disable_irq()
/* synchronize_irq(): 현재 실행 중인 handler + thread_fn 완료 대기 */
synchronize_irq(dev->irq);
/* 이 시점에서 handler와 thread_fn이 실행 중이지 않음을 보장 */
cleanup_resources(dev);
/* disable_irq(): IRQ 비활성화 + 실행 중인 handler/thread_fn 완료 대기 */
disable_irq(dev->irq);
/* IRQ 비활성화됨 + 진행 중이던 처리 완료됨 */
modify_shared_data(dev);
enable_irq(dev->irq);
/* disable_irq_nosync(): IRQ만 비활성화, 완료 대기 안 함 */
disable_irq_nosync(dev->irq); /* hardirq에서 사용 가능 */
데드락 시나리오와 해결
disable_irq()를 호출하고, hardirq handler가 같은 mutex를 시도하면 데드락이 발생합니다. hardirq handler에서는 mutex를 사용하지 마세요 (spin_lock만 가능).
디버깅(Debugging) 기법
threaded IRQ 관련 문제를 진단하는 실전 기법입니다.
/proc/interrupts 해석
$ cat /proc/interrupts
CPU0 CPU1
9: 45 0 IO-APIC 9-fasteoi acpi
16: 12847 0 IO-APIC 16-fasteoi ahci[0000:00:1f.2]
27: 8923 0 IO-APIC 27-fasteoi i2c-touch
# 컬럼: IRQ번호 / CPU별 카운트 / 컨트롤러 / 트리거+타입 / 이름
# fasteoi: ACK 방식 (irq chip 의존)
# 카운트가 빠르게 증가하면 IRQ storm 의심
/proc/irq/N/ 디렉토리
$ ls /proc/irq/27/
affinity_hint effective_affinity smp_affinity
actions effective_affinity_list smp_affinity_list
chip_name hwirq spurious
node type
$ cat /proc/irq/27/spurious
count 0
unhandled 0
last_unhandled 0 ms
$ cat /proc/irq/27/actions
i2c-touch
$ cat /proc/irq/27/type
edge
ftrace를 이용한 추적
# IRQ 핸들러 진입/종료 추적
$ echo 1 > /sys/kernel/debug/tracing/events/irq/irq_handler_entry/enable
$ echo 1 > /sys/kernel/debug/tracing/events/irq/irq_handler_exit/enable
# IRQ 스레드 추적 (available_events에서 확인)
$ cat /sys/kernel/debug/tracing/trace
# irq/27-i2c-to-207 [001] d.. 123.456: irq_handler_entry: irq=27 name=i2c-touch
# irq/27-i2c-to-207 [001] d.. 123.456: irq_handler_exit: irq=27 ret=handled
# function_graph로 상세 호출 추적
$ echo irq_thread_fn > /sys/kernel/debug/tracing/set_graph_function
$ echo function_graph > /sys/kernel/debug/tracing/current_tracer
일반적인 오류와 해결
| 증상 | 원인 | 해결 |
|---|---|---|
| IRQ storm (카운트 폭증) | Level-triggered + ONESHOT 미설정 | IRQF_ONESHOT 추가 |
| "nobody cared" 메시지 | 공유 IRQ에서 모든 handler가 IRQ_NONE 반환 | handler에서 자기 디바이스 인터럽트 확인 로직 점검 |
| "irq N: nobody cared (try booting with irqpoll)" | Spurious 인터럽트 100,000회 초과 | 하드웨어 ACK 로직 확인, IRQ 라인 연결 점검 |
| thread_fn이 실행되지 않음 | handler가 IRQ_HANDLED만 반환 | handler에서 IRQ_WAKE_THREAD 반환하도록 수정 |
| -EINVAL 반환 | handler=NULL + ONESHOT 미설정 | IRQF_ONESHOT 추가 또는 handler 구현 |
| 시스템 응답 지연 | thread_fn 처리 시간이 너무 김 | thread_fn 최적화, 또는 작업을 workqueue로 추가 분할 |
성능 분석
Threaded IRQ의 오버헤드와 성능 특성을 정량적으로 분석합니다.
오버헤드 구성 요소
| 단계 | 지연 시간 (일반) | 지연 시간 (RT) | 설명 |
|---|---|---|---|
| 하드웨어 인터럽트 전달 | ~1us | ~1us | 인터럽트 컨트롤러 -> CPU |
| hardirq handler 실행 | 1~5us | 1~5us | ACK + IRQ_WAKE_THREAD |
| 스레드 웨이크업 | 2~5us | 2~5us | wake_up_process() + 스케줄링 |
| 컨텍스트 스위칭(Context Switching) | 1~3us | 1~3us | 현재 태스크(Task) -> irq 스레드 |
| thread_fn 시작 | 4~13us (총합) | 4~13us (총합) | 인터럽트 발생부터 thread_fn 진입까지 |
cyclictest 성능 비교
# 비RT 커널 (threadirqs 미사용)
$ cyclictest -t4 -p 98 -i 1000 -l 100000
T: 0 Min: 1 Act: 2 Avg: 3 Max: 87
# 비RT 커널 (threadirqs 사용)
$ cyclictest -t4 -p 98 -i 1000 -l 100000
T: 0 Min: 1 Act: 2 Avg: 4 Max: 42
# PREEMPT_RT 커널
$ cyclictest -t4 -p 98 -i 1000 -l 100000
T: 0 Min: 1 Act: 1 Avg: 2 Max: 12
커널 설정 옵션
Threaded IRQ와 관련된 주요 커널 설정 옵션들입니다.
| 설정 | 기본값 | 설명 |
|---|---|---|
CONFIG_IRQ_FORCED_THREADING | y (대부분 distro) | force_irqthreads 기능 활성화 (threadirqs cmdline 지원) |
CONFIG_PREEMPT_RT | n | 완전한 RT 선점 모델, force_irqthreads 기본 활성화 |
CONFIG_PREEMPT_DYNAMIC | y (6.x) | 런타임에 선점 모델 전환 가능 |
CONFIG_GENERIC_IRQ_DEBUGFS | n | /sys/kernel/debug/irq/ 디버그 인터페이스 |
CONFIG_DEBUG_SHIRQ | n | 공유 IRQ 디버깅 (free_irq 시 가짜 인터럽트 발생) |
CONFIG_PROVE_LOCKING | n | lockdep: 잠금 의존성 검증 (IRQ 안전성 포함) |
threadirqs 커널 cmdline 파라미터
# GRUB 설정에서 추가
GRUB_CMDLINE_LINUX="threadirqs"
# 또는 부팅 시 GRUB 편집 (임시)
linux /vmlinuz ... threadirqs
# 적용 확인
$ dmesg | grep -i thread
[ 0.000000] Forcing IRQ threading
권장 설정 매트릭스
| 환경 | CONFIG_PREEMPT_RT | threadirqs | IRQ 스레드 우선순위 조정 | 비고 |
|---|---|---|---|---|
| 일반 서버 | n | n | 불필요 | 기본 설정, 처리량(Throughput) 우선 |
| 지연 민감 서버 | n | y | 선택적 | 최악의 지연 감소 |
| 임베디드 (비RT) | n | y | 권장 | I2C/SPI 디바이스 안정성 |
| 임베디드 (RT) | y | 자동 | 필수 | 실시간 응답 보장 |
| 산업 제어 | y | 자동 | 필수 | 결정론적 응답 시간 |
| 오디오 워크스테이션 | y | 자동 | 권장 | 오디오 언더런 방지 |
내부 구현: __setup_irq() 핵심 경로
request_threaded_irq()가 호출되면 내부적으로 __setup_irq()가 실행됩니다. 이 함수의 threaded IRQ 관련 핵심 경로를 분석합니다.
/* kernel/irq/manage.c — request_threaded_irq() 핵심 흐름 */
int request_threaded_irq(unsigned int irq,
irq_handler_t handler, irq_handler_t thread_fn,
unsigned long irqflags, const char *devname, void *dev_id)
{
struct irqaction *action;
struct irq_desc *desc;
desc = irq_to_desc(irq);
if (!desc)
return -EINVAL;
/* handler와 thread_fn 둘 다 NULL이면 에러 */
if (!handler && !thread_fn)
return -EINVAL;
/* handler=NULL이면 기본 핸들러 사용 */
if (!handler) {
if (!thread_fn)
return -EINVAL;
handler = irq_default_primary_handler;
}
/* irqaction 구조체 할당 및 초기화 */
action = kzalloc(sizeof(*action), GFP_KERNEL);
action->handler = handler;
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;
/* 핵심: __setup_irq()에서 스레드 생성, 검증, 등록 */
return __setup_irq(irq, desc, action);
}
핵심 자료 구조
Threaded IRQ를 이해하려면 관련 자료 구조의 관계를 파악해야 합니다.
/* include/linux/irqdesc.h */
struct irq_desc {
struct irqaction *action; /* 핸들러 체인 (linked list) */
struct irq_data irq_data; /* IRQ chip, hwirq 등 */
unsigned int irq_count; /* 인터럽트 발생 횟수 */
unsigned int depth; /* enable/disable 중첩 카운트 */
unsigned int threads_handled; /* 스레드 처리 완료 횟수 */
unsigned int threads_active; /* 현재 활성 스레드 수 */
wait_queue_head_t wait_for_threads; /* synchronize_irq() 대기 */
/* ... */
};
/* include/linux/interrupt.h */
struct irqaction {
irq_handler_t handler; /* hardirq 핸들러 */
irq_handler_t thread_fn; /* 스레드 함수 */
struct task_struct *thread; /* 커널 스레드 task */
unsigned long thread_flags; /* IRQTF_RUNTHREAD 등 */
unsigned long thread_mask; /* ONESHOT sync 마스크 */
void *dev_id; /* 디바이스 식별자 */
unsigned int irq; /* IRQ 번호 */
unsigned int flags; /* IRQF_* 플래그 */
const char *name; /* 이름 */
struct irqaction *next; /* 공유 IRQ: 다음 action */
};
ONESHOT 구현 상세: irq_finalize_oneshot()
ONESHOT 인터럽트의 unmask 타이밍은 irq_finalize_oneshot() 함수가 제어합니다. 공유 IRQ에서 여러 thread_fn이 모두 완료되어야 unmask하는 로직을 분석합니다.
/* kernel/irq/manage.c */
static void irq_finalize_oneshot(struct irq_desc *desc,
struct irqaction *action)
{
if (!(desc->istate & IRQS_ONESHOT) ||
action->handler == irq_forced_secondary_handler)
return;
again:
chip_bus_lock(desc);
raw_spin_lock_irq(&desc->lock);
/* thread_mask에서 자신의 비트 클리어 */
if (!test_and_clear_bit(action->thread_mask,
&desc->threads_oneshot)) {
raw_spin_unlock_irq(&desc->lock);
chip_bus_sync_unlock(desc);
goto again;
}
/* 모든 thread_fn이 완료되었는지 확인 */
if (desc->threads_oneshot == 0) {
/* 모두 완료: unmask! */
desc->istate &= ~IRQS_MASKED;
irq_unmask(desc);
}
raw_spin_unlock_irq(&desc->lock);
chip_bus_sync_unlock(desc);
}
에러 처리 패턴
Threaded IRQ에서 발생할 수 있는 에러 상황과 올바른 처리 패턴을 정리합니다.
thread_fn에서의 에러 처리
static irqreturn_t robust_thread_fn(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
int ret;
/* I2C/SPI 통신 에러 처리 */
ret = regmap_read(dev->regmap, REG_STATUS, &dev->status);
if (ret) {
dev_err(dev->dev, "IRQ: 상태 읽기 실패: %d\n", ret);
/* 에러여도 IRQ_HANDLED 반환 (IRQ_NONE이면 spurious 카운트 증가) */
return IRQ_HANDLED;
}
/* 데이터 처리 */
if (dev->status & ERROR_BIT) {
dev_warn(dev->dev, "디바이스 에러 감지: 0x%x\n", dev->status);
my_device_reset(dev);
} else {
process_normal_data(dev);
}
return IRQ_HANDLED;
}
/* 안전한 서스펜드/리줌 패턴 */
static int my_suspend(struct device *dev)
{
struct my_device *mydev = dev_get_drvdata(dev);
/* IRQ 비활성화 + 진행 중인 thread_fn 완료 대기 */
disable_irq(mydev->irq);
/* 이 시점에서 thread_fn이 확실히 실행 중이지 않음 */
save_device_state(mydev);
return 0;
}
static int my_resume(struct device *dev)
{
struct my_device *mydev = dev_get_drvdata(dev);
restore_device_state(mydev);
enable_irq(mydev->irq);
return 0;
}
IRQ Affinity와 스레드 Affinity
Threaded IRQ에서 IRQ affinity를 설정하면 스레드의 CPU affinity도 자동으로 동기화됩니다.
# IRQ 27을 CPU 2에 바인딩
$ echo 4 > /proc/irq/27/smp_affinity
# 확인: IRQ 스레드도 CPU 2로 이동
$ taskset -p $(pgrep "irq/27")
pid 207's current affinity mask: 4
# managed_irq: 디바이스 드라이버가 affinity를 관리
# irq_set_affinity_hint()로 힌트 제공
# 커널이 자동으로 최적 배치
NUMA 최적화
/* PCIe 디바이스의 NUMA 노드에 IRQ 바인딩 */
static int setup_numa_irq(struct pci_dev *pdev)
{
int node = dev_to_node(&pdev->dev);
const struct cpumask *mask;
if (node != NUMA_NO_NODE) {
mask = cpumask_of_node(node);
irq_set_affinity_hint(pdev->irq, mask);
}
return 0;
}
Managed IRQ와 자동 affinity
최신 멀티큐 디바이스(NVMe, 네트워크 카드)는 managed IRQ를 사용하여 커널이 자동으로 IRQ를 CPU에 최적 배치합니다:
/* managed IRQ: 커널이 affinity를 자동 관리 */
struct irq_affinity affd = {
.pre_vectors = 1, /* admin queue */
.post_vectors = 1, /* polling queue */
};
/* pci_alloc_irq_vectors_affinity()로 벡터 할당 시
* IRQ_NO_BALANCING + managed 속성이 자동 설정됨 */
ret = pci_alloc_irq_vectors_affinity(pdev,
min_vecs, max_vecs,
PCI_IRQ_MSIX | PCI_IRQ_AFFINITY,
&affd);
/* 결과: 각 벡터가 자동으로 다른 CPU에 배치됨
* IRQ N → CPU 0, IRQ N+1 → CPU 1, ...
* 스레드 affinity도 자동으로 동기화
* /proc/irq/N/effective_affinity에서 확인 가능 */
Affinity 변경 시 스레드 마이그레이션
IRQ affinity가 변경되면, IRQ 스레드도 자동으로 해당 CPU로 마이그레이션됩니다. 이 과정은 irq_thread_check_affinity()에서 처리됩니다:
# IRQ affinity 변경 과정 관찰
$ echo 4 > /proc/irq/27/smp_affinity # CPU 2로 변경
$ cat /proc/irq/27/effective_affinity # 실제 적용된 affinity
4
# 스레드 마이그레이션 확인
$ taskset -p $(pgrep "irq/27")
pid 207's current affinity mask: 4 # CPU 2로 이동 완료
# irq_set_affinity_notifier()로 변경 알림 수신 가능
# 드라이버가 affinity 변경에 대응해야 할 때 사용
/proc/irq/N/smp_affinity를 수동 변경할 수 없습니다. 커널이 CPU 온/오프라인 이벤트에 따라 자동으로 재배치합니다. 수동 affinity가 필요하면 managed 옵션 없이 벡터를 할당해야 합니다.
웨이크업 인터럽트와 Threaded IRQ
시스템 서스펜드 상태에서 특정 인터럽트가 시스템을 깨울 수 있어야 하는 경우, 웨이크업 인터럽트로 설정합니다.
/* 웨이크업 가능한 threaded IRQ 설정 */
static int wakeup_probe(struct platform_device *pdev)
{
int irq = platform_get_irq(pdev, 0);
/* 웨이크업 소스로 등록 */
device_init_wakeup(&pdev->dev, true);
devm_request_threaded_irq(&pdev->dev, irq,
NULL, wakeup_thread_fn,
IRQF_ONESHOT | IRQF_TRIGGER_LOW | IRQF_NO_SUSPEND,
"wakeup-key", pdev);
/* 서스펜드 시 IRQ를 웨이크업 소스로 설정 */
dev_pm_set_wake_irq(&pdev->dev, irq);
return 0;
}
/* 서스펜드 콜백에서 웨이크업 활성화 */
static int wakeup_suspend(struct device *dev)
{
if (device_may_wakeup(dev))
enable_irq_wake(dev->irq);
return 0;
}
static int wakeup_resume(struct device *dev)
{
if (device_may_wakeup(dev))
disable_irq_wake(dev->irq);
return 0;
}
Secondary Actions (이중 핸들러)
커널은 내부적으로 force_irqthreads 환경에서 기존 handler를 "primary" thread와 "secondary" thread로 분리할 수 있습니다. 이는 handler가 원래 hardirq와 softirq 양쪽 모두에서 작업을 수행하던 경우에 발생합니다.
/* kernel/irq/manage.c — secondary action 생성 */
if (new->handler && new->thread_fn) {
/* handler: 원래 hardirq 부분 유지
* thread_fn: 원래 softirq/bottom-half 부분 */
if (force_irqthreads && new->handler != irq_default_primary_handler) {
/* force threading 시:
* handler -> primary thread로 이동
* thread_fn -> secondary thread 생성 */
struct irqaction *secondary;
secondary = kzalloc(sizeof(*secondary), GFP_KERNEL);
secondary->handler = irq_forced_secondary_handler;
secondary->thread_fn = new->thread_fn;
new->secondary = secondary;
}
}
request_threaded_irq()에서 handler와 thread_fn을 둘 다 제공한 경우에만 발생합니다. handler=NULL이거나, handler가 이미 irq_default_primary_handler인 경우에는 Secondary actions가 생성되지 않습니다. 일반 커널에서는 전혀 사용되지 않으며, force_irqthreads 환경에서만 내부적으로 자동 생성됩니다.
패턴 정리: Threaded IRQ 사용 가이드
드라이버 개발자를 위한 Threaded IRQ 패턴을 유형별로 정리합니다.
| 디바이스 유형 | handler | thread_fn | 플래그 | 예시 |
|---|---|---|---|---|
| I2C/SPI 센서 | NULL | 데이터 읽기 | ONESHOT | TRIGGER_* | BMP280, BME680 |
| I2C 터치스크린 | NULL | 좌표 읽기 + 입력 보고 | ONESHOT | TRIGGER_LOW | FT5x06, Goodix |
| PMIC (regmap) | regmap_irq | regmap_irq | ONESHOT | TRIGGER_LOW | MAX77686, AXP20x |
| PCIe NIC | 상태 확인 + ACK | 패킷(Packet) 처리 | (MSI: 0) | 네트워크 카드 |
| PCIe INTx (공유) | 상태 확인 필수 | 데이터 처리 | SHARED | ONESHOT | 레거시 PCI |
| GPIO 버튼 | NULL | 디바운스 + 입력 | ONESHOT | TRIGGER_* | gpio-keys |
| GPIO expander 자식 | nested | nested | (부모가 관리) | PCA953x |
| DMA 컨트롤러 | 완료 ACK | 버퍼(Buffer) 처리 + 콜백 | 0 또는 ONESHOT | DMA engine |
- 슬로우 버스(I2C/SPI) 디바이스:
handler=NULL,IRQF_ONESHOT필수 - MMIO 디바이스: handler에서 빠른 ACK, thread_fn에서 나머지
- MSI/MSI-X: ONESHOT 선택적 (edge-triggered)
- 공유 INTx: handler 필수, IRQF_SHARED
- 의심스러우면
IRQF_ONESHOT을 추가하세요. 안전합니다.
흔한 실수와 안티패턴
| # | 실수 | 결과 | 해결 |
|---|---|---|---|
| 1 | handler=NULL + ONESHOT 미설정 | -EINVAL 반환, 등록 실패 | IRQF_ONESHOT 추가 |
| 2 | Level-triggered + ONESHOT 미설정 | IRQ storm, 시스템 마비 | IRQF_ONESHOT 추가 |
| 3 | thread_fn에서 IRQ_NONE 반환 | spurious 카운트 증가, IRQ 비활성화 위험 | IRQ_HANDLED 반환 |
| 4 | IRQF_SHARED + handler=NULL | 자기 디바이스 인터럽트 확인 불가 | handler 구현 필수 |
| 5 | hardirq handler에서 mutex 사용 | 스케줄링 불가 컨텍스트에서 슬립 시도 | spin_lock 사용 또는 thread_fn으로 이동 |
| 6 | free_irq() 없이 드라이버 언로드 | dangling 핸들러, 커널 패닉(Kernel Panic) | devm API 사용 |
| 7 | thread_fn에서 disable_irq() 호출 | 자기 자신의 thread_fn 완료를 대기하며 데드락 | disable_irq_nosync() 사용 |
| 8 | IRQF_NO_AUTOEN 후 enable_irq() 누락 | 인터럽트가 영원히 비활성화 | 초기화 완료 후 enable_irq() 호출 |
API 레퍼런스 요약
| 함수 | 설명 | 컨텍스트 |
|---|---|---|
request_threaded_irq() | threaded IRQ 등록 | 프로세스 |
devm_request_threaded_irq() | managed threaded IRQ 등록 | 프로세스 |
free_irq() | IRQ 해제 (스레드 종료 대기) | 프로세스 |
devm_free_irq() | managed IRQ 수동 해제 | 프로세스 |
synchronize_irq() | 진행 중인 handler/thread_fn 완료 대기 | 프로세스 |
disable_irq() | IRQ 비활성화 + 완료 대기 | 프로세스 |
disable_irq_nosync() | IRQ 비활성화 (완료 대기 안 함) | 모든 컨텍스트 |
enable_irq() | IRQ 활성화 | 모든 컨텍스트 |
enable_irq_wake() | 웨이크업 소스로 설정 | 프로세스 |
disable_irq_wake() | 웨이크업 소스 해제 | 프로세스 |
irq_set_affinity_hint() | IRQ affinity 힌트 설정 | 프로세스 |
handle_nested_irq() | nested IRQ 처리 (부모 스레드에서) | 스레드 |
irq_set_nested_thread() | IRQ를 nested 모드로 설정 | 프로세스 |
실제 커널 드라이버 사례 분석
실제 리눅스 커널에서 threaded IRQ를 사용하는 대표적인 드라이버들을 분석합니다.
PMIC: MAX77686 (drivers/mfd/max77686-irq.c)
삼성 Exynos 플랫폼의 PMIC으로, I2C 연결입니다. regmap_irq를 사용하여 다수의 인터럽트 소스를 관리합니다:
/* 간략화된 MAX77686 IRQ 설정 */
ret = regmap_add_irq_chip(max77686->regmap, max77686->irq,
IRQF_ONESHOT | IRQF_TRIGGER_LOW |
IRQF_SHARED,
0, &max77686_irq_chip,
&max77686->irq_data);
/* 결과: I2C를 통한 모든 인터럽트 상태 읽기/마스킹이
* threaded handler에서 자동으로 처리됨 */
터치 컨트롤러: Goodix (drivers/input/touchscreen/goodix_ts_core.c)
/* Goodix 터치 컨트롤러: handler=NULL 패턴 */
ret = devm_request_threaded_irq(&ts->client->dev,
ts->client->irq, NULL, goodix_ts_irq_handler,
IRQF_ONESHOT, "goodix_ts", ts);
/* thread_fn에서 I2C로 터치 데이터 읽기 + 입력 이벤트 보고 */
NVMe (drivers/nvme/host/pci.c)
NVMe는 MSI-X를 사용하지만, 특정 경우 threaded IRQ 패턴을 활용합니다:
/* NVMe: 컴플리션 큐 처리를 위한 threaded IRQ */
ret = pci_request_irq(pdev, nr,
nvme_irq, /* hardirq: CQ doorbell 확인 */
nvme_irq_check, /* thread: 컴플리션 큐 처리 */
nvmeq, "nvme%dq%d", ...);
GPIO Keys (drivers/input/keyboard/gpio_keys.c)
GPIO Keys 드라이버는 GPIO 기반 버튼 입력을 처리하는 대표적인 threaded IRQ 사용 사례입니다. 디바운싱과 웨이크업 기능을 모두 지원합니다:
/* GPIO Keys: 하드웨어 디바운스 미지원 시 소프트웨어 디바운스 + threaded IRQ */
static irqreturn_t gpio_keys_irq_isr(int irq, void *dev_id)
{
struct gpio_button_data *bdata = dev_id;
struct input_dev *input = bdata->input;
BUG_ON(irq != bdata->irq);
spin_lock_irqsave(&bdata->lock, flags);
if (!bdata->key_pressed) {
if (bdata->button->wakeup)
pm_wakeup_event(bdata->input->dev.parent, 0);
input_event(input, EV_KEY, *bdata->code, 1);
input_sync(input);
if (!bdata->release_delay) {
input_event(input, EV_KEY, *bdata->code, 0);
input_sync(input);
}
}
spin_unlock_irqrestore(&bdata->lock, flags);
return IRQ_HANDLED;
}
/* 등록: ONESHOT + 트리거 타입 조합 */
ret = request_any_context_irq(bdata->irq,
gpio_keys_irq_isr, irqflags, desc, bdata);
/* request_any_context_irq(): hardirq/threaded 자동 선택
* GPIO가 I2C expander 뒤에 있으면 → nested (threaded)
* GPIO가 SoC 직접 연결이면 → hardirq
* 반환값으로 어떤 컨텍스트가 선택되었는지 알 수 있음 */
IIO (Industrial I/O) 센서 트리거
IIO 프레임워크의 센서 드라이버는 데이터 준비 인터럽트를 threaded IRQ로 처리하여 고속 센서 데이터를 수집합니다:
/* IIO 센서: data-ready 인터럽트 → threaded IRQ → 버퍼 push */
static irqreturn_t bmp280_trigger_handler(int irq, void *p)
{
struct iio_poll_func *pf = p;
struct iio_dev *indio_dev = pf->indio_dev;
struct bmp280_data *data = iio_priv(indio_dev);
s32 adc_temp, adc_press;
/* I2C/SPI로 센서 데이터 읽기 (프로세스 컨텍스트에서 슬립 가능) */
mutex_lock(&data->lock);
regmap_bulk_read(data->regmap, BMP280_REG_PRESS_MSB,
data->buf, BMP280_DATA_LEN);
mutex_unlock(&data->lock);
/* IIO 버퍼에 push */
iio_push_to_buffers_with_timestamp(indio_dev,
data->buf, pf->timestamp);
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
}
/* IIO 트리거 + threaded IRQ 등록 */
ret = devm_iio_triggered_buffer_setup(&client->dev, indio_dev,
iio_pollfunc_store_time, /* hardirq: 타임스탬프만 저장 */
bmp280_trigger_handler, /* thread_fn: 데이터 읽기 + push */
NULL);
request_any_context_irq() — 자동 컨텍스트 선택
일부 드라이버는 GPIO가 SoC에 직접 연결되었는지, I2C GPIO expander 뒤에 있는지에 따라 hardirq 또는 threaded 모드가 결정됩니다. request_any_context_irq()는 이를 자동으로 판단합니다:
/* include/linux/interrupt.h */
extern int request_any_context_irq(
unsigned int irq,
irq_handler_t handler,
unsigned long flags,
const char *name,
void *dev_id);
/* 반환값:
* IRQC_IS_HARDIRQ — hardirq 컨텍스트로 등록됨
* IRQC_IS_NESTED — nested threaded로 등록됨 (I2C/SPI 뒤)
* 음수 — 에러
*
* 내부적으로 irq_settings_is_nested_thread(desc)를 확인하여
* nested이면 자동으로 threaded IRQ로 등록 */
debugfs 인터페이스
CONFIG_GENERIC_IRQ_DEBUGFS=y를 활성화하면 /sys/kernel/debug/irq/에 상세한 IRQ 정보가 제공됩니다.
# debugfs IRQ 정보 확인
$ ls /sys/kernel/debug/irq/irqs/
0 1 2 3 4 5 6 7 8 9 10 ...
$ cat /sys/kernel/debug/irq/irqs/27
handler: 0xffffffff81234560 [my_hardirq]
thread_fn: 0xffffffff81234580 [my_thread_fn]
flags: 0x00002080 (SHARED|ONESHOT)
thread: irq/27-my-dev (pid 207)
type: level-low
chip: GICv3
hwirq: 47
domain: /interrupt-controller@...
affinity: 0-3
effective: 2
# IRQ 도메인 트리 확인
$ cat /sys/kernel/debug/irq/domains/list
IRQ 디버깅 워크플로우
실전 디버깅: IRQ Storm 복구
# 1단계: IRQ storm 감지
$ watch -n1 'cat /proc/interrupts | head -5'
# 특정 IRQ의 카운트가 초당 수천~수만 씩 증가하면 storm
# 2단계: 해당 IRQ 임시 비활성화 (긴급)
$ echo 1 > /proc/irq/27/disable # 주의: 관련 디바이스 동작 중단
# 3단계: IRQ 정보 수집
$ cat /proc/irq/27/type # edge vs level 확인
$ cat /proc/irq/27/actions # 어떤 드라이버인지 확인
$ cat /proc/irq/27/spurious # unhandled 카운트 확인
# 4단계: dmesg에서 관련 메시지 확인
$ dmesg | grep -i "irq 27\|nobody cared\|irq.*disabled"
# 5단계: 드라이버 수정 후 재활성화
$ echo 0 > /proc/irq/27/disable # 또는 드라이버 reload
참고자료
커널 공식 문서
- Linux generic IRQ handling — 리눅스 범용 IRQ 처리 프레임워크 공식 문서입니다
- Generic IRQ — Threaded Handlers — 스레드화 핸들러 하위 섹션으로, hardirq/thread_fn 분리 구조를 설명합니다
- Generic IRQ — IRQ chip flags — IRQCHIP_ONESHOT_SAFE 등 IRQ 칩 플래그 설명입니다
커널 소스 코드
- kernel/irq/manage.c —
request_threaded_irq(),__setup_irq(),irq_thread()등 핵심 구현이 위치한 파일입니다 - kernel/irq/irqdesc.c — IRQ 디스크립터(irq_desc) 할당과 관리 코드입니다
- kernel/irq/chip.c — irq_chip 콜백과 flow handler 구현입니다
- include/linux/interrupt.h —
request_threaded_irq(),devm_request_threaded_irq(), IRQF_* 플래그 선언입니다 - include/linux/irq.h —
struct irq_data,struct irq_chip, IRQ_ONESHOT 등 내부 구조체 정의입니다 - kernel/irq/spurious.c — 스퓨리어스(Spurious) IRQ 감지와 자동 비활성화 로직입니다
LWN.net 기사
- Threaded interrupts (2008) — Thomas Gleixner의 스레드화 인터럽트 패치 시리즈 초기 제안을 다룬 LWN 기사입니다
- Moving interrupts to threads (2010) — 인터럽트를 커널 스레드로 이동시키는 메커니즘의 설계 결정을 분석합니다
- A PREEMPT_RT progress report (2020) — PREEMPT_RT 메인라인 통합 진행 현황과 forced threading의 역할을 설명합니다
- The state of realtime (2017) — RT 패치셋의 상태와 스레드화 인터럽트가 실시간 성능에 미치는 영향을 논의합니다
- A realtime preemption overview (2005) — Paul McKenney의 RT 선점 개요로, hardirq 스레드화의 초기 배경을 설명합니다
서적 및 교육 자료
- Linux Kernel Development, Robert Love, 3rd Edition — Chapter 7 "Interrupts and Interrupt Handlers"에서 top half/bottom half 개념과 threaded IRQ의 기초를 다룹니다
- Linux Device Drivers, 3rd Edition (LDD3) — Chapter 10 "Interrupt Handling"에서 공유 인터럽트, IRQ 핸들러 등록 패턴을 설명합니다
- Understanding the Linux Kernel, Daniel P. Bovet & Marco Cesati, 3rd Edition — Chapter 4 "Interrupts and Exceptions"에서 리눅스 인터럽트 아키텍처를 심도 있게 분석합니다
컨퍼런스 발표 및 외부 자료
- Threaded IRQs and Real-Time — Embedded Linux Conference 발표 자료로, 스레드화 IRQ와 RT 환경의 관계를 설명합니다
- Real-Time Linux Wiki — Linux Foundation의 RT 프로젝트 위키로, PREEMPT_RT 패치의 설계 원칙과 forced threading 동작을 문서화합니다
- PREEMPT_RT Setup Guide — PREEMPT_RT 커널 빌드 및 설정 방법을 안내합니다
- OSADL Real-Time Linux Project — OSADL의 실시간 리눅스 프로젝트 페이지로, 레이턴시 테스트 결과와 스레드화 인터럽트 벤치마크를 제공합니다
- The PREEMPT_RT patchset is in the home stretch (2021) — PREEMPT_RT의 메인라인 커널 최종 통합 과정을 다룬 기사입니다
관련 문서
Threaded IRQ와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.