[WIL] Week 9-10

dolggul2·2026년 5월 7일

jungle

목록 보기
13/16

목표

  • Pintos Project 2 user program 실행 흐름 이해
  • argument passing 구현을 위한 syscall, stack 구조 학습
  • args-none, args-single 계열 테스트 통과
  • 구현 위치와 함수 책임에 대한 설계 기준 정리

이번주 작업

syscall

  • SYS_WRITE 개념 학습
  • SYS_EXIT 개념 학습
  • 유저 프로그램 출력이 write() syscall에 의존한다는 점 확인
  • exit(status)가 반환이 아니라 프로세스 종료 흐름이라는 점 정리

argument passing

  • Pintos user program 실행 흐름 파악
  • process_create_initd -> initd -> process_exec -> load -> setup_stack -> do_iret -> _start -> main 흐름 정리
  • command line 파싱 위치 결정
  • 유저 스택에 argc, argv, 문자열, NULL sentinel, fake return address 배치
  • args-none, args-single 테스트 통과

trouble shooting

  • process_wait() 무한 루프로 인한 TIMEOUT 원인 파악
  • write-syscall 테스트 등록 파일명 오류 수정
  • thread name에 전체 command line이 들어가 exit 출력이 틀어지는 문제 진단
  • strtok_r()가 원본 문자열을 수정한다는 점을 고려해 복사본 위치 설계

argument passing trouble shooting

이번 주에 가장 많이 고민했던 부분은 argument passing을 구현하면서 복사본을 어디에서 생성하고 관리할지였다.

처음에는 command line을 단순히 공백 기준으로 나누면 된다고 생각했다. 예를 들어 "args-single onearg"가 들어오면 실행 파일 이름은 args-single, 인자는 onearg로 나누면 된다. 이를 위해 strtok_r()을 사용하려고 했다.

그런데 strtok_r()은 문자열을 읽기만 하는 함수가 아니었다. 공백 문자를 '\0'으로 바꾸면서 원본 문자열을 직접 수정한다. 이 점 때문에 원본 command line을 그대로 넘겨서 파싱하면, load()를 호출한 쪽에서도 자신이 넘긴 문자열이 변경될 수 있다는 사실을 알아야 했다.

처음에는 process_exec()에서 복사본을 만들어 load()에 넘기는 방법도 생각했다. 하지만 그렇게 하면 process_exec()load() 내부에서 어떤 방식으로 문자열을 파싱하는지 알아야 한다. 나중에 load() 내부 구현이 바뀔 때마다 호출자 쪽 코드도 함께 영향을 받을 수 있다.

그래서 복사본은 load() 내부에서 생성하고, load()const char *file_name을 받아 원본을 직접 수정하지 않는 방향으로 결정했다. 호출자는 load() 내부에서 파싱을 하는지, 어떤 함수를 사용하는지 알 필요가 없고, load()는 자신에게 필요한 메모리 준비 작업을 스스로 책임지게 된다.

이 방식은 복사본을 만들기 때문에 메모리를 조금 더 사용한다. 하지만 원본 command line을 보존할 수 있고, 호출자와 load() 사이의 책임 경계가 더 명확해진다. 이번 구현을 통해 복사본 위치는 단순히 메모리 효율 문제가 아니라 함수의 책임과 유지보수성의 문제라는 점을 배웠다.

stack 구현

argument passing에서 실제로 값을 저장하는 위치는 커널 스택이 아니라 유저 스택이다. setup_stack() 이후 if_->rsp는 유저 스택의 최상단을 가리키고, 이 값을 감소시키면서 데이터를 쌓아야 한다.

스택에는 먼저 문자열을 역순으로 저장했다. 이후 각 문자열의 유저 스택 주소를 argv 배열에 다시 저장했다. 그 다음 8바이트 정렬을 맞추고, argv[argc] == NULL을 의미하는 NULL 포인터 값을 넣었다. 이후 문자열 주소들을 다시 역순으로 스택에 넣고, 마지막으로 fake return address를 추가했다.

최종적으로 rdi에는 argc, rsi에는 argv 배열의 시작 주소를 넣어야 했다. 여기서 헷갈렸던 부분은 rsiargv[0] 문자열 주소를 넣는 것이 아니라, 스택에 만들어둔 argv 포인터 배열의 시작 주소를 넣어야 한다는 점이었다.

회고

Keep

  • 개념을 바로 구현하지 않고, 먼저 의사코드로 작성하며 이해를 확인했다.
  • syscall, process 흐름, stack 구조를 나누어 학습해서 복잡도를 낮출 수 있었다.
  • 코드 위치를 정할 때 “어디에 넣을 수 있는가”보다 “누가 책임지는 게 자연스러운가”를 고민했다.

Problem

  • process_wait()이 아직 정식 구현되지 않아 테스트가 TIMEOUT으로 끝나는 문제가 있었다.
  • strtok_r(), 포인터 복사, NULL 포인터 값 복사처럼 C 언어에서 익숙하지 않은 부분 때문에 구현 과정에서 헷갈리는 지점이 많았다.
  • 테스트 출력이 틀렸을 때 argument passing 문제인지, exit message 문제인지 처음에는 구분하기 어려웠다.

Try

  • process_wait()을 정식으로 구현해 임시 sleep이나 무한 루프를 제거한다.
  • SYS_WRITE, SYS_EXIT 이후 파일 관련 syscall을 fork 전까지 순차적으로 구현한다.
  • 유저 포인터 검증, 페이지 매핑, stack growth 관련 개념을 Project 3 전에 다시 정리한다.
  • 설계 결정을 할 때 이유를 주석이나 문서로 남겨 팀원이 빠르게 이해할 수 있게 한다.

Feedback

이번 주에는 단순히 테스트를 통과하는 것보다, 왜 이 위치에 코드를 작성해야 하는지 고민하는 시간이 많았다. 처음에는 argument passing을 문자열 파싱과 스택 저장 문제로만 보았지만, 실제로 구현하다보니 함수 책임, 메모리 소유권, 원본 보존, 호출자와 피호출자 사이의 계약까지 함께 고민해야 했다.

특히 복사본을 어디에서 만들 것인지에 대한 고민이 인상 깊었다. 메모리를 아끼는 것도 중요하지만, 호출자가 내부 구현을 몰라도 되는 구조를 만드는 것이 유지보수 측면에서 더 중요할 수 있다는 점을 배웠다.

Pintos는 운영체제 개념을 직접 구현하는 프로젝트이지만, 동시에 좋은 코드를 어떻게 설계하고 팀원에게 어떻게 설명할 것인지까지 연습하게 만드는 프로젝트라는 생각이 들었다.

profile
delve into computer

0개의 댓글