본문 바로가기

OS /Linux 2.6 kernel basic (LKD)

07. Interrupt (1) Top-Halves


1. Interrupt 

; cpu외부에서 I/O device가 asynchronous하게 processor에게 보내는 electronic signal


* cf ) Polling

;kernel이(processor가) device의 상태를 periodic하게 check

- 장 : polling 시기를 정해 asynchronous한 h/w처리가 발생하지 않음 => 구현 쉬움

- 단 : device가 처리하지 않은 경우도 check하기 때문에 cpu clock 낭비




2. Exception

; cpu 내부에서 synchronous하게 발생하는 interrupt 

ex) programming error, page fault, software interrupt


* synchronous vs asynchronous

synchronous : cpu clock에 맞춰 (cpu clock에 synchronous) 정해진 일을 진행

asynchronous : cpu clock에 상관 없이 (cpu clock에 asynchronous) random time에 event 발생  




3. IRQ line

; interrupt 별로 unique하게 할당하는 h/w line,   

ex) IRQ0: timer, IRQ1: key board


*IRQ (Interrupt Request; Interrupt가 request 던진 것)




4. Top halves & Bottom halves

; interrupt는 asynchronous하기 때문에 다른 실행중 code를 중단하고 context를 진행한다. 따라서 최대한 빨리 끝내야하며 실행중 context가 중단될 수 없다. 현재 꼭 필요한 부분 및 h/w처리 정도만 top halves에서 처리하고 나머지 부분은 bottom halves에서 처리하도록 미루어 중단되었던 context를 최대한 빨리 복구 한다. 


top halves : h/w interrupt가 발생 하였을 때 interrupt request에 대한 최소한의 response를 담당

-interrupt handler가 수행 (빠른 실행 속도) eg. load network packet to memory

bottom halves : 해당 interrupt에 대한 실제 처리 담당

-나중에 할 수 있는 일들 (대량 작업 실행) eg. load된 packet 처리 




5. Interrupt Handler (interrupt service routine)

; interrupt 처리하기 위해 kernel이 실행하는 함수

- device별로 존재 하며 device driver에 포함

- 특정 IRQ line에서 rising edge시 해당 line에 속한 interrupt handler가 실행됨

- interrupt handler가 수행중인 부분 까지를 top halves 라고 함  



6. Interrupt Context

; kernel이 interrupt가 발생함으로써 수행하는 kernel code (top half or bottom half를 실행중인 상태)

- process context가 아님 -> 특정 process에 묶여있지 않음 -> scheduling 대상이 될 수 없음  -> sleeping 불가능 -> sleeping 상태가 될 수 있는 함수를 호출할 수 없음.

- interrupt 발생시 process context -> interrupt context로 switching. 

- 이 때 최소한의 context switching만이 일어남. (cr3 register를 변경, 사용 하지 않음/ virtual memory를 변경, 사용 하지 않음)

=> user space virtual memory에 access 할 수 없음 

- interrupt마다 own stack을 가지고 있는 것이 아니라서 sleeping, scheduling 불가 

- cf) process context : user application에서 system call을 이용해 kernel이 '대신에' 수행하는 kernel code

- scheduling 가능 (preemptible)

- sleeping 가능 

- user space virtual memory에 access 가능 


* interrupt handler stack

- 과거 : interrupt context에서 사용하는 자체적 stack이 없어 stop시킨 process의 kernel mode stack (2page size/ 32bit : 8kb, 64bit : 16kb)를 이용함

- 현재 : processor 마다 자체적인 interrupt stack 추가 (1page size/ 4kb, 8kb)

- interrupt stack의 size가 반으로 줄었으나 page전체를 사용할 수 있게되어 실제적으로 사용할 수 있는 공간은 증가   










7. APIC (Advanced Programmable Interrupt Controller)

; IRQ Line에 들어온 IRQ(interrupt request)를 교통정리해 CPU에게 보내는 역할 수행


(1) Device에서 I/O APIC로 해당 IRQ Line을 통해 electoric signal을 보냄. 

