System call 구현하기
Computer Science |
xv6: a simple, Unix-like teaching operating system의 Ch 4.는 Trap과 System call에 대해서 다룬다. 이를 읽고 정리한 문서는 Ch4. Traps and system calls에 있다.
PA2 Specification
kbdints()
구현하기
- device가 boot한 시점부터 들어온 모든 keyboard interrupts의 개수를 세서 리턴하는 system call
kbdints()
를 구현해야 한다. - System call number는 22로 주어진다.
time()
구현하기
mtime
레지스터를 읽어서 리턴하는 system calltime()
을 구현해야 한다.mtime
레지스터를 읽어오는rdtime
instruction은 machine mode에서만 실행 가능하다.- System call number는 23으로 주어진다.
1. kbdints()
구현하기
kbdints()를 구현하기 위해서는 먼저 system call number를 추가해주어야 했다. 이는 주어진 스켈레톤 코드에서 이미 설정되어 있었다.
|
|
따라서 kernel/syscall.c 에서 함수를 extern으로 선언해주고, syscall[]()
에 추가해준다. 이렇게 해주면 ecall
이 발생되었을 때 커널에서 system call임을 인지하고, syscall()
에서는 a7
레지스터에 받은 system call number를 읽어 내가 구현한 custom system call을 실행해 주게 된다.
|
|
console.c
에서는 keyboard interrupt가 날 때 마다 올려줄 변수 kbdints
을 선언해주고, 인터럽트가 발생하면 실행되는 함수 consoleintr()
의 마지막에 ++kbdints
를 해주어 키보드 인터럽트의 횟수가 잘 저장될 수 있도록 해준다.
|
|
system call의 실체는 sysproc.c에 구현하였다.
|
|
이렇게 단순히 저장된 키보드 인터럽트 횟수를 리턴해주게 되면 원하는 system call kbdints()
를 구현할 수 있다.
2. time()
구현하기
kbdints()
는 비교적 단순하게 구현할 수 있었지만, time()
의 경우에는 삽질을 많이 했다. 우선 xv6 운영체제는 3개의 privilege mode를 가지고 있는데, user mode, supervisor mode, machine mode가 이 3가지이다. 사용자는 user space에서 프로그램을 실행하고, 커널의 대부분은 supervisor mode에서 실행된다. 더 높은 privilege mode인 machine mode는 부팅 시에 운영체제를 세팅하는 코드만 조금 실행한다. xv6는 기본적으로 모든 trap을 machine mode에서 처리하는데, xv6는 부팅 시에 start()
함수에서 mideleg
, medeleg
레지스터를 모두 0xffff
로 수정하여 모든 interrupt와 exception을 supervisor mode로 delegate한다.
2.1. machine mode에서 코드 실행하기
우리는 time()
시스템콜 내에서 mtime
레지스터를 읽어야 하므로 machine mode에서 trap을 처리할 수 있도록 해주어야 한다. medeleg
와 mideleg
에서 적절한 비트를 0으로 내려서 특정한 trap은 machine mode에서 처리할 수 있도록 해주어야 한다. mcause
가 리턴하는 값에 해당하는 인덱스를 수정하면 되는데, 각 비트가 뜻하는 것은 다음에서 확인할 수 있다.
Interrupt | Exception code | Description |
---|---|---|
0 | 0 | Instruction address misaligned |
0 | 1 | Instruction access fault |
0 | 2 | Illegal instruction |
0 | 3 | Breakpoint |
0 | 4 | Load address misaligned |
0 | 5 | Load access fault |
0 | 6 | Store/AMO address misaligned |
0 | 7 | Store/AMO access fault |
0 | 8 | Environment call from U-mode |
0 | 9 | Environment call from S-mode |
0 | 10 | Reserved |
0 | 11 | Environment call from M-mode (mcause only) |
0 | 12 | Instruction page fault |
0 | 13 | Load page fault |
0 | 14 | Reserved |
0 | 15 | Store/AMO page fault |
0 | >= 16 | Reserved |
1 | 0 | Reserved |
1 | 1 | Supervisor software interrupt |
1 | 2 | Reserved |
1 | 3 | Machine software interrupt (mcause only) |
1 | 4 | Reserved |
1 | 5 | Supervisor timer interrupt |
1 | 6 | Reserved |
1 | 7 | Machine timer interrupt (mcause only) |
1 | 8 | Reserved |
1 | 9 | Supervisor external interrupt |
1 | 10 | Reserved |
1 | 11 | Machine external interrupt (mcause only) |
1 | >= 12 | Reserved |
Interrupt가 0이면 medeleg
에서, 1이면 mideleg
에서 수정해주면 된다. 나는 time()
System call 안에서 rdtime을 하고싶은 것이므로, environment call (ecall
) from supervisor인 9번째 비트를 0으로 바꿔주어야 했다. 따라서 medeleg
레지스터의 9번째 비트를 0으로 내려주기 위해, 0xffff
로 설정하던 medeleg를 0xfdff
로 바꿔주었다.
|
|
내가 구현하고 있는 sys_time()
에서 ecall
instruction을 실행해서 machine mode로 privilege level을 올리기 위해서 위와 같이 비트를 내려주었다. 비트가 0xffff
로 설정되어 있을 때 ecall
을 실행하면, supervisor mode에서 ecall
을 처리하므로 stvec
가 가리키고 있는 kernelvec이 실행된다. 하지만 비트를 내려서 machine mode에서 처리하도록 하면 mtvec
가 가리키고 있는 timervec
이 실행된다. 이로서 machine mode로 privilege level을 올리는 것 까지는 성공하였다.
2.2. mtime
레지스터 읽어오기
내가 구현한 sys_time()
은 다음과 같다.
|
|
ecall
instruction을 실행하면 timervec
을 실행하게 된다. 그러나 timervec
은 timer interrupt를 처리하기 위해 있는 코드이므로, timervec
의 코드를 수정해주어야 했다. 따라서 timervec
에서는 mcause
를 읽어 ecall from s-mode인 경우에는 다른 함수를 실행할 수 있도록 해주고자 했다.
|
|
따라서 임시 레지스터인 t0
레지스터에 mcause
레지스터를 읽어오고, mcause
가 9이면 내가 만든 함수인 machinevec
으로 뛰도록 했다. machinevec
에서는 a0
레지스터에 mtime
을 읽고, mepc
에 4를 더해 ecall
다음 instruction으로 리턴할 수 있도록 했다. 이후 위에서 볼 수 있듯 sys_time()
함수에서 a0
레지스터를 읽어 리턴한다.
RISC-V 구조에서 사용하는 Register는 다음 그림에 있다.
컴퓨터구조 수업을 듣지 않고 듣는 수업이라 RISC-V 어셈블리를 처음 다뤄보는 것이어서 조금 헤맸지만, 구현하고 나니 생각보다 쉬운 과제였다. 이 과제에서도 이만큼 헤맸는데 다음 과제는 각오하고 풀어야 할 것 같다.