# 인터럽트 

: 주변장치나 CPU가 자신에게 발생한 사건을 리눅스 커널에 알리는 매커니즘


- 외부 인터럽트. 하드웨어적 인터럽트 (인터럽트)

- 소프트웨어적 인터럽트 (트랩, 예외처리라고도 함)


# 인터럽트 핸들러의 수행

- 인터럽트 발생

- PC(or instruction pointer) 레지스터값을 미리 정해진 특정 번지로 설정

ex. ARM 

0x00000000 + offset로 점프

- reset interrupt : offset은 0

- undefined instruction : offset은 4

- software interrupt : offset은 8


# 인터럽트 백터 테이블

: 인터럽트는 간결히 작성해도 4Byte를 넘기때문에 다른위치에 인터럽트 핸들러를 작성하고 0x00000000에는 인터럽트 핸들러로 점프하는 명령어만 기록

     

ex. ARM CPU의 인터럽트 백터 테이블

--------------------------------------------------------------

0x00000000    _start :        b        reset

b        undefined_instruction

b        software_interrupt

b        prefetch_abort

b        data_abort

b        not_used

b        IRQ

b        FRQ        

--------------------------------------------------------------

- 보통 IDT(Interrupt Descriptor Table) 또는 IVT(Interrupt Vector Table)이라고 부름


# 인터럽트/트랩의 처리

: 문맥저장->인터럽트처리->문맥복원


- 리눅스에서는 인터럽트와 트랩을 동일한 방법으로 처리

- 외부인터럽트/트랩을 처리하기 위한 루틴을 함수로 구현

- 각 함수의 시작주소를 리눅스의 IDT인 idt_table이라는 이름의 배열에 기록

- 다양한 CPU를 지원하기위해 idt_table의 0~31까지 32개를 트랩핸들러를 위해 할당, 그 외의 엔트리는 전부 외부 인터럽트 핸들러를 위해 사용

- 외부 인터럽트를 발생할 수 있는 주변장치는 하드웨어적으로 PIC(Program-mable Interrupt Controller)라는 칩의 각 핀에 연결

- PIC는 CPU의 한 핀에 연결

- x86 CPU의 경우, idt_table의 32번 부터  PIC로 사용이 가능(31까지는 트랩이 사용하므로)

- 리눅스 커널 부팅중에 설정


ex. idt_table의 32번 엔트리에 timer 인터럽트 발생 (가정)

- timer는 PIC와 연결된 선에 펄스를 보냄

- PIC는 수신한 펄스를 적절한 번호로 변환

- I/O 포트에 저장하여 CPU가 버스를 통해 읽을 수 있도록 함

- CPU와 연결된 라인에 펄스를 보내 외부인터럽트 발생을 알림

- CPU가 인터럽트 발생을 알게되어 PIC의 I/O 포트를 읽어 발생한 외부인터럽트의 벡터번호를 확인

- 인터럽트 선을 원래대로 복원시켜 PIC가 다른 인터럽트를 받을 수 있게 함

- 리눅스 커널은 발생한 인터럽트의 번호를 확인

- 이 번호로 idt_table에 인덱싱을 하여 엔트리에 있는 핸들러를 실행


- x86 CPU에서의 idt_table


그림 출처. http://duksoo.tistory.com/entry/System-call-%EB%93%B1%EB%A1%9D-%EC%88%9C%EC%84%9C



# irq_desc table

: 외부 인터럽트가 발생되어 들어오는 라인은 한정되어 있으며, 디바이스 드라이버들은 동적으로 인터럽를 동적으로 할당받거나 해제할 수 있음.

따라서 별도의 관리 매커니즘이 필요. 


- idt_table의 32~255까지(128번 제외, 시스템 호출이 사용)를 같은 인터럽트 핸들러 함수가 등록

- 해당 함수들은 do_IRQ()를 호출

- do_IRQ() : 외부 인터럽트 번호로 irq_des 테이블을 인덱싱하여, 해당 인터럽트 번호와 관련한 irq_desc_t자료구조를 탐색

