티스토리 뷰

8 인터럽트

8.4 인터럽트 지연처리

8.4.1 top-half, bottom-half

  • 인터럽트 핸들러에서 인터럽트를 처리하는 동인 인터럽트는 비활성화 상태가 됨.
  • 이때 blocking 되어 데드락이 걸리거나 인터럽트를 놓칠 수 있고, sleep 하는 경우는 깨어나지 못할 수도 있음
  • 이런 문제를 방지하기 위해, 인터럽트 핸들러를 별도의 안전한 컨텍스트에서 실행함
구분 top-half bottom-half
실행 위치 하드웨어 인터럽트 핸들러 내에서 수행 하드웨어 인터럽트 핸들러 밖에서 수행
실행 컨텍스트 하드웨어 인터럽트 컨텍스트 소프트웨어 인터럽트 컨텍스트 or 프로세서 컨텍스트
인터럽트 활성화 여부 인터럽트 비활성화 상태 인터럽트 활성화 상태
주요 작업 다음 인터럽트 발생 가능하도록 최소한의 작업 수행 처리시간이 길거나 블록 될 수 있는 지연된 작업 처리

top-half: ISR(Interrupt Service Routine) 에서 bottom-half 에 분배

hard IRQ 와 soft IRQ

아키텍처 관점

  • HW IRQ: 하드웨어 장치가 발생하는 인터럽트
  • SW IRQ: 소프트웨어(시스템 콜, 예외)가 발생시키는 인터럽트

리눅스 커널 관점

  • Hard IRQ: 인터럽트가 발생하면 즉시 실행됨 (짧고 빠르게 처리)
  • Soft IRQ: Hard IRQ에서 넘겨받은 작업을 나중에 실행 (네트워크 패킷, 블록 디바이스 I/O 처리)

Hard IRQ와 Soft IRQ는 인터럽트 처리에 관한 개념이고, Top-half와 Bottom-half는 인터럽트 처리의 흐름을 설명하는 개념

soft IRQ 와 SW IRQ 는 전혀 다른 개념. softIRQ 는 인터럽트를 지연 처리 하는것

$ cat /proc/softirqs
                    CPU0       CPU1       CPU2       CPU3       CPU4       CPU5       CPU6       CPU7       CPU8       CPU9       CPU10      CPU11
          HI:          0          0          0          0          0          0          0          0          0          0          0          0
       TIMER:      13662      11818      18707      16318      20230      14917      14078       8432      16445       6710      14198       6197
      NET_TX:          0          0          0          0          0          0          0          0          0          0          0          0
      NET_RX:       1854       1417       1742        704       3880       1002       1880       1160       1877        820       1949       1136
       BLOCK:          0          0          0          0          0          0          0          0          0          0          0          0
    IRQ_POLL:          0          0          0          0          0          0          0          0          0          0          0          0
     TASKLET:     129745     109616     100553          0          1          0          0          0          0          0          0          0
       SCHED:      68081      41242      51434      93770      63607      32627      55538      22766      57432      18298      52017      22680
     HRTIMER:          0          0          0          0          0          1          0          0          0          0          0          0
         RCU:      53004      41567      59739      85283      63843      40492      58084      31793      58515      27775      56497      32467

8-11 kernel/softirq.c softirq_init

void __init softirq_init(void)
{
    int cpu;

    for_each_possible_cpu(cpu) {
        per_cpu(tasklet_vec, cpu).tail =
            &per_cpu(tasklet_vec, cpu).head; // tail 을 head 에 연결만하네
        per_cpu(tasklet_hi_vec, cpu).tail =
            &per_cpu(tasklet_hi_vec, cpu).head;
    }

    open_softirq(TASKLET_SOFTIRQ, tasklet_action); // TASKLET_SOFTIRQ
    open_softirq(HI_SOFTIRQ, tasklet_hi_action); // HI_SOFTIRQ
}

SOFTIRQ 와 TASKLET 비교

tasklet 은 softirq 개념 위에서 동작하며, HI_SOFTIRQ 와 TASKLET_SOFTIRQ 에서 동작함

tasklet은 런타임에 할당 및 초기화 될 수있는 softirqs

softirq와 달리 동일한 유형의 tasklet은 한 번에 여러 프로세서에서 실행될 수 없음.

