Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

8200
629
98815


[Linux][Kernel] 인터럽트 - request_threaded_irq 기초 [Linux][Kernel] IRQ(Interrupt)

개요
하드웨어 관점에서 인터럽트는 인터럽트 콘트롤러에 의해 인가되는 전기신호 인데요. 이 신호는 인터럽트 컨트롤러 입력 핀으로 전달됩니다. 각각 보드마다 인터럽트 컨트롤러는 연결된 여러 개의 인터럽트 배선을 하나의 배선에 묶어서 프로세서에 전달해준다. 인터럽트를 받으면 인터럽트 콘트롤러는 프로세서에 신호를 보낸다. 신호를 감지하면 프로세서는 인터럽트를 처리하기 위해 현재 실행하던 일을 잠시 중단한다. 그다음 프로세서는 커널에 인터럽트가 일어났다는 것을 알리고, 커널은 상황에 맞게 인터럽트를 처리한다.

위에서 설명한 인터럽트가 처리되는 과정은 어느 플렛폼이나 거의 유사하다.

주의해야 할 점
인터럽트가 올라오면 인터럽트 서비스 루틴(ISR)이 호출이 된다. 이 때 프로세서는 기존에 하던 일을 잠시 멈춘 상황이라서  ISR의 실행 속도는 빨라야 한다. 반드시 ISR에서 처리해야 할 일은 ISR 에서 마무리를 하고 나머지 일은 워크큐나 태스크릿을 통해서 후반부 프로세스 단계에서 마무리가 되도록 한다.

인터럽트 컨텍스트 VS 커널 스래드 컨택스트
ISR을 처리하는 방식은 2가지가 있다.
request_irq, request_threaded_irq
- 두 API 모두 인터럽트를 처리하는 ISR 을 등록하는 경우에 사용하며, ISR 타입 및 등록 방법은 동일하다.

request_irq : 주로 EINT 를 처리하는 경우에 사용하며, interrupt context 로의 context switching 이 발생한다. 즉, irq 를 등록할 때 지정하는 ISR 은 interrupt context 에서 실행 됨을 의미한다.
ISR은 절대 자면 안되고 빠르고 간단해야 한다. 그리고 인터럽트가 발생하기 전의 프로세스의 스택을 사용하므로 로컬 변수는 스택을 많이 잡지 않아야 한다.

request_threaded_irq : 마찬가지로 EINT 를 처리하는 경우에 사용하며, interrupt context 로의 context switching 이 발생하지 않는다. kernel thread 를 생성하여 kthread 의 수행 함수가 등록시에 사용하는 ISR 타입의 함수가 될 뿐이다.  다시 말하면, 각각의 핸들러가 별도 커널 쓰레드로 동작하게 하려면 request_threaded_irq() 함수를 이용해 등록하면 된다. 이 방식은 핸들러  함수(quick_check_handler)가 하나 더 있는데, 인터럽트가 발생하면 이 핸들러가 먼저 실행된 후 인터럽트가 자신의 것이 맞는지 여부와, 그렇다면 그에 따라 인터럽트 핸들러 쓰레드를 깨울지, 직접 인터럽트 문맥에서 실행할 지 등을 결정하는 리턴값을 돌려주면 그에 따라 인터럽트 핸들러 쓰레드가 동작하는 방식이다. bottom half 기법이라 볼 수 있으며, tasklet 이나 workQ 를 대체 가능한 구조이다.


- 언제 사용하나?
request_irq : irq 발생시 바로 isr 을 처리해주고자 할 때 사용 가능하다. 처리하고자 하는 인터럽트가 빨리 실행되어야 할 때 등록할 수 있다. tophalf 의 형태라 볼 수 있다.

request_threaded_irq : 주로 uevent 나 usermode helper 를 연동해서 사용하고자 할 때 사용 가능하다.
                                일례로 request_irq 로 등록한 isr 에서 uevent 나 usermode helper를 이용하면? 
                                당신의 리눅스 machine 을 자살시키는 좋은 방법이 될 것이다.

실제 샘플 소스는 아래와 같다.
#ifdef DEBUG_IRQ_STUDY_BSP
    error = request_threaded_irq(data->irq, NULL, mxt_interrupt,
            IRQF_TRIGGER_LOW | IRQF_ONESHOT | IRQF_NO_SUSPEND, client->name, data);
