여기서는 INT3 interrupt 가 발생했을 때, 실제 어떻게 동작하는지 대략적으로 살펴보자.
INT3 이 발생하면, 정해진 handler (interrupt handler) 가 수행된다.
어떤 경로를 통해 interrupt handler 가 호출되는 지를 확인해 보도록 하자. os 부분은 source code 를 확인할 수 있는 linux 를 택했다.
<그림. interrupt call> 을 먼저 확인하자. interrupt call 의 그림을 중심으로 이야기를 풀어나가 보자.
전체적인 흐름은 밑에 "Linux 에서 INT3 동작 정리" 를 확인하자.
interrupt 동작방식
from operating-system concepts 10th edition- I/O 작업이 실행되기 위해서, device driver 가 적절한 register 들을 device controller 에 load 해야 한다.
- 그러면, device controller 는 이 registers 의 내용을 확인해서 어떤 동작을 할지 정한다.
- controller 는 device 에서부터 local buffer 로 data 전송을 시작한다.
- data 전송이 끝나면, device controller 가 device driver 에게 작업이 끝났다고 알려준다.
- 그리고 나서 device driver 가 os 의 다른 부분으로 control 을 넘겨준다.
- 이때 만약 read i/o 작업이라면 data 의 pointer 를 넘기는 식으로 결과 data 를 주기도 한다.
- 다른 작업에서는 status 값을 준다.
- controller 가 작업이 끝난것을 device driver 에게 알려줄때 interrupt 를 사용하게 된다.
- cpu 는 interrupt-request line 이라는 wire 를 갖는다. 그래서 cpu 가 매 instruction 을 수행할 때마다, 그것을 sense 한다.
- 그래서 만약 controller 가 signal 을 interrupt-request line 에 보냈다면,
- cpu 가 그것을 인지하고, interrupt number 를 읽고, interrupt-handler routine 으로 jump 한다. 이때 이 interrupt number 가 interrupt vector 의 index 가 된다.
- interrupt handler 가 작업중에 변경이 돼야 하는 state 를 저장하고,
- interrupt 의 원인을 파악, 필요한 작업을 하고, 끝난후 state 를 돌려놓는다.
- 그리고 return_from_interrupt instruction 을 수행해서 CPU 를 interrupt 일어나기 이전의 execution state 로 돌려놓는다.
IDT
IDT 가 무엇인지 알아보자.interrupt vector
interrupt vector 는 intrrupt handler 의 memory 주소이다.[ref. 4]그리고 이 interrupt vector 를 여러 개 모아놓은 table 이 intrrupt vector table 이다.
IDT
Interrupt Descriptor Table(IDT) 는 interrupt vector table 을 구현해 놓은 것으로 x86 architecture 에서 쓰인다. 이 녀석은 cpu(processor) 가 intterupt 나 exception 발생 시에 쓰게 된다. IDT 는 총 256개의 interrupt vector 로 되어 있다.exception 의 종류
exception 는 2개의 category 로 나눌 수 있다.- Hardware generated exceptions : Processor 가 만들어 내는 것(Faults, Traps, Aborts)
- Software generated exceptions : int, int3 같은 programmed exceptions
IDT 에서 위치에 따른 interrupt 종류
IDT 는 아래 3가지 경우에 쓰인다.- software inturrupt (software exceptions)
- hardware inturrupt ( hardware exceptions)
- processor exceptions
- 0~31 : hardware generated exceptions[ref. 3]
- 32~47 : maskable interrupts(such as, IRQs)
- 48~255 : software interrupts
IDT 는 이름에서 알 수 있듯이 table 이다. 그럼 이 table 의 하나의 entry 는 어떻게 구성되어 있을까. 이 entry 들을 intel 에서는 gate 라고 하는 듯 하다. 이 gate 에서 알아보자.
intel gates
intel CPU 에서 제공하는 mode
- real mode
- protected mode
4가지 previliege level
- ring0 : kernel 은 이 level 에서 실행된다. kernel 은 그래서 cpu 의 모든 register, 모든 hardware 와 memory 에 접근 가능하다.
- ring1
- ring2
- ring3 : 보통 user application 이 실행되는 level
gates
protected mode 에서 IDT 는 8-byte의 descriptor 의 배열로 되어 있다. 이 8-byte descriptor 가 IDT 의 entry 가 된다.이 descriptor 들은 셋 중에 하나다.
- interrupt gates
- trap gates
- task gates
1, 2 는 code 가 있는 memory loaction 을 가리킨다.
이 둘의 차이는
- interrupt gates 는 hardware interrupt 를 위해 만들어진 녀석이라 interrupt 하는 것 이외에 다른 일은 불가능하다.
- trap gates 는 software interrupt 나 exceptions 을 처리하는 데에 쓰인다.
- task gates 는 현재 task-stae 가 active 인 segment 를 switch 가 되게 만든다.
protected mode IDT 는 물리적인 memory 어느 곳에 상주하지만, 딱히 정해진 위치에 있지 않다. 그래서 이 녀석의 주소를 저장하고 있을 곳이 필요하다. 그 녀석이 IDTR 이다. 그리고, 이 register에 IDT 의 주소를 load 해 줄 때 쓰는 명령어가 LIDT 이다.
< IDT Gate descriptor / from: ref.2 Figure 6-2 > |
IDTR
CPU에는 IDT 를 위한 register(IDTR)가 하나 있는데, 얘가 table 의 physical base address 주소와 length 를 가지고 있게 된다.IDTR 은 base address 를 저장하는 부분 4byte 와 length(limit) 를 저장할 수 있는 부분 2byte 로 되어 있다. limit 은 IDT 의 마지막 1byte 의 주소를 알아내기 위한 값이다. 그래서 8N-1 의 계산을 하게 된다. 1개의 interrupt vector 가 있으면 마지막 byte 는 7 이 된다.
그래서 interrupt 가 발생하면 그 숫자에 8을 곱해서 base address 에 더해서 나온 주소(이 주소를 A라 하자.)에 해당 descriptor 가 있게 된다.
이 A 주소가 존재하는 주소인지에 대한 검사는 length를 가지고 하게 된다. 만약 주소 A가 너무 크면 exception 이 발생하고, 정상인 경우에는 주소 A에 있는 descriptor 를 불러오고 불러온 descriptor의 type 과 contents 에 따라 동작이 취해진다.
IDT 는 2KB(8 byte 의 entry 가 256) 의 크기를 갖는다. 하지만, IDT 는 더 작은 수의 descriptor 를 갖고 있을 수 있다. 왜냐하면, 발생할 것 같은 interrupt 나 exception 에 대한 descriptor 만 있으면 되기 때문이다. 단, 비어 있는 slot 은 'P' 가 '0' 으로 set 돼야 한다.[ref. 2]
IDT instructions
이 IDT 를 위한 instruction 이 2개가 있다.- LIDT : load IDT register, IDT register에 IDT의 base address 와 limit 을 불러오는 명령어. CPL 이 '0'일 때만 가능하다. 보통 OS 가 초기화될 때 한 번 호출된다.
- SIDT : store IDT register, IDT register 에 있는 내용을 memory 로 copy 해 준다. CPL 에 상관없이 가능하다.
< IDT와 IDTR, ref. 5> |
LINUX 에서 INT3 동작 정리
- IDTR 을 통해서 interrupt vector 를 구하게 된다.(< 그림. IDT와 IDTR >)
- 이 interrupt vector 를 이용해서 IDT 에 있는 trap gate 를 보고 interrupt procedure 의 주소를 계산해 낸다.( < 그림. interrupt call >)
- 이 interrupt procedure 주소가 linux 에서는 intermediate handler 의 주소가 되고, 이 녀석을 통해서 ENTRY(int3) 이 실행될 것이다.
- ENTRY(int3)이 실행되면, error_code 를 통해 do_int3() 이 호출된다.
이제 실제로 source code 로 구현된 부분을 보면서 어떻게 동작하는 지 확인 해 보자.
In Source code
Event
event 는 하드웨어에서 전기적인 신호가 감지되는 것이다. 이 신호를 받아서 cpu 가 수행하고 있던 instruction 을 멈추고 다른 instruction 을 수행하는 것이다. 즉, instruction 의 순서를 바꾸는 것이다.[ref. 6]< interrupt call / from : ref. 2, figure 6-3 > |
아래는 IDT 를 만드는 것과 관련된 linux source 이다. IDT 는 BIOS routine 에서 만들어지지만, linux OS 에서는 한 번 더 만든다. 그게 아래 코드이다.[ref. 6]
/linux/include/asm/system.h
#define _set_gate(gate_addr,type,dpl,addr) \ __asm__ __volatile__ ("movw %%dx,%%ax\n\t" \ "movw %2,%%dx\n\t" \ "movl %%eax,%0\n\t" \ "movl %%edx,%1" \ :"=m" (*((long *) (gate_addr))), \ "=m" (*(1+(long *) (gate_addr))) \ :"i" ((short) (0x8000+(dpl<<13)+(type<<8))), \ "d" ((char *) (addr)),"a" (KERNEL_CS << 16) \ :"ax","dx") #define set_intr_gate(n,addr) \ _set_gate(&idt[n],14,0,addr) #define set_trap_gate(n,addr) \ _set_gate(&idt[n],15,0,addr) #define set_system_gate(n,addr) \ _set_gate(&idt[n],15,3,addr) #define set_call_gate(a,addr) \ _set_gate(a,12,3,addr)
software interrupt 는 DPL field 가 3으로 되어 있다. 그러므로 INT3 의 경우도 DPL 이 3 이다.
linux/kernel/linux/arch/i386/kernel/traps.c
void trap_init(void) { ... set_call_gate(&default_ldt,lcall7); set_trap_gate(0,÷_error); set_trap_gate(1,&debug); set_trap_gate(2,&nmi); set_system_gate(3,&int3); /* int3-5 can be called from all */ set_system_gate(4,&overflow); set_system_gate(5,&bounds); set_trap_gate(6,&invalid_op); set_trap_gate(7,&device_not_available); set_trap_gate(8,&double_fault); set_trap_gate(9,&coprocessor_segment_overrun); set_trap_gate(10,&invalid_TSS); set_trap_gate(11,&segment_not_present); set_trap_gate(12,&stack_segment); set_trap_gate(13,&general_protection); set_trap_gate(14,&page_fault); set_trap_gate(15,&spurious_interrupt_bug); set_trap_gate(16,&coprocessor_error); set_trap_gate(17,&alignment_check); ... }
DO_VM86_ERROR( 3, SIGTRAP, "int3", int3, current) #define DO_VM86_ERROR(trapnr, signr, str, name, tsk) \ asmlinkage void do_##name(struct pt_regs * regs, long error_code) \ { \ if (regs->eflags & VM_MASK) { \ if (!handle_vm86_trap((struct vm86_regs *) regs, error_code, trapnr)) \ return; \ /* else fall through */ \ } \ tsk->tss.error_code = error_code; \ tsk->tss.trap_no = trapnr; \ force_sig(signr, tsk); \ die_if_kernel(str,regs,error_code); \ }
Interrupt 발생부터 call interrupt procedure 까지
interrupt 가 발생해서 interrupt procedure 의 주소를 찾아서 수행하게 된다.
exception -----> intermediate Handler -----> Real Handler
linux 에서 대부분의 intermidate Handler 는 entry.S 에 정의되어 있다.
entry.S 에 정의된 int3 의 intermedate Handler 는 아래와 같다.
#define GET_CURRENT(reg) \ movl $-8192, reg; \ andl %esp, reg ENTRY(int3) pushl $0 pushl $ SYMBOL_NAME(do_int3) jmp error_code error_code: pushl %ds pushl %eax xorl %eax,%eax pushl %ebp pushl %edi pushl %esi pushl %edx decl %eax # eax = -1 pushl %ecx pushl %ebx cld movl %es,%ecx movl ORIG_EAX(%esp), %esi # get the error code movl ES(%esp), %edi # get the function address movl %eax, ORIG_EAX(%esp) movl %ecx, ES(%esp) movl %esp,%edx pushl %esi # push the error code pushl %edx # push the pt_regs pointer movl $(__KERNEL_DS),%edx movl %edx,%ds movl %edx,%es GET_CURRENT(%ebx) call *%edi # call do_int3 addl $8,%esp jmp ret_from_exception
error_code에서 call *%edi (call do_int3)이전에 하는 일
- do_int3 에 넘겨줄 register 값들을 push 를 통해 stack 에 넣는다.
- stack 아래쪽에 %es, -1 을 넣는다.
- error code 와 마지막 esp 를 추가로 stack 에 넣는다.
- %ds, %es 에는 kernel data segment selector 를 넣고,
- %ebx 에는 current process descriptor's address 를 넣는다.
여기서 부족한 부분은
- LIDT 가 실행되는 시점
- INT3 instruction 을 만난 후 interrupt vector 를 구하는 부분
이다. 이 부분은 차후에 보충하기로 하자.
References
- http://forums.codeguru.com/showthread.php?370029-What-is-INT-3
- Intel® 64 and IA-32 Architectures Developer's Manual: Vol. 3A
- http://en.wikipedia.org/wiki/Interrupt_descriptor_table
- http://en.wikipedia.org/wiki/Interrupt_vector
- 1장. 펜티엄 프로세서의 interrupt mechanism, Interrupt Mechanism and Application of Intel IA32 Architecture
- Handling the Interrupt Descriptor Table
- http://en.wikipedia.org/wiki/Direction_flag
댓글 없음:
댓글 쓰기