[임베디드 시스템] interrupt

interrupt는 상당히 복잡하고 깊은 내용인건 확실해 보인다. 사실 수업에서 한단원에 걸쳐서 interrupt를 배웠지만, 아직 확실한 이해가 되진 못했다. 차후에 인강으로 보강하면 될 듯 한데, 일단 수업에서 배운 내용을 정리해야겠다.

  • why interrupt?
    • 원래 process가 외부의 신호를 받아들이는 방식은 2가지가 있다. 그것은 바로 polling 방식과 interrupt 방식이다.
    • polling방식은 while문을 통해 지속적으로 외부의 신호를 프로세스에서 듣는 방식이다. 따라서 이 방식은 그 자체로 overhead가 생긴다.
    • 따라서 HardWare단에서 유사시 signal을 보내는 interrupt방식을 통해 이 overhead를 없앨 수 있다. 
      • polling : overhead발생, process는 하던 일을 끝내고 신호를 받아들임.
      • interrupt : overhead없음, process는 하던 일을 중지하고 바로 신호를 받아들인다.
    • 즉 interrupt는 process와 비동기적으로 발생된다.
그림1 interrupt handler

그림2 interrupt flow

  • interrupt flow (그림2를 보자)
    • interrupt 에는 크게 3종류가 있다. 
      • RTC : Real Time Clock으로 timer interrupt이다. 현재 1초에 1000번의(1000HZ) interrupt가 발생하도록 설정되어 있다.
      • Software : Systemcall interrupt / Error interrupt
      • PIC : 가장 일반적인 interrupt로, I/O hardware device에 의해 발생하는 interrupt를 control한다.
    • interrupt가 발생하면 CPU의 (아마도 interrupt관련 register) 변화가 생기고, 이는 kernel에 전달되어 interrupt table 내의 interrupt vector이 호출된다. interrupt vector은 각 interrupt handler 함수가 등록되어 있으며, 이것이 실행된다.
그림3 interrupt coding
  • interrupt coding (그림3을 보자)
    • request_irq(irq, inter_handler, flag, "xxx", &data)
      • irq : 인터럽트 번호(identifier)이다. 이녀석이 irq_desc라는 interrupt table 의 index로서 들어간다. 
      • inter_handler : interrupt 발생시 호출되는 handler 함수이다.
      • flag: SA_INTERRUPT, SA_SHIRQ가 있다.
        • SA_INTERRUPT : fast interrupt이다. (interrupt 가 발생되는 동안 cpu 내 다른 interrupt를 허용하지 않는 방식)
        • SA_SHIRQ : device들 사이에서 interrupt를 공유할 수 있다. 즉, 하나의 irq를 여러 device가 share할 수 있게 설정하는 방식이다.
        • dev_id : shared interrupt에서 사용되는 녀석으로, driver에서 parameter로 handler함수에 보내고 싶은 값이 있을때도 사용할 수 있다.
          • 같은 interrupt가 여러 device에서 handling하고 싶을 경우가 있다. 이 경우에 SA_SHIRQ모드에서는 한 irq에 여러 interrupt handler 함수가 등록되어 있으며, interrupt발생시 순차적으로 수행된다. 따라서 각 device에 맞는 handler함수에서는 해당  interrupt가 자신의 device에서 비롯된 녀석인지 확인해야 한다. 이때 사용하는 것이 dev_id이다.
          • 이런 번거로운짓을 하는 이유는 interrupt의 리소스가 제한되어 있기 때문이다. 즉, 등록할 수 있는 irq갯수가 제한적이기 때문에, 여러 device의 interrupt를 한 irq에 등록해야 할 수 있는 것이다. 
그림4 irq_desq[]

그림5 dev_id 확인


    • inter_handler(irq, dev_id, regs)
      • interrupt발생시 handler되는 함수이다.
      • dev_id를 통해 device 또는 parameter가 전달된다.
  • /proc interface
    • cat /proc/interrupts 로 현재 등록된 interrupt정보를 알 수 있다.
    • cat /proc/stat 로 더 자세한 정보를 알 수 있다.
    • 그림5를 보면 첫번째 열이 irq인데, 두번째, 세번째 열이 CPU내 interrupt발생 횟수, 5번째 열이 dev_id이다.
      그림6 /proc/interrupts

  • implementation Tips
    • interrupt context 는 sleep이 불가능하다.
    • user space와 data transfer이 불가능하다.
      • user space가 아닌, Hard Ware와의 interaction이다.
    • handler함수는 minimum한 시간동안 빠르게 실행되어야 한다. 
      • 이를 보강하기 위해 tasklet, workqueue가 있다.

  • Top , Bottom halves
    • handler함수는 빠르게 실행되야 하므로 실행부위를 분리한다.
      • Top half : 처음에 호출되는 handler 함수이다. 빠르게 실행되어야만 하는 부분이다. 여기서 실행되지 못한 코드는 Bottom half로 분리하여 실행한다.
      • Bottom half : handler함수 밖에서 실행되는 함수로 tasklet, workqueue가 있다.
        • tasklet : interrupt context로 sleep이 불가능하다.
        • workqueue : process context로 sleep이 가능하다.