#else
    error = request_irq(data->irq, mxt_interrupt,
            IRQF_TRIGGER_LOW | IRQF_ONESHOT | IRQF_NO_SUSPEND, client->name, data);
#endif

mxt_interrupt() int handler의 바디에 보면 아래와 같은 코드가 있다.
...
        input_report_abs(input_dev, ABS_MT_POSITION_X, x);
        input_report_abs(input_dev, ABS_MT_POSITION_Y, y);
        input_report_abs(input_dev, ABS_MT_PRESSURE, amplitude);
        input_report_abs(input_dev, ABS_MT_TOUCH_MAJOR, area);
        input_report_abs(input_dev, ABS_MT_ORIENTATION, vector);
...

이 ISR을 request_irq()함수를 사용하여 등록을 하였더니, 커널은 아래와 같은 유언을 남기고 뻗어버렸다.
[   21.719319 01-01 10:20:37.054] [callstack mxt_interrupt,2449] task[InputReader]========= 
[   21.719382 01-01 10:20:37.054] BUG: scheduling while atomic: InputReader/1039/0x00010001
[   21.719417 01-01 10:20:37.054] [<c00169a8>] (unwind_backtrace+0x0/0x144) from [<c083e0e8>] (dump_stack+0x20/0x24)
[   21.719432 01-01 10:20:37.054] [<c083e0e8>] (dump_stack+0x20/0x24) from [<c083edfc>] (__schedule_bug+0x50/0x5c)
[   21.719444 01-01 10:20:37.054] [<c083edfc>] (__schedule_bug+0x50/0x5c) from [<c0845b54>] (__schedule+0x7c4/0x890)
[   21.719455 01-01 10:20:37.054] [<c0845b54>] (__schedule+0x7c4/0x890) from [<c0845d70>] (schedule+0x40/0x80)
[   21.719468 01-01 10:20:37.054] [<c0845d70>] (schedule+0x40/0x80) from [<c0843bc0>] (schedule_timeout+0x190/0x33c)
[   21.719480 01-01 10:20:37.054] [<c0843bc0>] (schedule_timeout+0x190/0x33c) from [<c08450b4>] (wait_for_common+0xb8/0x15c)
[   21.719491 01-01 10:20:37.054] [<c08450b4>] (wait_for_common+0xb8/0x15c) from [<c0845198>] (wait_for_completion_timeout+0x1c/0x20)
[   21.719504 01-01 10:20:37.054] [<c0845198>] (wait_for_completion_timeout+0x1c/0x20) from [<c04dbd88>] (tegra_i2c_xfer_msg+0x380/0x958)
[   21.719517 01-01 10:20:37.054] [<c04dbd88>] (tegra_i2c_xfer_msg+0x380/0x958) from [<c04dc674>] (tegra_i2c_xfer+0x314/0x438)
[   21.719531 01-01 10:20:37.054] [<c04dc674>] (tegra_i2c_xfer+0x314/0x438) from [<c04d7bb8>] (i2c_transfer+0xc4/0x128)
[   21.719546 01-01 10:20:37.054] [<c04d7bb8>] (i2c_transfer+0xc4/0x128) from [<c04b3174>] (__mxt_read_reg+0x70/0xc8)
[   21.719560 01-01 10:20:37.054] [<c04b3174>] (__mxt_read_reg+0x70/0xc8) from [<c04b6f60>] (mxt_read_and_process_messages+0x58/0x1648)
[   21.719572 01-01 10:20:37.054] [<c04b6f60>] (mxt_read_and_process_messages+0x58/0x1648) from [<c04b85c8>] (mxt_interrupt+0x78/0x144)
[   21.719588 01-01 10:20:37.054] [<c04b85c8>] (mxt_interrupt+0x78/0x144) from [<c00e8000>] (handle_irq_event_percpu+0x88/0x2ec)
[   21.719601 01-01 10:20:37.054] [<c00e8000>] (handle_irq_event_percpu+0x88/0x2ec) from [<c00e82b0>] (handle_irq_event+0x4c/0x6c)
[   21.719614 01-01 10:20:37.054] [<c00e82b0>] (handle_irq_event+0x4c/0x6c) from [<c00ead98>] (handle_level_irq+0xbc/0x118)
[   21.719626 01-01 10:20:37.054] [<c00ead98>] (handle_level_irq+0xbc/0x118) from [<c00e7780>] (generic_handle_irq+0x3c/0x50)
[   21.719642 01-01 10:20:37.054] [<c00e7780>] (generic_handle_irq+0x3c/0x50) from [<c0311138>] (tegra_gpio_irq_handler+0xa8/0x124)
[   21.719655 01-01 10:20:37.054] [<c0311138>] (tegra_gpio_irq_handler+0xa8/0x124) from [<c00e7780>] (generic_handle_irq+0x3c/0x50)
[   21.719669 01-01 10:20:37.054] [<c00e7780>] (generic_handle_irq+0x3c/0x50) from [<c000fa04>] (handle_IRQ+0x5c/0xbc)
[   21.719682 01-01 10:20:37.054] [<c000fa04>] (handle_IRQ+0x5c/0xbc) from [<c0008504>] (gic_handle_irq+0x34/0x68)
[   21.719694 01-01 10:20:37.054] [<c0008504>] (gic_handle_irq+0x34/0x68) from [<c000ec40>] (__irq_svc+0x40/0x70)