특징 softirq tasklet
동시성 같은 프로레서에서 실행 가능 같은 tasklet 은 다른 cpu 에서 실행 될 수 없음
등록 방법 컴파일시 정적 등록 런타임 시 동적 등록
우선순위 softirq 마다 우선순위 HI, TASKLET 으로 구분
사용하는곳 빈번함. 병렬처리 필요한 곳 직렬 처리가 필요한곳. 디바이스 드라이버에서 필요한 경우 사용 가능

kernel/softirq.c raise_softirq

/*
 * This function must run with irqs disabled!
 */
inline void raise_softirq_irqoff(unsigned int nr)
{
    __raise_softirq_irqoff(nr);

    // #define in_interrupt()        (irq_count())
    // #define irq_count()    (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK | NMI_MASK))
    if (!in_interrupt())
        wakeup_softirqd();
}

void raise_softirq(unsigned int nr)
{
    unsigned long flags;

    local_irq_save(flags);
    raise_softirq_irqoff(nr);
    local_irq_restore(flags);
}

void __raise_softirq_irqoff(unsigned int nr)
{
    trace_softirq_raise(nr);
    or_softirq_pending(1UL << nr);
}

static void wakeup_softirqd(void)
{
    /* Interrupts are disabled: no need to stop preemption */
    struct task_struct *tsk = __this_cpu_read(ksoftirqd);

    if (tsk && tsk->state != TASK_RUNNING)
        wake_up_process(tsk);
}

8-13 kernel/softirq.c __do_soft_irq


asmlinkage __visible void __softirq_entry __do_softirq(void)
{
    // jiffies 시스템에 내장된 타이머에서 인터럽트 된 마지막 시간
    unsigned long end = jiffies + MAX_SOFTIRQ_TIME; // 최대 수행 시간
    unsigned long old_flags = current->flags;
    int max_restart = MAX_SOFTIRQ_RESTART; // 최대 retry 횟수

    pending = local_softirq_pending(); // 마스킹 된 softirq 있는지 확인
    account_irq_enter_time(current);

    // IP : Instruction Pointer
    __local_bh_disable_ip(_RET_IP_, SOFTIRQ_OFFSET); 선점 못하게
    in_hardirq = lockdep_softirq_start();

restart:
    set_softirq_pending(0);

    local_irq_enable();

    h = softirq_vec;

    while ((softirq_bit = ffs(pending))) { // 우선순위 높은거부터
        unsigned int vec_nr;
        int prev_count;

        h += softirq_bit - 1;

        vec_nr = h - softirq_vec;
        prev_count = preempt_count();

        h->action(h); // 실행
        h++;
        pending >>= softirq_bit;
    }

    rcu_bh_qs();
    local_irq_disable();

    pending = local_softirq_pending();
    if (pending) {
        if (time_before(jiffies, end) && !need_resched() && // 한번 더
            --max_restart)
            goto restart;

        wakeup_softirqd();
    }
}

8.4.3 워크큐

프로세스 컨특스트에서 실해오디어, 긴 시간동안 처리해야 하는 작업에 적합하다.

softirq 는 인터럽트 발생 외 컨텍스트 스위칭이 발생하지 않지만, 워크 큐는 유저 태스크를 포함하여 다른 태스크가 선점이 가능하다.

워크큐의 개념

워커는 작업이 없으면 IDLE 상태로 진입했다가, 작업이 도착하면 깨어나 동작한다. 워커 쓰레드는 워커 풀에 의해 관리된다.

cmwq (Concurrency-Managed Workqueues)

워커 관리와 동시성 향상을 위해 추가된 워크 큐

기존 방식

  • Multi Thread (MT) 워커 : cpu 당 하나의 워커. cpu 개수만큼 사용하여 불필요하게 많은 워커쓰레드가 필요.
  • Single Thread (ST) 워커 : 시스템 당 하나
구분 bound unbound
동작 방식 특정 cpu 에서 동작 동작할 cpu 를 지정하지 않음 (작업 요청 cpu 우선)

pwq: pool workqueue. 워크 큐와 워크 풀은 풀 워크큐를 통해 연결됨. 동시처리가 지연되어 워커 풀에 들어가지 못하는 경우, 풀 워크큐에 등록

cpu 별로 보통 우선순위, 높은 우선순위 풀을 두개씩 갖고 있음.

__queue_work

work_struct 로 워크큐에 등록할 작업 구조체를 선언하고 초기화.

  • schedule_XXX : system_wq 워크큐에 등록
  • XXX_on : bound 로 cpu 를 지정
  • XXX_delayed_XXX : 지연 시간 이후 동작하도록 등록
#define DECLARE_WORK(n, f)                        \
    struct work_struct n = __WORK_INITIALIZER(n, f)

