Kernel Trap (트랩)

2021. 12. 19. 15:22OS

    목차
반응형
이번에는, Trap이 무엇인지에 대해서 알아보도록 하겠습니다.
 
어떤 process가 OS service를 사용하려고 할때, process 입장에서는 단순하게 OS의 member function의 호출을 수행하려고 합니다. 그런데, OS는 supervisor mode에서 동작 합니다. 그런데 이것을 호출한 process는 user mode에서 동작을 하고 있지요. 그래서 user-space process들에게 CPU mode를 supervisor mode로 바꾸고 OS의 fuction으로 동시에 branch 할 수 있는 방법을 design 하게 되었는데요, trap instruction이 바로 이것을 수행하는 것이다.
 
trap instruction
 
    즉, fancy branch instruction라고 할 수 있죠.
    수행하는 behavior는 다음과 같습니다.
 
  • 1. CPU를 supervisor mode로 swtich
  • 2. target function의 address를 kernel-space trap table에서 찾음
  • 3. trap table에 있는 address가 가리키는 kernel-space function으로 branch 수행
 
    위의 3가지 단계를 한번에 수행하게 되죠.
    그리고 trap은 CPU mode bit를 supervisor mode로 바꾸는 유일한 instruction입니다.
 
trap instruction에 대한 정말 좋은 것중 하나는 OS function이 trap을 통해서만 호출 될 수 있는 trusted function으로서 작성되게 허용한다는 것 입니다. OS designer들이 새로운 function을 작성할때마다, trap table에 대한 new entry가 위치해야 합니다.
(이 말이 무엇을 의미하냐..하면 다음과 같습니다.)
OS는 booting 시에, interrupt vector table을 만들게 되죠. 예를들어 ARM processor의 경우는 0번 address에 interrupt vector table이 존재해서 바로, reset handler의 address로 jump를 수행하죠. 이러한 table은 이미 compile 단계에서 0번 address로 들어가도록 작성됩니다. 그리고 storage에서 RAM으로 relocating을 수행할 때, 0번 address로 이 vector table을 copy하게 되죠. 그리고 jump.. reset 작업 및 initialization을 수행합니다. 그리고, OS는 이 vector table의 위치를 변경 시킬 수도 있지요. 그거야 vector table로 jump하는 주소만 다르게 지정해 주면 되니깐요.
 
ARM processor의 경우 Linux에서는 어떻게 이 vector table을 지정해 주는지 살펴 봅시다.
 
linux/arch/arm/kernel/calls.S
file 내에 system call을 추가 할 수 있습니다.
 
위 file은 entry-common.S에서 #include "calls.S" 와 같이 include 됩니다.
calls.S의 내용은 SWI handler로서 사용됩니다.
 
 
    entry-armv.S 를 살펴보면, ARM용 interrupt handling에 대한 코드를 볼 수 있습니다.
    ...
    vector_addrexcptn:
     b vector_addrexcptn



 .LCvswi:
     .word vector_swi
     .globl __stubs_end


    ENTRY(vector_swi)
        sub sp, sp, #S_FRAME_SIZE
        stmia sp, {r0 - r12} @ Calling r0 - r12
        ...
        // Get the system call number.
        
        
        enable_irq                      @ C function
        get_thread_info tsk
        
        adr tbl, sys_call_table     @ load syscall table pointer
        
        ...
        
        cmp scno, #NR_syscalls         @ check upper syscall limit
        adr lr, ret_fast_syscall     @ return address
        ldrcc pc, [tbl, scno, lsl #2] @ call sys_* routine
        
        
        bcs arm_syscall                     @ Handle all unrecognised system calls
        b sys_ni_syscall         @ not private func​
사실, trap에 대한 명확한 정의는 통일되어 있지 않습니다.
어떤 책에서는 exception의 종류로서 trap이 있다고 얘기를 하고 있기도 합니다.
혹은, exception과 trap을 따로 구분 하기도 하고, trap의 경우에는 software interrupt 라고 부르기도 합니다. - 
 
trap이 가장 많이 사용되는 것은 바로 system call의 호출 입니다.
이 system call은 interrupt descriptor table의 특정 offset의 handler로서 존재합니다. user software에서 이런 system call을 호출 하기 위해서는 system call을 직접 (Assembly level에서 처리 하던가) 혹은, 간단하게 system call 호출의 처리가 이미 되어 있는 library call을 이용합니다. 예를들어 sys_write를 호출하기 위해서는 write(..) 함수를 호출 하는 것이죠.
 
system call은 개발자가 원하는대로 추가 할 수 있습니다. 그렇다면, 이 system call을 어떻게 등록 할까요?
 
include/asm-i386/unistd.h에서 system 호출 번호를 할당 합니다.
..
#define __NR_newcall 294
#define NR_syscalls 295 <-- 위에 하나를 추가 했으면 값을 1 증가 시킵니다.
 
그리고, arch/i386/kernel/syscall_table.s에서 새로운 entry를 추가 합니다.
.long sys_newcall  // 294

이제, 이 새로운 system call을 작성합니다.

#include <linux/kernel.h>

asmlinkage int sys_newcall()
{
    return 0x5;
}
 
여기서 asmlinkage는 assembly에서 C 로 작성된 함수를 호출 할 수 있도록 하기 위해서 지정하는 gcc 확장 기능 입니다.
kernel/Makefile을 수정합니다.
obj-y = ....의 마지막에 새롭게 작성한 system call의 object file name을 추가 합니다.
 
make bzImage로 kernel을 새롭게 build하고, 
kernel을 복사 호, reboot합니다.
 
이제 이것을 사용하는 app.을 개발하여 사용할 수 있습니다.
 
#include <linux/unistd.h>

_syscall0(int, newcall);

int main(void)
{
    int i = newcall();
    printf("%d", i);
    return 0;
}
 
위와 같은 호출을 수행하게 되면, 0x5 (==5)을 출력할 것 입니다.
위 file의 compile은 다음과 같습니다.
gcc -I/usr/src/linux-2.6.14.6/include newcall_test.c -o newcall_test
 
-I는 include 문에서 사용하는 header file의 path를 지정합니다.
이렇게 지정하지 않으면, default path인 /usr/include/linux/unistd.h를 찾는데, 
이는 수정한 header file이 아닙니다. 현재 사용중인 kernel ver.에 따라서 자동으로 수행 되게 만들고 싶다면 다음과 같이 수정합니다.
 
gcc -I/usr/src/linux-'uname -r'/include new_call_test.c -o new_call_test

 

[uname -r]은 커널의 버전을 숫자 형태로 변환 합니다.
printk 로 출력하는 msg는, console이 아닌 경우에 보려면, dmesg를 수행해야 합니다.
 
_syscall0()은 인자가 없는 껍데기 함수를 생성합니다.
인자는 최대 6개 까지 가능 합니다. 즉, _syscall6(..)까지 가능 합니다.
이것에 대한 macro는 /include/asm-i386/unistd.h에서 확인 가능 합니다.
 
ex.
int sys_a(struct def_data_type *data);
의 경우는, 
 
_syscall1(int, a, struct def_data_type *, data);
로 정의 합니다.
 
user level과 kernel level에서 data를 교환하는 데는 포인터가 효과 적이기 때문에 포인터를 사용합니다.

 

반응형

'OS' 카테고리의 다른 글

RTOS?  (0) 2021.12.19