- irq_desc_t : 하나의 인터럽트를 공유할 수 있도록 action이라는 자료구조의ㅏ 리스트를 유지

- action : 이 리스트를 통해 단일 인터럽트 라인의 공유가 가능


# 문맥저장


- idt_table에 등록되어있는 common_interrupt (외부 이너럽트를 위한 공통 핸들러)

-----------------------------------------------------------------------------------------------------------------------------------

# define  SAVE_ALL     \

cld;    \

pushl    %es;    \

pushl    %ds;    \

pushl    %eax;    \

pushl    %ebp;    \

pushl    %edi;    \

pushl    %esi;    \

pushl    %edx;    \

pushl    %ecx;    \

pushl    %ebx;    \

novl    $(___USER_DS),    %edx;    \

novl    %edx,    %ds;    \

novl    %edx,    %es;


common_interrupt:

SAVE_ALL                    // 인터럽트가 발생한 시점에 수행중이던 태스크의 문맥 저장

call    do_IRQ                // do_IRQ() 함수를 호출하여 실제 인터럽트 서비스가 수행

jmp ret_from_intr         // 인터럽트 처리가  종료되면 SAVE_ALL 매크로로 저장했던 문맥을 RESTORE_ALL 매크로를 통해 복원

-----------------------------------------------------------------------------------------------------------------------------------



# 리눅스에서 트랩의 처리

- 트랩은 구분이 필요함

1. fault : fault를 일으킨 명령어 주소를 eip에 저장 후 해당 핸들러 종료 후 eip에 저장되어있는 주소부터 다시 수행

2. trap : trap을 일으킨 명령어의 다음 주소를 eip에 저장 후 해당 핸들러 종료후 다시 수행

3. abort : 심각한 에러. eip에 값을 저장할 필요가 없고 현재 태스크를 강제 종료


-  ret_from_exception()

: 시스템 콜을 제외한 트랩의 경우 ret_from_exception()을 호출하여 이전 문맥으로 돌아감

- ret_from_intr()

: 일반적인 외부 인터럽트의 경우 해당함수를 호출하여 이전 문맥으로 돌아감

- ret_from_sys_call()

: 0x80 인터럽트, 즉 시스템 콜의 경우

- ret_from_fork() 

: 시스템콜 중 fork(), vfork(), clone()의 경우



# 시스템 호출 처리과정 (intel CPU의 경우로 가정)

그림 출처. http://duksoo.tistory.com/entry/System-call-%EB%93%B1%EB%A1%9D-%EC%88%9C%EC%84%9C


- fork() 시스템 콜 호출

- /usr/lib/libc.a 표준 C 라이브러리에 구현되어있는 fork() 라이브러리 함수 호출

- CPU내의 범용 레지스터 중 eax 레지스터(ARM CPU의 경우 r7 레지스터)에 fork() 함수에 할당되어 있는 고유한 번호인 2를 저장

- 0x80인자(시스템콜)로 int명령으로 트랩을 발생시킴. (ARM CPU의 경우 swi 명령어)

- 트랩 발생 후 커널로 제어 이동

- 문맥저장 후 트랩의 번호(0x80으로 가정)에 대응되는 엔트리에 등록되어있는 함수(0x80이므로 sys_call())를 호출

- sys_call()에서 eax의 값을 인덱스(여기에서는 2)로 sys_call_table을 탐색하여 sys_fork()함수의 포인터를 얻어옴

sys_call() : arch/x86/kernel/의 entry_32.S 또는 entry_64.S에 구현

sys_call_table : arch/x86/kernel/의 syscall_32.c 또는 sysscall_64.c에 구현


=> 사용자 응용단에서 fork() 시스템 콜 호출 후 IDT 테이블과 sys_call_table을 이용해 커널에서 구현된 sys_fork()함수 호출



# 시스템 콜에 할당되어있는 고유번호 찾기(Intel CPU 기준)

- 리눅스 커널에서 제공하는 모든 시스템콜은 고유한 번호를 가짐

- ~/arch/x86/syscalls/syscall_64.tbl 또는 syscall_32.tbl에 정의

- 총 317개