#define INIT_WORK(_work, _func)                        \
    __INIT_WORK((_work), (_func), 0)

8-14 kernel/workqueue.c __queue_work

struct workqueue_struct { // 작업 큐 전체를 대표하는 구조체
    struct list_head    pwqs;        /* WR: all pwqs of this wq */
    struct list_head    list;        /* PR: list of all workqueues */
    ...
}

struct pool_workqueue { // 
    struct worker_pool    *pool;        /* I: the associated pool */
    struct workqueue_struct *wq;        /* I: the owning workqueue */
    int            work_color;    /* L: current color */
    int            flush_color;    /* L: flushing color */
    int            refcnt;        /* L: reference count */
    int            nr_in_flight[WORK_NR_COLORS];
    ...
}

struct worker_pool { // 워커 쓰레드 그룹
    spinlock_t        lock;        /* the pool lock */
    int            cpu;        /* I: the associated cpu */
    int            node;        /* I: the associated node ID */
    int            id;        /* I: pool ID */
    unsigned int        flags;        /* X: flags */

    unsigned long        watchdog_ts;    /* L: watchdog timestamp */

    struct list_head    worklist;    /* L: list of pending works */
    int            nr_workers;    /* L: total number of workers */
    ...
}

workqueue_struct 는 종류별로 필요한 만큼 만들 수 있음

extern struct workqueue_struct *system_wq;
extern struct workqueue_struct *system_highpri_wq;
extern struct workqueue_struct *system_long_wq;
extern struct workqueue_struct *system_unbound_wq;
extern struct workqueue_struct *system_freezable_wq;
extern struct workqueue_struct *system_power_efficient_wq;
extern struct workqueue_struct *system_freezable_power_efficient_wq;
extern struct workqueue_struct *system_bh_wq;
extern struct workqueue_struct *system_bh_highpri_wq;

pwq (pool_workqueue) 는 기본적으로 CPU 바운드라면 CPU 개수만큼, UNBOUND라면 1개만 생성됨.

worker_pool은 task(작업)를 실행할 수 있는 워커(worker) 스레드 그룹을 관리하는 구조체

static void __queue_work(int cpu, struct workqueue_struct *wq,
             struct work_struct *work)
{
    if (unlikely(wq->flags & __WQ_DRAINING) && // 실행중인 쓰레드가 내 work 의 쓰레드가 동일할때만 queue 하는 DRAINING flag
        WARN_ON_ONCE(!is_chained_work(wq)))
        return;
retry:
    if (req_cpu == WORK_CPU_UNBOUND) // 지정된 cpu 부터 시작하여 round robing 으로 cpu 를 지정
        cpu = wq_select_unbound_cpu(raw_smp_processor_id());

    if (!(wq->flags & WQ_UNBOUND)) // bound 인경우
        pwq = per_cpu_ptr(wq->cpu_pwqs, cpu);  // cpu마다 pool_workqueue 구조체를 가짐. cpu 에 맞는 pwq 를 가져옴.
    else
        pwq = unbound_pwq_by_node(wq, cpu_to_node(cpu)); // cpu 가 속한 node 에서 unbound 용 pwq 를 가져옴

    last_pool = get_work_pool(work); // 마지막 사용 풀
    if (last_pool && last_pool != pwq->pool) {
        struct worker *worker;

        spin_lock(&last_pool->lock);

        worker = find_worker_executing_work(last_pool, work); // 풀 안에있는 worker 중 실행가능한? 워커를 가져옴

        if (worker && worker->current_pwq->wq == wq) { // 워크큐와 같다면
            pwq = worker->current_pwq // worker 의 pwq 를 사용
        } else {
            /* meh... not running there, queue here */
            spin_unlock(&last_pool->lock);
            spin_lock(&pwq->pool->lock); // 새로 사용할 pool 에다가 Lock 을 잡음
        }
    } else { // pwq 와 work 의 pool 이 같은 경우
        spin_lock(&pwq->pool->lock); // 해당 pool 을 사용
    }

    if (unlikely(!pwq->refcnt)) {
        if (wq->flags & WQ_UNBOUND) {  // 버그상황인 경우, unbound 에서는 다른 cpu 를 찾아서 실행하도록 retry
            spin_unlock(&pwq->pool->lock);
            cpu_relax();
            goto retry;
        }
        /* oops */
        WARN_ONCE(true, "workqueue: per-cpu pwq for %s on cpu%d has 0 refcnt",
              wq->name, cpu); // bound 로 cpu 가 지정된 경우는 어쩔수 없으니 경고를 띄움
    }

