project2에 돌입했다.
과제 2번은 시스템 콜을 구현하는 것이었다.
pintOS에서 시스템 콜은 어떤 과정을 통해 이루어질까?? 하나씩 따라가보자
init.c
먼저 커널이 부팅을 해야한다.
threads/init.c 의 main 함수가 커널 부팅을 시작해준다.
/* Pintos main program. */
int main(void)
{
uint64_t mem_end;
char **argv;
/* Clear BSS and get machine's RAM size. */
bss_init();
/* Break command line into arguments and parse options. */
argv = read_command_line();
argv = parse_options(argv);
....
#ifdef USERPROG
exception_init();
syscall_init(); // 여기에서 시스템 콜 초기화
#endif
/* Start thread scheduler and enable interrupts. */
thread_start();
serial_init_queue();
...
}
우린 이 함수에서 #ifdef USERPROG 로 감싸져있는 syscall_init() 를 따라간다.
syscall_init
syscall_init 는 userprog/syscall.c 안에 있다. 시스템 콜을 초기화 해주는 역할을 한다
void syscall_init(void)
{
write_msr(MSR_STAR, ((uint64_t)SEL_UCSEG - 0x10) << 48 |
((uint64_t)SEL_KCSEG) << 32);
write_msr(MSR_LSTAR, (uint64_t)syscall_entry);
/* 인터럽트 서비스 루틴은 syscall_entry가 사용자 스택을 커널 모드 스택으로
* 교체할 때까지 어떤 인터럽트도 처리하지 않아야 합니다.
* 따라서 FLAG_FL을 마스킹했습니다. */
write_msr(MSR_SYSCALL_MASK,
FLAG_IF | FLAG_TF | FLAG_DF | FLAG_IOPL | FLAG_AC | FLAG_NT);
}
우린 이 함수에서 write_msr(MSR_LSTAR, (uint64_t)syscall_entry);
이 코드에 주목하자.
여기서 우리는 두가지 함수에 대해 알아야 한다. 먼저 write_msr 그리고 syscall_entry 에 대해 알아보자
write_msr
이 함수는 MSR(Model-Specific Register)에 값을 기록하는 함수이다.
MSR은 인텔과 AMP CPU에만 존재하는 하드웨어의 특수 기능 제어나 상태 정보를 담는 64비트 레지스터 세트이다.
운영체제가 CPU의 동작 방식을 제어하는데 사용한다고 한다.
저 코드를 해석해보면, MSR_LSTAR 라는 상수를 번호로 가진 MSR 레지스터에 syscall_entry가 있는 주소를 저장하라는 뜻이다.
이후 syscall이 발생하면 CPU가 자동으로 MSR_LSTAR 에 담긴 주소로 점프한다고 한다.
그럼 syscall 명령이 발생하면 저 MSR_LSTAR 에 담긴 주소로 점프하는건 대체 어디서 정해진건가? 누가 정해주는건가?
이건 x86-64 아키텍처에서 명세로 약속된 규칙이다. 참고로 32비트 호환 모드에서는 MSR_STAR로 이동한다고 한다.
syscall_entry
그럼 이제 이 함수가 무엇인지 알아보자. 일단 안을 까보면
#include "threads/loader.h"
.text
.globl syscall_entry
.type syscall_entry, @function
syscall_entry:
movq %rbx, temp1(%rip)
movq %r12, temp2(%rip) /* callee saved registers */
movq %rsp, %rbx /* Store userland rsp */
movabs $tss, %r12
movq (%r12), %r12
movq 4(%r12), %rsp /* Read ring0 rsp from the tss */
/* Now we are in the kernel stack */
push $(SEL_UDSEG) /* if->ss */
push %rbx /* if->rsp */
push %r11 /* if->eflags */
어셈블리어로 작성되어있다. 그럼 이 함수가 무슨 일을 하냐?
- 사용자 프로세스는 현재 유저 스택을 사용하고 있지만, 시스템 콜로 커널로 진입하면 커널 스택으로 스위칭 진행
- 유저 모드에서 넘어온 CPU 상태들을 저장 (intr_frame에)
- C에서 시스템 콜 처리 함수 호출 → syscall_handler 호출
- 시스템 콜 처리 후, 유저 모드로 복귀, 이후 저장해둔 CPU 상태 복원
엥? intr_frame은 또 뭐야?
intr_frame
이 구조체 또한 그냥 넘어갈수 없는 중요한 포인트다.
내부를 또 까보자
intr_frame은 threads/interrupt.h 파일 안에 선언되어 있다.
struct intr_frame {
/* Pushed by intr_entry in intr-stubs.S.
These are the interrupted task's saved registers. */
struct gp_registers R;
uint16_t es;
uint16_t __pad1;
uint32_t __pad2;
uint16_t ds;
...
}
우리는 여기서 또 gp_registers 구조체를 까봐야 한다
struct gp_registers {
uint64_t r15;
uint64_t r14;
uint64_t r13;
uint64_t r12;
uint64_t r11;
uint64_t r10;
uint64_t r9;
uint64_t r8;
uint64_t rsi;
uint64_t rdi;
uint64_t rbp;
uint64_t rdx;
uint64_t rcx;
uint64_t rbx;
uint64_t rax;
} __attribute__((packed));
레지스터 값들이 전부 여기 담겨서 intr_frame에 저장되는 것이다 !!
위에서 syscall_entry는 syscall_handler를 호출한다고 했다.
그럼 시스템 콜 처리 함수 syscall_handler는 뭘 하면 될까?
바로 이 intr_frame 안의 gp_registers를 까서 레지스터 값을 꺼내서 처리해주면 된다는 것이다 !!
일단 시스템 콜은 각자 번호로 구분되는데, 이는 rax 레지스터에 담긴다. 시스템 콜마다 필요한 인자들이 있는데, 이 인자들은 순서대로 rdi, rsi, rdx, r10, r8, r9 순으로 담겨서 전달된다.
그럼 이제 syscall_handler를 보자 !!
syscall_handler
이 함수도 userprog/syscall.c 밑에 존재한다.
/* The main system call interface */
void syscall_handler(struct intr_frame *f UNUSED)
{
// TODO: Your implementation goes here.
printf("system call!\n");
thread_exit();
}
그럼 우린 이 코드에서 어떻게 구현을 해주면 될까?
여기까지 읽었으면 간단하다.
void syscall_handler(struct intr_frame *f) {
uint64_t syscall_num = f->R.rax; // 시스템 콜 번호
uint64_t arg1 = f->R.rdi; // 첫 번째 인자
uint64_t arg2 = f->R.rsi; // 두 번째 인자
uint64_t arg3 = f->R.rdx; // 세 번째 인자
// ... 필요하다면 4, 5, 6번째 인자(r10, r8, r9)도 사용
switch (syscall_num) {
case SYS_WRITE:
f->R.rax = sys_write(arg1, (const void*)arg2, (unsigned)arg3);
break;
case SYS_EXIT:
// ...
break;
// ... 나머지 시스템 콜도 분기 처리
default:
// 알 수 없는 시스템 콜
break;
}
}
세상에 이렇게 쉽다니 !! 나는 이제 pintOS의 신이야
흐름
- init.c의 main 함수로 커널이 부팅되면서 syscall_init 호출
- syscall_init가 MSR_LSTAR 레지스터에 syscall_entry 주소 저장
- syscall 발생
- MSR_LSTAR에 저장되어 있던 syscall_entry 주소로 점프
- syscall_handler 실행
아 쉽다 쉬워
'크래프톤 정글' 카테고리의 다른 글
| [pintOS] 유저 프로그램 실행 흐름 (0) | 2025.05.17 |
|---|---|
| [pintOS] 버그/에러 모음 (0) | 2025.05.17 |
| 9주차 개발일지 (0) | 2025.05.14 |
| 8주차 개발일지 (1) | 2025.05.07 |
| 7주차 개발일지 (0) | 2025.04.30 |