그림7 tasklet code

그림8 workqueue code
  • request_irq 는 device driver에서 device open 시에 등록하며, free_irq는 device driver에서 device close시에 등록하는 게 좋다. (IRQ는 제한된 자원이므로)

  • Spinlock VS Context switching 관련 개념
    • process 는 세가지의 상태를 지닌다. (정확힌 new, terminate 합해서 5가지 상태)
      • running -> wait -> ready -> running....
      • process 가 wait상태로 되기 전 context switching이 일어나서 process의 정보를 저장한다.
    • critical section
      • 둘 이상이 접근하면 안되는 공유자원을 접근하는 코드의 일부
    • semaphore 
      • 공유된 자원에 둘 이상의 process가 동시에 접근하면 문제가 생기므로, 한번에 하나의 process만 접근이 가능하도록 제한한다.

그림9 semaphore

    • spinlock 
      • critical section에 진입이 불가능하면, 진입이 가능할때까지 loop를 돌면서 재진입하는 방식으로 구현된 lock
      • critical section에 짧은 시간만 접근할시 context switching을 안해서 효율적이다
      • 정리하면, critical section에는 semaphore을 설정해서 spinlock을 걸어주는 듯 하다.
    •  multi processor에서는 spinlock 이 context switching보다 유리하다.
    • find grained : 짧게 lock을 거는 방식. 성능 굿
    • coarse grained : 크게 lock을 거는 방식. 성능 낫굿
  • Disabling and Enabling Interrupts
    •  전체 system에 대해 interrupt 설정
      • disable_irq(irq), enable_irq(irq)
    • 현재 processor에 대해서만 interrupt 설정
      • local_irq_enable() / local_irq_disable()
      • local_irq_save(flags) / local_irq_restore(flag)
        • local_irq_save : 현재 interrupt상태를 flag에 저장하고 disable시킨다.
        • local_irq_restore : 현재 flag로 restore한다.
        • 즉 previous state에 맞는 행동을 취할 수 있다는 점이 우수하다. 전 상태로 돌아가는 부분이 핵심

  • Blocking I/O
    • read(fd, buf, 1000)이 blocking 이라면, 1000개를 읽을 때까지 user단에서 wait한다.
      • how to wait?
        • 루프를 돌면서 기다린다 -> CPU 낭비
        • wait -> sleep 시키고, interrupt handler에서 1000개를 받으면 process를 wake up시킨다.
    • non blocking이면 1000개 읽을때까지 기다리지 않고 읽은 그대로 리턴함.

  • Blocking Processes (그림10을 보자)
    • process1는 blocking I/O read를 호출했기 때문에 read될때까지 sleep에 빠진다.
    • 허면 CPU는  context switching과정을 거쳐 process2를 수행한다.
    • process1에서 주황색지점이 끝나면 read가 끝나고 interrupt handler에 의해 wake up 된다. 
    • 하지만 이때 process1는 바로 running 상태로 가지 못하고, ready 상태에 놓여있게 된다. process2가 아직 마무리 되지 않았기 때문이다.
    • process2에서 context switching이 일어나고 나면, process1은 running상태가 된다.
    • 즉 process1의 wake시킨 시점과 실제 running시점 사이에는 gap이 존재하며, 이 과정을 고려해서 코딩하지 않으면 문제가 생길 수 있다. 
    • 다시말하면 여기서 wait을 할때 if문이 아닌 while문을 사용해서 condition이 자신도 모르게 바뀔 수 있는 가능성을 차단해야 한다. (즉, 조건을 다시 check해야 한다.)
    • 즉 그림11에서 if문이 아닌 while문을 걸어줘야 안전한 코딩이 되는 것이다.

그림10 blocking process
그림11 interruptible_sleep_on
  • API for Sleeping Processes
    • interruptible
      • 외부 signal에 의해 깨어날 수 있다. (ex: kill)
      • interruptible_sleep_on(wait_queue_head_t *queue)
      • wake_up_interruptible()
    • uninterruptible
      • 외부 signal도 통하지 않아 더 불완전한 방법이다.
      • sleep_on()
      • wake_up()


그림12 blockio_dev.c (driver)

그림13 blockio_app.c

  • Example (그림12, 13을 보자)
    • blockio_dev.c (driver)
      • open시에 request_irq() 함수를 통해 interrupt를 등록한다. 
      • read시에 interruptible_sleep_on으로 count만큼 read되지 않으면 sleep된다.
      • 그러면 interrupt handler에서 count가 채워질때까지 기다리다가, 다 채워지면 process 를 wake up한다.



댓글

가장 많이 본 글