    /* pwq determined, queue */
    trace_workqueue_queue_work(req_cpu, pwq, work);

    if (WARN_ON(!list_empty(&work->entry))) {
        spin_unlock(&pwq->pool->lock);
        return;
    }

// static int work_next_color(int color)
// {
//     return (color + 1) % WORK_NR_COLORS;
// }

    pwq->nr_in_flight[pwq->work_color]++; // 현재 실행중인 color 에 + 1
    work_flags = work_color_to_flags(pwq->work_color);

    if (likely(pwq->nr_active < pwq->max_active)) {
        trace_workqueue_activate_work(work);
        pwq->nr_active++;
        worklist = &pwq->pool->worklist; // 실행 가능한 상태인 경우 pool->worklist 로 지정
        if (list_empty(worklist))
            pwq->pool->watchdog_ts = jiffies;
    } else {
        work_flags |= WORK_STRUCT_DELAYED;
        worklist = &pwq->delayed_works; // 실행 불가능한 상태인 경우 pwq->delayed_works 로 지정
    }

    insert_work(pwq, work, worklist, work_flags); // work 를 list 에 삽입

    spin_unlock(&pwq->pool->lock);
}

kernel/workqueue.c worker_thread

enum {
    WORK_STRUCT_PENDING_BIT    = 0,    /* work item is pending execution */
    WORK_STRUCT_DELAYED_BIT    = 1,    /* work item is delayed */
    WORK_STRUCT_PWQ_BIT    = 2,    /* data points to pwq */
    WORK_STRUCT_LINKED_BIT    = 3,    /* next work is linked to this one */
#ifdef CONFIG_DEBUG_OBJECTS_WORK
    WORK_STRUCT_STATIC_BIT    = 4,    /* static initializer (debugobjects) */
    WORK_STRUCT_COLOR_SHIFT    = 5,    /* color for workqueue flushing */
#else
    WORK_STRUCT_COLOR_SHIFT    = 4,    /* color for workqueue flushing */
#endif

    WORK_STRUCT_COLOR_BITS    = 4,

    WORK_STRUCT_PENDING    = 1 << WORK_STRUCT_PENDING_BIT,
    WORK_STRUCT_DELAYED    = 1 << WORK_STRUCT_DELAYED_BIT,
    WORK_STRUCT_PWQ        = 1 << WORK_STRUCT_PWQ_BIT,
    WORK_STRUCT_LINKED    = 1 << WORK_STRUCT_LINKED_BIT,
}

// nr_running == 0이면 아무 worker도 현재 일을 하고 있지 않은 상태고,
// nr_running == 1이면 현재 딱 하나의 worker만 일하는 중
static bool keep_working(struct worker_pool *pool)
{
    return !list_empty(&pool->worklist) &&
        atomic_read(&pool->nr_running) <= 1;
}

