linux-debug/interrupt

created : 2020-11-10T14:55:41+00:00
modified : 2020-12-09T04:31:37+00:00
linux-debug interrupt

인터럽트

 int (*request_irq)(struct dispc_device *dispc, irq_handler_t handler, void *dev_id);
인터럽트 컨텍스트 활성화 시기
인터럽트 디스크립터
인터럽트 처리 흐름
  1. 인터럽트 발생 : 인터럽트가 발생하면 프로세스는 실행 도중 인터럽트 벡터로 이동, 인터럽트 벡터에서 인터럽트 처리를 마리한 후 다시 프로세스를 실행하기 위해 실행 중인 프로세스의 레지스터 세트를 스택에 저장, IRQ 서 브시스템을 구성하는 함수들이 호출
  2. 인터럽트 핸들러 호출 : 발생한 인터럽트에 대앙하는 인터럽트 디스크립터를 읽어서 인터럽트 핸들러를 호출
  3. 인터럽트 핸들러 실행 : 인터럽트 핸들러에서 하드웨어를 직접 제어하고 유저 공간에 전달
인터럽트 서술자 테이블 (Interrupt Descriptor Table - IDT)
IRQ Chip
리눅스 커널
  /*
   * do_IRQ handles all normal device IRQ's (the special
   * SMP cross-CPU interrupts have their own specific
   * handlers).
   */
  __visible unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
  {
    struct pt_regs *old_regs = set_irq_regs(regs);
    struct irq_desc * desc;
    /* high bit used in ret_from_ code  */
    unsigned vector = ~regs->orig_ax;

    entering_irq();

    /* entering_irq() tells RCU that we're not quiescent.  Check it. */
    RCU_LOCKDEP_WARN(!rcu_is_watching(), "IRQ failed to wake up RCU");

    desc = __this_cpu_read(vector_irq[vector]);

    if (!handle_irq(desc, regs)) {
      ack_APIC_irq();

      if (desc != VECTOR_RETRIGGERED && desc != VECTOR_SHUTDOWN) {
        pr_emerg_ratelimited("%s: %d.%d No irq handler for vector\n",
                 __func__, smp_processor_id(),
                 vector);
      } else {
        __this_cpu_write(vector_irq[vector], VECTOR_UNUSED);
      }
    }

    exiting_irq();

    set_irq_regs(old_regs);
    return 1;
  }
Interrupt Context
common_interrupt:
  addq	$-0x80, (%rsp)			/* Adjust vector to [-256, -1] range */
  call	interrupt_entry
  UNWIND_HINT_REGS indirect=1
  call	do_IRQ	/* rdi points to pt_regs */
  /* 0(%rsp): old RSP */
ENTRY(interrupt_entry)
  UNWIND_HINT_IRET_REGS offset=16
  ASM_CLAC
  cld

  testb	$3, CS-ORIG_RAX+8(%rsp)
  jz	1f
  SWAPGS
  FENCE_SWAPGS_USER_ENTRY
  /*
   * Switch to the thread stack. The IRET frame and orig_ax are
   * on the stack, as well as the return address. RDI..R12 are
   * not (yet) on the stack and space has not (yet) been
   * allocated for them.
   */
  pushq	%rdi

  /* Need to switch before accessing the thread stack. */
  SWITCH_TO_KERNEL_CR3 scratch_reg=%rdi
  movq	%rsp, %rdi
  movq	PER_CPU_VAR(cpu_current_top_of_stack), %rsp

   /*
    * We have RDI, return address, and orig_ax on the stack on
    * top of the IRET frame. That means offset=24
    */
  UNWIND_HINT_IRET_REGS base=%rdi offset=24

  pushq	7*8(%rdi)		/* regs->ss */
  pushq	6*8(%rdi)		/* regs->rsp */
  pushq	5*8(%rdi)		/* regs->eflags */
  pushq	4*8(%rdi)		/* regs->cs */
  pushq	3*8(%rdi)		/* regs->ip */
  UNWIND_HINT_IRET_REGS
  pushq	2*8(%rdi)		/* regs->orig_ax */
  pushq	8(%rdi)			/* return address */

  movq	(%rdi), %rdi
  jmp	2f
1:
  FENCE_SWAPGS_KERNEL_ENTRY
2:
  PUSH_AND_CLEAR_REGS save_ret=1
  ENCODE_FRAME_POINTER 8

  testb	$3, CS+8(%rsp)
  jz	1f

  /*
   * IRQ from user mode.
   *
   * We need to tell lockdep that IRQs are off.  We can't do this until
   * we fix gsbase, and we should do it before enter_from_user_mode
   * (which can take locks).  Since TRACE_IRQS_OFF is idempotent,
   * the simplest way to handle it is to just call it twice if
   * we enter from user mode.  There's no reason to optimize this since
   * TRACE_IRQS_OFF is a no-op if lockdep is off.
   */
  TRACE_IRQS_OFF

  CALL_enter_from_user_mode