- 해당 IRQ line이 rising될 때 I/O APIC 내부의 IRR 레지스터에서 비트마스크 형태로 맞는 line을 1로 만듬 

- masking 되어 있을 경우 IMR 레지스터의 비트마스크에 masking된 line bit 는 0으로 되어 있고 IRR과 IMR이 AND연산을 통해 출력되기 때문에 해당 Line을 masking out (모든 processor에게 해당 IRQ line을 전달하지 않음) 할 수 있음


(2) I/O APIC 에서 어느 core에서 IRQ 수행할 지 Local APIC로 distribution 수행

- static routing

by referencing redirection table

- dynamic routing

Local APIC안에 TRR(task priority register)을 참조해 해당 local CPU에서 수행중인 task중 가장 priority 낮은 task를 돌리고 있는 CPU에게 전달

같은 lowest priority 가지고 있는 processor가 2개 이상이라면, arbitration을 통해 routing


(3) Local APIC 에서 받은 interrupt를 CPU로 전달

- IRQ Line -> Interrupt Number 변환


(4) CPU는 IDTR의 IDT 참고해 current task를 즉시 중단 시키고 IDT에 해당하는 vector entry에서 interrupt code 수행 

- PIC에 ACK 보냄 (PIC는 ACK를 받으면 다음 Interrupt 보낼 수 있음)

- 해당 IRQ number에 맞는 irq_desc[]의 entry를 참조해 line status 갱신 및 해당 line의 interrupt handlers 수행 




8. Process Context <-> Interrupt Context Switching

1. elfags, cs, eip (+ss, esp ; current 특권수준 바꿔야 될 경우 이전 수준 stack저장) -> stack에 save

- cs의 LSB 2bit를 보고 특권 수준 검사, 새로운 특권 수준으로 전환 해야될 경우 TSS(in tr)에서 해당 process의 새로운 특권수준 stack을 가져오고 그 new stack에 위의 register 값들 저장 

2. IDT의 해당 vector의 entry에서 %cs, %eip를 update (jump), execute instructions 

3. 위에서 바꾼 register제외 나머지 handler가 사용할 register들 current stack(interrupted process's stack)에 저장 

4. do_IRQ()

4-1. thread_union size 조사, stack 저장 및 hard irq stack으로 전환


4KB -> h/w IRQ stack으로 전환

- local cpu의 irq_ctx union이 가진 thread_info의 field에 

irq_ctx union . thread_info . task <-- interrupted process의 descriptor (interrupt context에서 current()시에도 중단된 task가 return될 수 있는 이유) 

irq_ctx union . thread_info . previous_esp  <-- %esp (interrupted process의 stack pointer)

저장


8KB -> process stack 그대로 사용

- %ebp에 current stack에서 진입점 되고 이를 기반으로 %esp로 변환된 stack pointer사용, 이 때 current()시, %ebp를 참조해 interrupted task의 thread_info 반환할 수 있게 됌


4-2. __do_IRQ() 호출, ISR 실행 (해당 ISR은 handle_IRQ_event()호출하여 수행, 실질적인 irq 처리, 자세한 내용 9 항목 참조)


5. irq_exit()

- __do_IRQ()에서 rasing 된(ISR실행할 때, 즉 handle_IRQ_event()내에서 device별로 필요한 job에 해당하는 pending table상의 vector에 rasing 표시) pending job(eg. softirq)이 있다면 invoke_softirq()를 호출해 softirq 처리. 

- softirq context는 interrupt context에서 그대로 사용할 수 있고 ksoftirqd kernel thread를 wake-up해 process context에서 수행할 수 도 있음 (실행시간이 길거나 재수행 반복할때 starvation 방지) 

eg1. softirq 호출 시 2ms이상 수행시간이 벗어날 경우 ksoftirqd 사용

eg2. do_softirq()에서 한 번 수행을 마친 후 pending table을 다시 보고 해당 job이 rasing되어있으면 이를 count하며 다시 반복 처리하는데, 이 때 count 값이 일정 값 이상이 되면 ksoftirqd에게 해당 job을 맡겨 process context상에서 job을 수행, scheduling이 가능해 user space의 task들이 starvation되는 현상을 방지 


6. ret_from_intr()

- current() macro를 통해 thread_info 로딩

- stack에 저장된 cs, eflags학인하여 이전 task가 user mode/ kernel mode 상에서 interrupted됬는지 조사


-user mode => resume_user label code수행

- thread_info's flag 확인 

-need_resched set => schedule() //새로운 task 수행

-else => restore_all label 수행 


-kernel mode => resume_kernel label code수행

- thread_info's preemt_count 확인

- 0 => schedule()

- else => restore_all


-restore_all : iret operation을 통해 stack에 저장된 값 들을 restore하여 interrupted task의 context로 다시 switching







9. __do_IRQ()


-IRQ Status 

[IRQ_DIABLED] interrupt not allowd (masked or not)

[IRQ_WAITING] interrupt 아직 안옴

[IRQ_PENDING] interrupt 받았고 ACK도 했으나 아직 kernel이 서비스 못하고 있음

[IRQ_INPROGRESS] kernel이 ISR 수행중


- 저장된 IRQ number를 추출해 irq_desc[n]에서 해당 IRQ line의 entry point얻음

- PIC로 ACK보냄

- 해당 entry의 status field를 reference하고 update

eg1) interrupt line disable

