Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

0112
737
82110


[리눅스커널][인터럽트] 인터럽트 디스크립터(struct irq_desc)에 대해서 5장. 인터럽트 핸들링

인터럽트 디스크립터는 인터럽트 세부 속성과 동작 흐름을 표현하는 자료구조로 struct irq_desc 구조체입니다. 이번 절에서는 struct irq_desc 구조체와 세부 필드가 리눅스 커널 내부 함수에서 어떻게 바뀌는지 살펴보겠습니다. 

5.5.1 인터럽트 디스크립터란

인터럽트 디스크립터는 각각 인터럽트를 표현하는 자료구조로 struct irq_desc 구조체입니다.
struct irq_desc 구조체 필드를 소개하기 전에 인터럽트 디스크립터란 용어에 대해 짚어 보겠습니다.

인터럽트 디스크립터는 인터럽트와 디스립터의 합성어입니다. 용어를 그대로 풀면 인터럽트 디스크립터는 인터럽트의 디스크립터라고 말할 수 있습니다. 인터럽트란 단어의 의미는 조금 알겠는데 디스크립터란 단어가 생소하게 들립니다.

리눅스 커널에서 디스크립터의 의미

디스크립터는 무엇일까요? 디스크립터란 커널이 특정 드라이버나 메모리와 같은 매우 중요한 객체를 관리하려고 쓰는 자료구조입니다. 커널 입장에서 이런 객체를 디스크립터로 관리합니다. 디스크립터에서 각 모듈에 대한 속성과 상세 동작 내역을 저장합니다.

커널이 중요하게 관리하는 모듈과 객체 종류가 뭘까요? 가장 먼저 프로세스가 떠오르지 않나요? 리눅스 커널에서 태스크 디스크립터로 프로세스를 관리합니다. 또한 메모리 시스템에서 페이지를 빼놓을 수 없습니다. 그리고 가상 파일 시스템도 디스크립터로 관리합니다. 이들을 관리하는 자료구조가 태스크 디스크립터, 페이지 디스크립터 그리고 파일 디스크립터입니다.
 - 태스크 디스크립터: struct task_struct
 - 페이지 디스크립터: struct page
 - 파일 디스크립터: struct files_struct

인터럽트 디스크립터 소개하기 

이렇게 인터럽트 디스크립터란 인터럽트 상세 동작과 인터럽트 속성을 모두 담고 있는 자료이자 객체입니다. 커널에서는 발생하는 인터럽트 종류만큼 인터럽트 디스크립터가 있습니다. 만약 인터럽트 개수가 34개면 이를 관리하는 34개의 인터럽트 디스크립터가 있습니다.

이미 설명했듯이 request_irq() 함수를 써서 인터럽트 번호와 인터럽트 핸들러를 등록합니다. 
커널은 인터럽트가 발생하면 어떤 정보를 참고해서 이미 등록한 인터럽트 핸들러를 호출할까요? 정답은 인터럽트 디스크립터입니다. 

각 디바이스 드라이버에서 인터럽트 번호, dev_id 그리고 인터럽트 핸들러 정보를 채워 request_irq() 함수를 호출합니다. 
[https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/interrupt.h]
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
    const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

request_irq() 함수에서 바로 호출되는 request_threaded_irq() 함수에서 커널은 이 파라미터들 이외에 다른 정보를 채워서 인터럽트 디스크립터를 갱신합니다. 

인터럽트 디스크립터 구조체 알아보기

