[pintos] Week2~3: User Program Part.4

이전 포스팅들에서 인자를 파싱했다. 이제 시스템 콜을 구현해 보겠다. 시스템 콜에 대한 것은 이전에 part.1에 담겨있다. 

2025.05.19 - [크래프톤 정글] - [pintos] Week2~3: User Program Part.1

 

[pintos] Week2~3: User Program Part.1

1주 차에 Alarm Clock, Priority Scheduling과 같이 동기화와 스케줄링에 대해 다루었다면 2~3주 차는 User Program이 Pintos에서 돌아갈 수 있도록 구현을 진행한다.User Program을 OS에 로드하고, System Call을 통해 U

www.gowoong.com

우리는 그 중 (halt, exit, create, remove)를 먼저 구현해서 테스트 코드의 시작 부분을 먼저 검증하면서 진행하겠다.


인자 파싱(Arguments Parssing) 방법

이 전 시간에 커널은 사용자가 실행을 요청한 프로그램을 실행하기 위해 실행하며 입력한 명령어 문자열을 분리하여 파일 이름, 인자값으로 나누는 과정을 거쳐야 한다고 했다. 그렇다면 어떻게 문자열을 나누어야 할까? 

Pintos강의 ppt에는 strtok_r() 함수를 사용해서 파싱을 진행하라고 나와있다.

strtok_r() 함수

char *strtok_r(char *str, const char *delim, char **save_ptr);

이 함수는 문자열을 특정 구분자로 잘라서 파싱 하는 함수다. 

 

  • str: 처음 호출 시 분할할 문자열, 이후 호출 시 NULL
  • delim: 분할할 구분자 (예: 공백 " ")
  • save_ptr: 내부 상태를 저장할 포인터 (재진입-safe)

마치 python의 .split(" ") 기능을 수행하는 것이라고 보면 된다. 차이점이라면 파이썬이 한 번에 리스트로 변환한다면 C에서는 반복문으로 하나씩 꺼내야 한다는 점이다. 또 다른 차이점으로는 strtok_r()은 원본 문자열을 수정한다는 것이다.

char str[] = "echo hello";
strtok_r(str, " ", &save);  // str → "echo\0hello"

 " "을 '\0' 으로 대체 삽입했다. 상태 저장을 위해 save_ptr로 호출자가 상태를 유지해야 한다는 점도 있다. 어쨌든 문자열을 분할하는 작업이다. 


1. syscall_handler() 함수

  • userprog/syscall.c

시스템 콜을 호출할 때, 원하는 기능에 해당하는 시스템 콜 번호를 rax에 담는다. 그리고 시스템 콜 핸들러는 rax의 숫자로 시스템 콜을 호출하고, 해당 콜의 반환값을 다시 rax에 담아서 intr frame(인터럽트 프레임)에 저장한다.

/* The main system call interface */
void
syscall_handler (struct intr_frame *f UNUSED) {
    switch (f->R.rax)
    {
    case SYS_HALT:
        halt(); // 핀토스 종료
        break;
    case SYS_EXIT:
        exit(f->R.rdi);	// 프로세스 종료
        break;
    case SYS_CREATE:
        f->R.rax = create(f->R.rdi, f->R.rsi);
        break;
    case SYS_REMOVE:
        f->R.rax = remove(f->R.rdi);
        break;
    default:
        exit(-1);
    }
}

2. check_address() 함수

  • userprog/syscall.c
  • 주소 값이 유저 영역에서 사용하는 주소 값인지 확인
  • 유저 영역을 벗어난 영역일 경우 프로세스 종료(exit(-1))
void 
check_address (void *addr)
{
    if (is_kernel_vaddr(addr) || addr == NULL || pml4_get_page(thread_current()->pml4, addr) == NULL)
        exit(-1);
}

Pintos의 userprog 프로젝트에서 매우 중요한 보안 및 안정성 기능으로, 유저 프로그램이 잘못된 주소(커널 주소나 매핑되지 않은 주소)에 접근할 경우 이를 미리 감지하고 종료(exit) 시키기 위해 사용한다.

조건 의미
is_kernel_vaddr(addr) 커널 영역 주소인지 확인. 유저가 커널 주소에 접근하려 하면 안 됨
addr == NULL NULL 포인터 접근은 잘못된 접근이므로 종료
pml4_get_page(thread_current()->pml4, addr) == NULL 현재 스레드의 페이지 테이블에서 해당 주소에 매핑된 실제 물리 주소가 없으면 → 접근 불가능한 페이지

이 함수를 통해 유저 프로그램이:

  • 커널 메모리를 읽거나 쓰는 행위 차단
  • 매핑되지 않은 주소에 접근해 Page Fault 발생시키는 것 방지

3. halt() 함수

  • userprog/syscall.c
  • 핀토스를 종료시키는 시스템 콜
void 
halt(void) 
{
    power_off();
}
GitBook 설명: Pintos를 종료한다. power_off()를 호출하며, src/include/threads/init.h에 선언되어 있다. 이 시스템 콜은 디버깅에 유용하지만, 데드락 등 문제 원인을 분석할 기회를 잃게 되므로 최소한으로 사용해야 한다.

4. exit() 함수

  • include/threads/thread.h
  • exit(), wait()을 구현할 때 필요한 변수나 값을 구조체에 추가하고 초기화 한다.
uint64_t *pml4;                     /* Page map level 4 */
int exit_status;
  • threads/thread.c
    t->original_priority = t->priority;
    t->niceness = NICE_DEFAULT;
    t->recent_cpu = RECENT_CPU_DEFAULT;
	/* 기존 구현 부 아래에 추가 */
    t->exit_status = 0;
}
if (success) {
	set_stack_data(buffer, count, &_if.rsp);
	_if.R.rdi = count;
	_if.R.rsi = (char *)_if.rsp + 8;
}
  • userprog/syscall.c
  • 현재 프로세스를 종료시키는 시스템 콜
  • 종료 시 “프로세스 이름: exit(status)” 출력 (Process Termination Message)
  • 정상적으로 종료 시 status는 0
  • status : 프로그램이 정상적으로 종료됐는지 확인
void 
exit(int status) 
{
    struct thread *t = thread_current();
    t->exit_status = status;
    printf("%s: exit(%d)\n", t->name, t->exit_status); // Process Termination Message
    thread_exit();
}
GitBook 설명 : 현재 사용자 프로그램을 종료하고 커널에 status를 반환한다. 부모 프로세스가 wait()를 호출하면 이 status가 전달된다. 일반적으로 0은 성공, 0이 아닌 값은 에러를 의미한다.

5.  create() 함수 구현

  • userprog/syscall.c
  • 파일을 생성하는 시스템 콜
  • 성공 일 경우 true, 실패 일 경우 false 리턴
  • file : 생성할 파일의 이름 및 경로 정보
  • initial_size : 생성할 파일의 크기
bool 
create(const char *file, unsigned initial_size) 
{
    check_address(file);

    return filesys_create(file, initial_size);
}
GitBook 설명 : 크기 `initial_size`의 새 파일을 생성한다. 성공 시 `true`, 실패 시 `false` 반환. 생성은 열기와 다르다. 열려면 open() 호출 필요. 

6. remove() 함수 구현

  • userprog/syscall.c
  • 파일을 삭제하는 시스템 콜
  • file : 제거할 파일의 이름 및 경로 정보
  • 성공 일 경우 true, 실패 일 경우 false 리턴
bool 
remove(const char *file) 
{
    check_address(file);

    return filesys_remove(file);
}
GitBook 설명 : 파일을 삭제한다. 열려 있든 닫혀 있든 상관없이 삭제 가능. 성공 시 true.