[임베디드 시스템] 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한다.
댓글
댓글 쓰기