인터럽트 디스크립터 구조체 타입은 다음과 같습니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/irqdesc.h]
01 struct irq_desc {
02 struct irq_common_data irq_common_data;  
03 struct irq_data irq_data;     
04 unsigned int __percpu *kstat_irqs;    
05 irq_flow_handler_t handle_irq;
06 #ifdef CONFIG_IRQ_PREFLOW_FASTEOI
07 irq_preflow_handler_t preflow_handler;
08 #endif
09 struct irqaction *action;    
10 unsigned int status_use_accessors;

각 구조체 필드를 살펴보겠습니다. 

struct irq_common_data irq_common_data;
커널에서 처리하는 irq_chip 메소드들에 대한 정보를 담고 있습니다.

struct irq_data irq_data;   
인터럽트 번호와 해당 하드웨어 핀 번호를 알 수 있습니다.

unsigned int __percpu *kstat_irqs;   
인터럽트가 발생한 횟수가 저장됩니다.

struct irqaction *action;
struct irq_desc 구조체 필드는 인터럽트 주요 정보를 저장합니다.
인터럽트 속성 중 핵심 데이터는 struct irq_desc 구조체 irq_data 필드에 저장돼 있습니다.

irq_data 필드 타입은 struct irqaction 구조체이며 선언부는 다음과 같습니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/interrupt.h]
01 struct irqaction {
02 irq_handler_t handler  
03 void *dev_id;            
04 void __percpu *percpu_dev_id;  
05 struct irqaction *next;
06 irq_handler_t thread_fn;         
07 struct task_struct *thread;
08 struct irqaction *secondary;
09 unsigned int irq;                
10 unsigned int flags;              
11 unsigned long thread_flags;
12 unsigned long thread_mask;
13 const char *name;
14 struct proc_dir_entry *dir;
15 } ____cacheline_internodealigned_in_smp;

struct irqaction 구조체 중 중요한 필드를 보겠습니다.

irq_handler_t handler;
인터럽트 핸들러를 저장합니다.

void *dev_id; 
인터럽트 핸들러에 전달하는 매개인자입니다. 보통 디바이스 드라이버에서 지정한 데이터가 저장됩니다. 다양한 자료 구조를 지원해야 하므로 void 타입입니다.

irq_handler_t thread_fn;
인터럽트를 threaded IRQ 방식으로 처리할 때 IRQ 스레드 핸들러 주소를 저장합니다. 대부분 이 필드는 NULL입니다.

unsigned int irq;
인터럽트 번호입니다.

unsigned int flags;
인터럽트 플래그 설정 필드입니다. 아래 비트 플래그를 OR 연산 후 저장합니다. 
[https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/interrupt.h]
#define IRQF_TRIGGER_NONE 0x00000000
#define IRQF_TRIGGER_RISING 0x00000001
#define IRQF_TRIGGER_FALLING 0x00000002
#define IRQF_TRIGGER_HIGH 0x00000004
#define IRQF_TRIGGER_LOW 0x00000008

위에서 살펴본 바와 같이 인터럽트 디스크립터로 각 인터럽트에 대한 상세 속성을 알 수 있습니다. 인터럽트 디스크립터는 추가로 인터럽트 실행 정보도 저장합니다. 

인터럽트 디스크립터로 인터럽트 처리 상태 확인하기

현재 인터럽트가 어느 단계로 처리되는지 알고 싶으면 어떤 변수를 참고하면 좋을까요?
이럴 때 인터럽트 디스크립터 struct irq_desc 구조체 irq_data.state_use_accessors 필드를 확인하면 됩니다.

아래 enum 타입 선언부만 봐도 어떤 의미인지 알 수 있을 것 같습니다. 
이 중에서 IRQD_IRQ_INPROGRESS 값이 어떻게 업데이트되는지 점검해보겠습니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/include/linux/irq.h] 
enum {
IRQD_TRIGGER_MASK = 0xf,
IRQD_SETAFFINITY_PENDING = (1 <<  8),
IRQD_NO_BALANCING = (1 << 10),
IRQD_PER_CPU = (1 << 11),
IRQD_AFFINITY_SET = (1 << 12),
IRQD_LEVEL = (1 << 13),
IRQD_WAKEUP_STATE = (1 << 14),
IRQD_MOVE_PCNTXT = (1 << 15),
IRQD_IRQ_DISABLED = (1 << 16),
IRQD_IRQ_MASKED = (1 << 17),
IRQD_IRQ_INPROGRESS = (1 << 18),  //<<  0x40000
IRQD_WAKEUP_ARMED = (1 << 19),
};

우선 struct irq_desc 구조체 irq_data.state_use_accessors 필드는 irqd_set() 함수에서 변경됩니다.

다음 코드를 살펴보겠습니다. 
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/irq/handle.c]
01 irqreturn_t handle_irq_event(struct irq_desc *desc)
02 {
03 struct irqaction *action = desc->action;
04 irqreturn_t ret;
05
06 desc->istate &= ~IRQS_PENDING;
07 irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);  
08 raw_spin_unlock(&desc->lock);
09
10 ret = handle_irq_event_percpu(desc, action);
11
12 raw_spin_lock(&desc->lock);
13 irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);   
14 return ret;
15 }