- read()는 0, write()는 1, open()는 2



# 시스템 호출 함수 구현


1. 사전 등록처리, 시스템 호출 번호 할당

- ~/arch/x86/kernel/syscalls/syscall_64_tbl 파일에 새로운 번호를 할당

- 시스템 호출 테이블에 새로운 시스템 호출 처리 함수를 등록

- 할당한 번호를 인자로 sys_call_table이 접근될 때 호출할 함수를 등록

- ~/include/linux/syscalls.h파일에 sys_newsyscall 함수 원형을 등록


2. 시스템 호출 함수 구현

- 태스크관리관련 : kernel/ 디렉터리, 파일시스템 관련 : fs/ 디렉터리

- sys_ 접두어 사용

- asmlinkage : C로 구현된 함수가 어셈블리 언어로 구현된 함수에서 호출 할 수 있도록 해주는 키워드 

  -> 인텔 CPU에서는 특별한 기능은 없으며 알파 CPU의 경우 어셈블리 언어에서 C로 구현된 함수를 호출할 때 몇가지 전처리 작업을 수행함

- 커널에서 수행되는 함수이므로 표준 C라이브리를 사용할 수 없음


3. 커널컴파일 수행

4. 재부팅


* 시스템 호출 함수를 kernel/newfile.c 이라는 새로운 파일에 구현하였으면, 

make 명령이 컴파일 할 때 이 파일도 함께 컴파일할 수 있도록 해야함.

-> kernel/Makefile 아래에 해당 내용을 추가


* syscall() : 인자로 시스템콜의 번호를 받아 해당 시스템콜을 호출해주는 매크로



* 라이브러리를 이용한 시스템 호출 함수 구현 : ar 명령어를 사용하여 라이브러리 만들 수 있음.


* Glibc 라이브러리 ?

- 리눅스 배포판이 설치될 때 기본적으로 설치되는 라이브러리 중 하나

- fork(), open() 등의 함수를 호출하게 해주는 라이브러리

- 위에서 말한 syscall()도 해당 라이브러리에 구현되어 있음


# 인자를 전달하는 시스템 호출

--------------------------------------------------------------------------------------------------------------------------------------

// system call 번호를 318로 등록


#include<linux/unistd.h>

#include<linux/kernel.h>

#include<asm/uaccess.h>

asmlinkage int sys_show_mult(int x, int y, int* res)     // 앞의 2개의 인자를 곱하여 결과를 3번째 인자로 넘김

{

int error, comute;

int i;

error = access_ok(VERIFY_WRITE,res,sizeof(*res));    // access_ok() : res라는 사용자 공간에 쓰기가 가능한지 체크

if(error < 0)

{

printk("error in cdang \n");

printk("error is %d \n", error);

return error;

}

compute = x*y;        // current 포인터 변수 : 현재 실행중인 task_struct

printk("compute is %d \n", compute);

i = copy_to_user(res,&compute,sizoeof(int));    // copy_to_user() 매크로 : 

return 0;                                                    //     include/asm/uaccess.h에 정의. 값을 사용자 수준 공간에 전달하기위해 복사

}



// 호출

int main(void) 

{

...

i=syscall(318,x,y,&mult_ret);

...

}

--------------------------------------------------------------------------------------------------------------------------------------



- 시스템 콜의 매개변수는 레지스터의 크기인 32 혹은 64 bit을 넘을 수 없음

- 레지스터의 개수가 제한적임(인텔 CPU의 경우 6개를 넘을 수 없음)

=> 구조체 사용


- kmalloc() : C 라이브러리 함수인 malloc()과 유사하며 커널 내부함수로써 할당 받는 공간은 물리적으로 연속된 공간을 보장




참고

리눅스 커널 내부구조(책)



'System > Linux Kernel' 카테고리의 다른 글

Device Tree, 리눅스 커널 4.0  (0) 2015.08.29
메모리관리  (0) 2015.08.18
VFS, Virtual File System  (0) 2015.08.16
리눅스 부팅과정과 런레벨  (0) 2015.08.13
Linux File System  (0) 2015.08.13

+ Recent posts