eg2 ) multi processor의 경우 해당 IRQ를 어느 CPU에게 처리하도록 할 지 결정 

- valid한 handler라면 (사용가능여부, 상태여부, 해당 device에서 호출한 것이 맞을 경우) handle_IRQ_event()

-[IRQF_DIABLED] 설정 안되어 있다해당 line제외 enable로 만듬 

-handler 실행 ([IRQF_SHARED]설정 시 모두 실행)

-[IRQF_SAMPLE_RANDOM] 설정시 interrupt line의 호출 타이밍을 entropy pool로 활용




10. Multi Processor 에서 Interrupt 처리 

; Interrupt 처리하던 processor에 같은 Interrupt 처리 맡김

(why? handler code는 mutual exclusion, do_IRQ() 수행 중 lock을 걸어 주어 어차피 다른 CPU로 같은 다음 interrupt 가 들어가더라도 해당 루틴이 끝날 때 까지 기다려야함, 다른 cpu 놀게 됌)

interrupt handler를 실행 중인 processorIRQ line에 실행 중 상태 플래그 설정 [IRQ_INPROGRESS] 

② IRQ_INPROGRESS 설정된 상태에서 interrupt 발생->다른 processorIRQ lineinterrupt 보류 설정[IRQ_PENDING]

③ [IRQ_PENDING] 설정 후 (PENDING을 설정한) processorinterrupt handler를 종료

④ [IRQ_INPROGRESS] 설정한 processorinterrupt 처리 종료 전 [IRQ_PENDING] 값에 따라 재차 실행

- irq_desc[]의 status field는 비트 마스크 형태, 각각의 flag 설정시 해당 flag bit 1, 아닐 시 0 

- ISR을 실행중일 때 kernel은 해당 line의 irq_desc[]의 status를 IN_PROGRESS로 설정하고 나머지 flag 0으로 만듬

- 다음 Interrupt가 다른 processor에 들어왔을 때 PROGRESS플래그를 set

- ISR 마치고 해당 processor는 다시 irq_desc[]의 status확인, 

- PENDING이 set일 경우, 해당 processor에서 다시 수행 



11. Synchronization in Interrupt

- IRQ가 interrupt handler에 동시 접근 하는 것을 막음  

=> IRQ의 asynchronou한 특성에 의해 발생하는 문제

=> 이를 교통정리 해주기 위해 해당 interrupt 처리중 현재 code를 다른 irq가 preemption하는 것을 막음 (같은 IRQ끼리는 serializing하게 됌)

=> disabling/ enabling macro 사용 (disable시 interrupt handler는 preemption되지 않음)

- 다른 processor가 IRQ Line descriptor(irq_desc[])에 동시 접근 하는 것을 막음 

=> Lock을 사용 (interrupt context는 blocking을 허용하지 않아 spin lock 사)



12. Disabling a specific IRQ Line

- [disable_irq() / enable_irq()]

