CS/OS (2022-1)

[OS] System Call이 호출될때 사용되는 Trap 코드 (xv6)

샤아이인 2024. 4. 13. 00:18

시스템 콜은 예전부터 사용하면서 Limited direct execution 이라는 개념을 알고 있었지만, 이게 코드상으로 어떻게 구현되는지는 확인해보지 않았었다.

 

그러다 문득, fork()와 같은 system call()이 사용자 정의 함수가 아닌 System Call 인 것을 어떻게 알고 인터럽트가 발생하게 되어 사용하는 것일까?라는 생각이 들었다.

 

이에 대한 답변을 위해 스스로 학습한 내용을 정리하고자 한다.

 

1. User Mode와 Kernel Mode의 전환

Limited direct execution에서는 user mode에서 kernel mode로 mode switch 할 때 trap을 사용하게 됩니다.

그렇다면 OS는 trap을 어떻게 사용할 수 있을까요? OS가 trap을 처리하기 위해선 다음 그림과 같은 trap table이라는 것을 사용합니다. 

참고로, trap table은 부팅할 때 초기화가 됩니다.

 

위 그림을 보면 Trap Table에 (번호 - handler 주소)가 1대1로 mapping 되어 있는 것을 볼 수 있습니다. 

이때의 실행 과정을 정리해 보면

  1. 사용자가 Fork System Call을 유저가 호출한다
  2. 해당 Fork System Call에 매핑되는 Number를 특정 register에 등록해 둡니다.
  3. trap이 호출됩니다. (사용자는 사용할 trap의 number를 명시합니다, 여기서 2번이라 가정해봅시다!)
  4. OS가 trap table에서 2번에 매핑되는 trap handler의 주소를 얻게 됩니다.
  5. 해당 주소를 통하여 trap handler 호출합니다.
  6. trap handler에서 (2)에서 register에 등록했었던 System call Number를 확인합니다.
  7. 해당 number를 보고 System call을 호출하게 됩니다.

위 그림을 주의 해서 봐야 하는 점이 있는데,

 

Trap Table에 있는 주소가 System Call 함수의 주소가 아닙니다! Trap handler의 주소입니다!

 

즉, 우리는 직접적으로 OS안의 코드를 실행하도록 하는 것이 아니라 number를 지정한 후에 trap을 호출하여 kernel 모드로 변경되고, 이때 OS가 해당 System call을 찾아 처리하게 됩니다.

 

이쯤 하고 코드로 조금 더 살펴봅시다!

 

2. xv6의 코드로 알아보기

2-1) Trap Table 살펴보기 (traps.h)

우선 OS가 실행될 때 초기화 되는 Trap Table의 구조는 다음과 같습니다.

우리가 지금 관심 있는 System call과 관련된 Trap Number는 64에 해당되는 것 을 확인할 수 있습니다.

 

예를 들어 User모드에서 System Call들 중 하나인 Fork를 사용하였다면, 해당 64번을 register에 등록한 후 trap을 호출하게 됩니다.

이때 trap이 호출되면서 위에서 말했듯 Kernel모드로 변경되었을 때 Trap Table에서 64번에 해당되는 함수 주소를 확인합니다.

이후 해당 주소의 함수를 호출하게 됩니다.

 

2-2) Trap 코드 살펴보기 (trap.c)

호출되는 trap 메서드를 살펴보면 다음과 같습니다!

우리가 이전에 살펴봤던 상수인 "T_SYSCALL" 즉 64번에 해당되는 경우 if문 내부를 수행하게 됩니다.

if 문 내부에서 현재 실행 중인 process의 trapframe을 할당한 후, syscall()을 호출하게 됩니다.

 

2-3) System call() 호출 (syscall.c)

직전의 syscall handler를 통해 내부에서 System Call table을 확인하고, register에 등록된 번호로 해당 System call을 호출하게 됩니다.

 

2-4) 아니 그럼 System Call Table은 어디에?? (usys.S, syscall.h)

그럼 System Call에 대한 번호와 주소가 매핑된 테이블은 어디에서 찾아볼 수 있을까?

바로 usys.S과 syscall.h 에서 그 해답을 얻어올 수 있다.

 

▶ syscall.h 코드

우선 syscall.h에 보면 fork, exit 등 각종 system call에 대하여 number가 1대 1로 매핑되어 있는 것 을 알 수 있다.

 

▶ usys.S 코드

 

usys.S의 SYSCALL(name) 매크로를 살펴보면 먼저 name을 전역으로 설정합니다. 그리고 SYS_라는 문자와 시스템 콜의 이름을 붙여 번호를 가져오게 됩니다.

 

예를 들어 read라는 시스템 콜의 경우 SYS_read에 해당하는 번호(5)를 eax 레지스터에 저장한다는 것을 의미합니다.

이때 번호는 바로 위의 사진인, #include "syscall.h"에서 가져오게 됩니다.

 

그리고 T_SYSCALL에 해당하는 번호로 trap 인터럽트(int)를 발생시키고 return 하는 것을 의미합니다.

 

trap 인터럽트가 발생하면 trap.c에 존재하는 trap함수가 호출되고, trap 함수는 T_SYSCALL에 해당하는 코드를 실행시키는 데 이것이 syscall.c에 존재하는 syscall함수를 호출합니다.

 

syscall함수는 eax레지스터에 저장된 값(5)을 가져와 syscall.c에 적혀있는 리스트를 확인합니다.

 

이후 리스트에서 5번에 해당하는 sys_read함수를 호출하게 되며 시스템 콜이 작동되게 됩니다.

 

 

 

 

 

SYSCALL(name) 매크로 아래에 보면 SYSCALL(read)와 같이 해당 매크로를 호출하게 됩니다.

.globl name;

 

이 과정으로 read는 전역으로 설정되며 모든 파일에서 read가 시스템 콜로서 컴파일 되게 됩니다.

따라서 usys.S에 매크로로 넣는 경우 모든 파일에서 read가 이러한 코드로 변환되며 시스템 콜을 호출하게 됩니다!