1:
  ENTER_IRQ_STACK old_rsp=%rdi save_ret=1
  /* We entered an interrupt context - irqs are off: */
  TRACE_IRQS_OFF

  ret
END(interrupt_entry)
in_interrupt
  #define in_interrupt()		(irq_count())
  #define irq_count()	(preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK \
     | NMI_MASK))
  DECLARE_PER_CPU(int, __preempt_count);
  /*
   * We mask the PREEMPT_NEED_RESCHED bit so as not to confuse all current users
   * that think a non-zero value indicates we cannot preempt.
   */
  static __always_inline int preempt_count(void)
  {
    return raw_cpu_read_4(__preempt_count) & ~PREEMPT_NEED_RESCHED;
  }
인터럽트 핸들러 등록
 static inline int __must_check
 request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
   const char *name, void *dev)
인터럽트 디스크립터
 struct irq_desc {
   struct irq_common_data irq_common_data;
   struct irq_data        irq_data;
   unsigned int __purcpu  *kstat_irqs;
   irq_flow_handler_t     handler_irq;
 #ifdef CONFIG_IRQ_PREFLOW_FASTEOI
   irq_preflow_handler_t  preflow_handler;
 #endif
  struct irqaction        *action;
  unsigned int            status_use_accessors;
 }
 struct irqaction {
   irq_handler_t         handler;
   void                  *dev_id;
   void __percpu         *percpu_dev_id;
   struct irqaction      *next;
   irq_handler_t         thread_fn;
   struct task_struct    *thread;
   struct irqaction      *second;
   unsigned int          irq;
   unsigned int          flags;
   unsigned long         thread_flags;
   unsigned long         thread_mask;
   const char            *name;
   struct proc_dir_entry *dir;
 } ____cacheline_internodealigned_in_smp;
인터럽트가 비활성화되어야 할 때
#define raw_local_irq_enable()		arch_local_irq_enable()
static inline notrace void arch_local_irq_enable(void)
{
  native_irq_enable();
}

static inline void native_irq_enable(void)
{
  asm volatile("sti": : :"memory");
}
인터럽트 디버깅
#!/bin/bash

echo 0 > /sys/kernel/debug/tracing/tracing_on
sleep 1
echo "tracing_off"
echo nop > /sys/kernel/debug/tracing/current_tracer
echo 0 > /sys/kernel/debug/tracing/events/enable
sleep 1

echo 1 > /sys/kernel/debug/tracing/events/sched/sched_switch/enable
sleep 1

echo 1 > /sys/kernel/debug/tracing/events/irq/irq_handler_entry/enable
echo 1 > /sys/kernel/debug/tracing/events/irq/irq_handler_exit/enable

echo 1 > /sys/kernel/debug/tracing/tracing_on
echo "tracing_on"

인터럽트 후반부 처리

Top Half/Bottom Half
인터럽트 후반부 처리 기법 정리
어떤 처리기법을 적용해야 하는가
IRQ 스레드 (threaded IRQ)
IRQ 생성 방법
IRQ 스레드의 실행과정
  1. 인터럽트 핸들러에서 IRQ_WAKE_THREAD를 반환
  2. IRQ 스레드를 꺠움
  3. IRQ 스레드 핸들러인 irq_thread() 함수를 실행
  4. irq_thread() 함수에서 IRQ 스레드 처리 함수 호출

    • handle_irq_event -> handle_irq_event_percpu -> __handle_irq_event_percpu -> __irq_wake_thread -> wake_up_process
    • 만약 IRQ 스레드를 깨우고 싶으면 IRQ_WAKE_THREAD를 반환하고,아니라면 IRQ_HANDLED를 반환하면 된다.

Soft IRQ

Soft IRQ 서비스의 라이프 사이클
  1. 부팅 과정
    • Soft IRQ 서비스 등록
    • open_softirq()
  2. 인터럽트 처리
    • Soft IRQ 서비스 요청
    • raise_softirq()
  3. Soft IRQ 컨텍스트
    • Soft IRQ 서비스 실행
    • __do_softirq()