- 특정 interrup만을 disable (masking out)

- interrupt 처리 중 IRQ line disable 함

- count형식으로 되어 있어 nest되어 호출 될 경우 disable 함수 호출한 만큼 enable 호출해줘야 enabling됌




13. Disabling Local CPU's Interrupt System

- [local_irq_diable() / local_irq_enable()]

- (in x86 architecture) [cli operation / sti operation]

- disable 상태에서 disable또 호출 할 경우 위험 

eg) disable -> {disable() - 작업 - enable()}  -> enable  : disable이 현재 상태인데 enable로 되어 이전 상태가 유지되지 않음 

=> sol ) save/restore 과정을 거쳐 상태 유지 

- [local_irq_save() / local_irq_restore()

-eg) disable -> save() -> {disable() - 작업 - enable()} -> restore() -> disable 




14. Registering an interrupt handler


[int request_irq (unsigned int irq, irq_handler_t handler, unsigned long flagsconst char *name, void *dev]


irq : interrupt 번호
handler : 실제 interrupt handler를 가리키는 함수 포인터
flags : interrupt handler 플래그를 조합한 비트 마스크 값
IRQF_DISABLED : handler 실행 중 processor의 모든 interrupt line disable (대부분 사용 않음)
IRQF_SAMPLE_RANDOM : device가 발생시킨 interrupt entropy pool에 활용함
IRQF_TIMER : 시스템 타이머를 위한 interrupt를 처리
- timer 에서 온 interrupt라는 것을 알림. interrut 통계를 tracking할 때 timer는 기록이 필요 없으므로 이 flag를 보고 discard함.
IRQF_SHARED : 여러 interrupt handler가 같은 interrupt line을 공유
공유하는 모든 handler가 플래그를 사용해야 함 : 지정하지 않을 경우 –EBUSY 오류
name : deviceASCII 이름
/proc/irq /proc/interrupts 에서 사용자와 통신할 때 사용
dev : device를 구별하는 고유한 쿠키 값, interrupt를 공유할 때 사용 (보통 device struct pointer사용)

- registering 과정 중 kmalloc() 호출, kmalloc은 flag에 따라 sleep이 될 수 있음. 따라서 중단될 수 있음 
=> interrupt context내에서 registering 불가


* shared handler 
;같은 Interrupt line상에 여러 handler가 존재
- 해당 line의 모든 handler가 IRQF_SHARED 지정해야함 
- 해당 line이 rising 됬을 때, 모든 handler를 호출
- dev device struct pointer를 이용해 실제로 그 device가 signal을 보냈는지 확인 후 해당 ISR(interrupt handler)실행 (h/w의 도움을 받아 해당 device가 실제로 interrupt 발생시켰는지 확인)




15. Freeing an interrupt handler


[free_irq(unsigned int irq, void *dev)]


- driver가 unload되면 해당 handler를 freeing해야함

- 모든 handler가 제거되면 interrupt line을 disabling



16. Interrupt handler code 정의 (실제로 handler가 수행하는 함수)


[static irqreturn_t intr_handler(int irq, void *dev)


irq : handler에 전달 되는 interrupt 번호, 로그 메시지 출력 외에는 거의 사용되지 않음
dev : 고유 값을 이용하여 handler를 공유하는 여러 device를 구분할 수 있음
Return
IRQ_NONE : 발생한 interrupt가 해당 device에서 생성한 것이 아닐 때
IRQ_HANDLED : interrupt가 올바르게 호출되었고, device가 실제로 interrupt를 생성한 경우
IRQ_RETVAL(val) : 리턴 값(val)을 확인하는(validinterrupt인지 확인하는) 매크로

val 값이 0이 아니면 IRQ_HANDLED 리턴. 0인 경우에는 IRQ_NONE 리턴



















'OS > Linux 2.6 kernel basic (LKD)' 카테고리의 다른 글

08. Interrupt (2) Bottom-Halves [예정]  (0) 2019.02.11
05. System Calls  (0) 2019.02.01
04. Process Scheduling  (0) 2019.01.25
03. Process Management  (2) 2019.01.25