# 인터럽트
: 주변장치나 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 |