요즘에 터치나 센서와 같은 인풋 디바이스는 request_threaded_irq함수를 사용하여 인터럽트를 처리하는 것이 대세이고 나머지는
각각 디바이스 드라이버의 시나리오에 따라 구현을 하면 되겠다.

인터럽트 활성화 비활성화
아래와 같이 local_irq_disable() ,local_irq_enable() 두 가지 함수를 사용하여 인터럽트가 발생하는 것을 막는다.
호출하기 전에 이미 인터럽트가 비활성화된 상태에서 local_irq_disable() 함수를 호출하는 것은 위험하다. 애초에 비활성화 상태였음에도 불구하고 대응하는 local_irq_enable()함수 때문에 무조건 인터럽트가 활성화된다.
static int rs_360_tiocmset(struct tty_struct *tty,
               unsigned int set, unsigned int clear)
{
    local_irq_disable();
    serial_out(info, UART_MCR, info->MCR);
    local_irq_enable();
#endif
    return 0;
}

인터럽트 시스템 상태
<linux/hardirq.h>에 있는 두 매크로는 커널의 현재 컨텍스트 상태를 확인해준다.
in_interrupt()
in_irq()

첫번 째 매크로가 더 유용해 보인다. 커널이 인터럽트를 처리하고 있는 경우에 0이 아닌 값을 반환한다.
디버깅할 때 정말 유용하게 사용하면 좋겠다.

아래는 reqeust_irq로 등록한 ISR에서 찍힌 로그이다.

ISR
static irqreturn_t cputimer_interrupt(int irq, void *dev_id)
{
    unsigned int cpu;

    int cutIntStatus = in_interrupt();
        printk("=====!!!!!current task[%s]=======, int[%d]\n", current->comm, cutIntStatus);

    return IRQ_HANDLED;
}

로그(인터럽트 콘텍스트)
[    0.072900 01-01 00:00:11.945] =====!!!!!current task[swapper/2]=======, int[65536]
[    0.073010 01-01 00:00:11.945] =====!!!!!current task[swapper/0]=======, int[65536]
[    0.073361 01-01 00:00:11.946] =====!!!!!current task[swapper/1]=======, int[65536]
[    0.073462 01-01 00:00:11.946] =====!!!!!current task[swapper/3]=======, int[65536]
[    0.073897 01-01 00:00:11.946] =====!!!!!current task[swapper/2]=======, int[65536]
[    0.074010 01-01 00:00:11.946] =====!!!!!current task[swapper/0]=======, int[65536]

아래는 request_threaded_irq()함수로 등록한 ISR(mxt_interrupt)에서 in_interrupt()값이다.
역시나 0이다. 

[   60.197356 01-01 11:05:21.331] [callstack mxt_interrupt,2449] task[irq/297-atmel_s] int[0]========= 
[   60.198108 01-01 11:05:21.332] [callstack mxt_interrupt,2449] task[irq/297-atmel_s] int[0]========= 
[   60.205521 01-01 11:05:21.339] [callstack mxt_interrupt,2449] task[irq/297-atmel_s] int[0]========= 



5장 인터럽트 핸들링
5.7 인터럽트 디버깅
   5.7.3 인터럽트 핸들러 함수 파악하기
   5.7.4 인터럽트 핸들러 실행 시간 점검하기





    덧글

    댓글 입력 영역