인터럽트 핸들러는 10 번째 줄인 handle_irq_event_percpu() 함수 서브 루틴에서 실행합니다. 
이 함수 전후로 07번째와 10 번째 줄, 인터럽트 핸들러를 처리하기 전후로 irqd_set() 함수가 호출되어 struct irq_desc 구조체 irq_data.state_use_accessors 필드 변수 비트를 설정하고 해제합니다.


인터럽트 디스크립터를 코어 덤프에서 확인해보기

이어서 리눅스 코어 덤프에서 인터럽트 디스크립터 세부 필드를 확인해보겠습니다.

아래는 Trace32로 인터럽트 디스크립터를 올려본 결과인데, 328번 인터럽트 번호에 struct irq_desc.irq_data.state_use_accessors는 0x4002입니다.
 (struct irq_desc *) (struct irq_desc *)0xDF4533C0 = 0xDF4533C0 = __bss_stop+0x1DD59510 -> (
    (struct irq_data) irq_data = (
      (unsigned int) irq = 328 = 0x0148, 
      (long unsigned int) hwirq = 13 = 0x0D, 
      (unsigned int) node = 0 = 0x0,
      (unsigned int) state_use_accessors = 16386 = 0x4002,

위 디버깅 정보를 해석하면 현재 인터럽트 핸들러 내부에서 어떤 코드가 실행되고 있다고 분석할 수 있습니다.

위에서 설명한 정보들은 실제 리눅스 코어 덤프에서 크래시 유틸리티(Crash-Utililty) 프로그램으로 인터럽트 디스크립터 정보를 볼 수 있습니다. 아래는 인터럽트 디스크립터 주소 eb4f3000를 통해 확인해본 각 멤버 변수들 값입니다.

[1]: 인터럽트 번호는 248
[2]: 인터럽트 핸들러는 gpio_keys_gpio_isr
[3]: dev_id는 0xe9505198입니다.
struct irq_desc {
  irq_data = {
    mask = 0,
    irq = 248,  //<<--[1]
    hwirq = 91,
    node = 4294967295,
    state_use_accessors = 3,
    chip = 0xc193646c 
    domain = 0xeb471440,
    parent_data = 0x0,
    handler_data = 0x0,
    chip_data = 0xebbb6a58,
    msi_desc = 0x0,
    affinity = {{
        bits = {255}
      }}
  },
  kstat_irqs = 0xc18d83c4,
  handle_irq = 0xc017b714 <handle_edge_irq>,
  action = 0xe956e400,

crash> struct irqaction 0xe956e400
struct irqaction {
  handler = 0xc07903ac <gpio_keys_gpio_isr>,  //<<--[2]
  dev_id = 0xe9505198, //<<--[3]
  percpu_dev_id = 0x0,
  next = 0x0,
  thread_fn = 0x0,
  thread = 0x0,
  irq = 248,
  flags = 131,
  thread_flags = 0,
  thread_mask = 0,
  name = 0xc342c00c "volume_up",
  dir = 0xe9579540
}

dev_id 주소가 0xe9505198인데, 이 void 타입 주소는 아래 코드에서 struct gpio_button_data 구조체로 캐스팅합니다.
static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id)
{
struct gpio_button_data *bdata = dev_id;

BUG_ON(irq != bdata->irq);

이 값을 크래시 유틸리티로 확인하면 아래와 같습니다.
crash> struct gpio_button_data 0xe9505198
struct gpio_button_data {
  button = 0xe9555418,
  input = 0xe9502840,
  timer = {
    entry = {
      next = 0x0,
      prev = 0x200
    },
    expires = 4294938339,
    base = 0xec0d0040,
    function = 0xc0790394 <gpio_keys_gpio_timer>,
    data = 3914355096,
    slack = -1,
    start_pid = -1,
    start_site = 0x0,
    start_comm = "000000000000000000000000000000"
  },
  work = {
    data = {
      counter = 64
    },
    entry = {
      next = 0xe95051d8,
      prev = 0xe95051d8
    },
    func = 0xc0790d0c <gpio_keys_gpio_work_func>


이어서 인터럽트 디스크립터에서 각 CPU 별로 발생한 인터럽트 발생 개수를 어떻게 저장하는지 살펴보겠습니다. 

# Reference (인터럽트 처리)

    핑백

    덧글

    댓글 입력 영역