// worker pool 에 등록된 작업을 갸져와서 실행하기 위함
// bottom-half 로 무거운 작업들이 work queue 에 담겨있음
static int worker_thread(void *__worker)
{
    struct worker *worker = __worker;
    struct worker_pool *pool = worker->pool;

    worker->task->flags |= PF_WQ_WORKER; // 현재 task 가 worker thread 임을 등록. 이후 counter 에 추가
woke_up:
    spin_lock_irq(&pool->lock);

    /* am I supposed to die? */
    if (unlikely(worker->flags & WORKER_DIE)) {
        ... // worker 가 죽은 경우 처리
        return 0;
    }

    worker_leave_idle(worker); // 워커가 일을 할 예정으로idled 리스트에서 빼옴
recheck:
    // return !list_empty(&pool->worklist) && !atomic_read(&pool->nr_running);
    if (!need_more_worker(pool)) // 다른 워커를 깨워야 할 필요가 없다면, queue 에 아이템이 없고 워커들이 돌지 않는 경우
        goto sleep;

    /* do we need to manage? */
    // may_start_working { return pool->nr_idle; }
    // manage_workers { manage_arb 락 잡은 경우 }. arb = arbitration = 조정중
    if (unlikely(!may_start_working(pool)) && manage_workers(worker))
        goto recheck;

    WARN_ON_ONCE(!list_empty(&worker->scheduled)); // worker 가 준비 되기전에 scheduled 된게 있었으면 경고

    worker_clr_flags(worker, WORKER_PREP | WORKER_REBOUND); // PREP, REBOUND 플래그를 제거

    do {
        struct work_struct *work = // 풀의 첫번째 작업 가져오기
            list_first_entry(&pool->worklist,
                     struct work_struct, entry);

        pool->watchdog_ts = jiffies; // 풀의 watchdog(주어진 시간에 특정 일이 발생하는지 감시하는 기능) 에 현재시간 등록

        if (likely(!(*work_data_bits(work) & WORK_STRUCT_LINKED))) { // 작업이 링크되어있지 않다면. 다음 work 가 연결되어있지 않은 경우
            /* optimization path, not strictly necessary */
            process_one_work(worker, work); // 워커에게 하나의 일을 수행 시킴
            if (unlikely(!list_empty(&worker->scheduled))) // 워커에 스케줄된 작업이 있다면 수행시킴
                process_scheduled_works(worker);
        } else {
            move_linked_works(work, &worker->scheduled, NULL); // link 되어있었다면 스케줄 리스트에 연결
            process_scheduled_works(worker); // 스케줄된 작업 수행
        }
    } while (keep_working(pool)); // 워커 풀이 다 처리 될때까지 반복

    worker_set_flags(worker, WORKER_PREP); // sleep 시키기 위해 준비중 상태로 변경
sleep:
    worker_enter_idle(worker); // idle 상태로 변경
    __set_current_state(TASK_INTERRUPTIBLE);
    spin_unlock_irq(&pool->lock);
    schedule();
    goto woke_up;
}
  • process 시점에는 lock 을 풀고 처리함. 그래서 병렬적으로 돔
    static void process_one_work(struct worker *worker, struct work_struct *work)
    __releases(&pool->lock)
    __acquires(&pool->lock)
    {

kernel/workqueue.c init_workqueues

8.4.3 에서 나온 unbound, pwq

구분 bound unbound
동작 방식 특정 cpu 에서 동작 동작할 cpu 를 지정하지 않음 (작업 요청 cpu 우선)

pwq(per-cpu workqueue): pool workqueue. 워크 큐와 워크 풀은 풀 워크큐를 통해 연결됨. 동시처리가 지연되어 워커 풀에 들어가지 못하는 경우, 풀 워크큐에 등록

workqueue(bound) [pwq0, pwq1, pwq2, pwq3] / workqueue(unbound) [pwq]
workqueue(bound) [pwq0, pwq1, pwq2, pwq3] / workqueue(unbound) [pwq]
.. (여러개 존재)

cpu0 [pool-normal, pool-prio] (각 pool 별로 worker 를 가짐)
cpu1 [pool-normal, pool-prio]
cpu2 [pool-normal, pool-prio]
cpu3 [pool-normal, pool-prio]

static int __init init_workqueues(void)
{
    int std_nice[NR_STD_WORKER_POOLS] = { 0, HIGHPRI_NICE_LEVEL }; // 워커풀을 normal, prio 로 관리
    int i, cpu;

    WARN_ON(__alignof__(struct pool_workqueue) < __alignof__(long long));

    BUG_ON(!alloc_cpumask_var(&wq_unbound_cpumask, GFP_KERNEL));
    // possible mask 값을 unbound_cpumask 로 복사해옴
    // 모든 cpu 가 unbound task 를 사용 가능한 상태로
     cpumask_copy(wq_unbound_cpumask, cpu_possible_mask); 

    // bound 는 per-cpu 로 관리되기 때문에
    // unbound 만 slab 캐시로 생성
    pwq_cache = KMEM_CACHE(pool_workqueue, SLAB_PANIC);

    cpu_notifier(workqueue_cpu_up_callback, CPU_PRI_WORKQUEUE_UP); // 이벤트 콜백 등록
    hotcpu_notifier(workqueue_cpu_down_callback, CPU_PRI_WORKQUEUE_DOWN);

    wq_numa_init();

    /* initialize CPU pools */
    for_each_possible_cpu(cpu) { // possible cpu 에 대한 워커풀을 순회
        struct worker_pool *pool;

        i = 0;
        for_each_cpu_worker_pool(pool, cpu) { // pool 은 per-cpu 로 관리됨. normal, prio 두개
            BUG_ON(init_worker_pool(pool));
            pool->cpu = cpu;
            cpumask_copy(pool->attrs->cpumask, cpumask_of(cpu));
            pool->attrs->nice = std_nice[i++];
            pool->node = cpu_to_node(cpu);

            /* alloc pool ID */
            mutex_lock(&wq_pool_mutex);
            BUG_ON(worker_pool_assign_id(pool));
            mutex_unlock(&wq_pool_mutex);
        }
    }

    /* create the initial worker */
    for_each_online_cpu(cpu) {
        struct worker_pool *pool;

        for_each_cpu_worker_pool(pool, cpu) {
            pool->flags &= ~POOL_DISASSOCIATED;
            BUG_ON(!create_worker(pool)); pool 에 초기 워커 생성
        }
    }

kernel/workqueue.c init_workqueues 2

    /* create default unbound and ordered wq attrs */
    for (i = 0; i < NR_STD_WORKER_POOLS; i++) {
        struct workqueue_attrs *attrs;

        BUG_ON(!(attrs = alloc_workqueue_attrs(GFP_KERNEL)));
        attrs->nice = std_nice[i];
        unbound_std_wq_attrs[i] = attrs; // unbound attr 설정

        /*
         * An ordered wq should have only one pwq as ordering is
         * guaranteed by max_active which is enforced by pwqs.
         * Turn off NUMA so that dfl_pwq is used for all nodes.
         */
        BUG_ON(!(attrs = alloc_workqueue_attrs(GFP_KERNEL)));
        attrs->nice = std_nice[i];
        attrs->no_numa = true;
        ordered_wq_attrs[i] = attrs; // bound attr 설정
    }

    // 여기서 미리 지정한 이유는 재사용하기 위함임. unboud, order
    // static int alloc_and_link_pwqs(struct workqueue_struct *wq)

    // 각 WORK QUEUE 들 생성
    // allock_work queue 에서 위에 정해둔 attr 을 따름
    system_wq = alloc_workqueue("events", 0, 0);
    system_highpri_wq = alloc_workqueue("events_highpri", WQ_HIGHPRI, 0);
    system_long_wq = alloc_workqueue("events_long", 0, 0);
    system_unbound_wq = alloc_workqueue("events_unbound", WQ_UNBOUND,
                        WQ_UNBOUND_MAX_ACTIVE);
    system_freezable_wq = alloc_workqueue("events_freezable",
                          WQ_FREEZABLE, 0);
    system_power_efficient_wq = alloc_workqueue("events_power_efficient",
                          WQ_POWER_EFFICIENT, 0);
    system_freezable_power_efficient_wq = alloc_workqueue("events_freezable_power_efficient",
                          WQ_FREEZABLE | WQ_POWER_EFFICIENT,
                          0);
    BUG_ON(!system_wq || !system_highpri_wq || !system_long_wq ||
           !system_unbound_wq || !system_freezable_wq ||
           !system_power_efficient_wq ||
           !system_freezable_power_efficient_wq);

    wq_watchdog_init();

    return 0;
}
early_initcall(init_workqueues);

enum {
    WQ_UNBOUND        = 1 << 1, /* not bound to any cpu */
    WQ_FREEZABLE        = 1 << 2, /* freeze during suspend */
    WQ_MEM_RECLAIM        = 1 << 3, /* may be used for memory reclaim */
    WQ_HIGHPRI        = 1 << 4, /* high priority */
    WQ_CPU_INTENSIVE    = 1 << 5, /* cpu intensive workqueue */
    WQ_SYSFS        = 1 << 6, /* visible in sysfs, see wq_sysfs_register() */
}

8.4.4 threaded irq

각각의 인터럽트를 위한 별도의 커널 쓰레드를 만들어 처리하는 개념.

구분 irq threaded irq
top-half IRQ context IRQ context
bottom-half tasklet, workqueue thread_fn (kernel thread)

https://stackoverflow.com/questions/68616407/what-is-the-difference-between-threaded-interrupt-handler-and-tasklet

좀더 사용자 원하는대로 커스텀 하는것이 thread_irq

kernel/irq/manage.c setup_irq_thread


static int
setup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary)
{
    struct task_struct *t;
    struct sched_param param = {
        .sched_priority = MAX_USER_RT_PRIO/2,
    };

    if (!secondary) { // primary case
        t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
                   new->name);
    } else { // secondary case
        t = kthread_create(irq_thread, new, "irq/%d-s-%s", irq,
                   new->name);
        param.sched_priority -= 1;
    }

    if (IS_ERR(t))
        return PTR_ERR(t);

    sched_setscheduler_nocheck(t, SCHED_FIFO, &param);

    get_task_struct(t); // 해제 못하도록 counter + 1
    new->thread = t;
    set_bit(IRQTF_AFFINITY, &new->thread_flags);
    return 0;
}
댓글