발생 흐름
  1. 인터럽트가 발생하면 해당 인터럽트 핸들러에서 Soft IRQ 서비스를 요청한다. 이를 위해 raise_softirq_irqoff() 함수를 호출해야 한다. 이는 인터럽트 핸들러에서 IRQ_WAKE_THREAD를 반환하는 동작과 유사하다.
  2. 인터럽트 서비스 루틴 동작이 끝나면 irq_exit() 함수를 호출한다. 여기서 Soft IRQ 서비스 요청 여부를 점검한다. 요청한 Soft IRQ 서비스가 있으면 __do_softirq() 함수를 호출해서 해당 Soft IRQ 서비스 핸들러를 실행한다. 만약 Soft IRQ 서비스 요청이 없으면 riq_exit() 함수는 바로 종료하게 된다.
  3. __do_softirq() 함수에서 Soft IRQ 서비스 핸들러 호출을 끝내면 Soft IRQ 서비스 요청이 있었는지 다시 체크한다. 이미 Soft IRQ 서비 스핸들러 처리 시간이 2ms 이상이거나 10번 이상 Soft IRQ 서비스 핸들러를 처리했다면 다음 동작을 수행한다.
    • wakeup_softirqd() 함수를 호출해서 ksoftirqd 프로세스를 깨움
    • __do_softirq() 함수 종료
  4. ksoftirqd 프로세스 가깨어나 3단계에서 마무리하지 못한 Soft IRQ 서비스 핸들러를 실행한다.
후반부 기법으로 Soft IRQ를 사용하는 상황
Soft IRQ 서비스 설명
우선순위 Soft IRQ 서비스 설명 Soft IRQ 서비스 핸들러
0 HI_SOFTIRQ 가장 우선순위가 높으며 TASKLET_HI로 적용 tasklet_hi_action()
1 TIMER_SOFTIRQ 동적 타이버로 사용 run_timer_softirq()
2 NET_TX_SOFTIRQ 네트워크 패킷 송신용으로 사용 net_tx_action()
3 NET_RX_SOFTIRQ 네트워크 패킷 수신용 사용 net_rx_action()
4 BLOCK_SOFTIRQ 블록 디바이스에서 사용 blk_done_softirq()
5 IRQ_POLL_SOFTIRQ IRQ_POLL 연관 동작 blk_iopoll_softirq()
6 TASKLET_SOFTIRQ 일반 태스크릿으로 사용 tasklet_action()
7 SCHED_SOFTIRQ 스케쥴러에서 사용 run_reblanace_domains()
8 HRTIMER_SOFTIRQ 현재 사용하지 않지만 하위 호환성을 위해 남겨둠 run_timer_softirq()
9 RCU_SOFTIRQ RCU 처리용으로 사용 rcu_process_callbacks()
raise_softirq() 함수
__do_softirq 함수
ksoftirqd 스레드
정리

ksoftirqd 스레드

ksoftirqd 스레드 기상 시점
 asmlinkage __visible void __softirq_entry __do_softirq(void)
 {
   unsigned long end = jiffies + MAX_SOFTIRQ_TIME;
   /* skip */
   restart :
   /* skip */
   while ((softirq_bit = ffs(pending))) {
   /* skip */
     trace_softirq_entry(vec_nr);
     h->action(h);
     trace_softirq_exit(vec_nr);
     /* skip */
     h ++;
     pending >>= softirq_bit;
   }
   pending = local_softirq_pending();
   if (pending) {
     if (time_before(jiffies, end) && !need_resched() &&
       --max_restart)
       goto restart;

     wake_softirqd();
   }
 }

 * Soft IRQ 서비스를 요청할  (raise_softirq_irqoff)

   ```c
   inline void raise_softirq_irqoff(unsigned int nr)
   {
     __raise_softirq_irqoff(nr);

     if (!in_interrupt())
       wakeup_softirqd();
   }
ksoftirqd 핸들러 run_ksoftirqd
static void run_ksoftirqd(unsigned int cpu)
{
  local_irq_disable();
  if (local_softirq_pending()) {
    __do_softirq();
    local_irq_enable();
    cond_resched_cru_qs();
    return;
  }
  local_irq_enable();
}
Soft IRQ 컨텍스트

태스크릿

태스크릿의 실행 주체
태스크릿 자료 구조
struct tasklet_struct
{
  struct tasklet_struct *next;
  unsigned long state;
  atomic_t count;
  void (*func)(unsigned long);
  unsigned long data;
}
  enum
  {
    TASKLET_STATE_SCHED,  /* Tasklet is scheduled for execution. */
    TASKLET_STATE_RUN     /* Tasklet is running (SMP only) */
  }
태스크릿 등록 방법
태스크릿 실행 요청 방법
Soft IRQ 디버깅 해보기
  #!/bin/bash

  echo 0 > /sys/kernel/debug/tracing/tracing_on
  sleep 1
  echo "tracing_off"

  echo 0 > /sys/kernel/debug/tracing/events/enable
  sleep 1
  echo "events disabled"

  echo 1 > /sys/kernel/debug/tracing/events/irq/softirq_raise/enable
  echo 1 > /sys/kernel/debug/tracing/events/irq/softirq_entry/enable
  echo 1 > /sys/kernel/debug/tracing/events/irq/softirq_exit/enable

  sleep 1
  echo "set_ftrace_filter enabled"

  echo 1 > /sys/kernel/debug/tracing/tracing_on
  echo "tracing_on"

인터럽트 정리