[ 카드캡터 핀토스 ] 🩷prologue – 꿈의 시작, 커널을 넘은 마법

김쟇연·2025년 5월 31일
6

PintOS Project 2

목록 보기
1/4
post-thumbnail

Prologue – 꿈의 시작, 커널을 넘은 마법

🌙 나는 오늘도 평범한 하루를 보낼 줄 알았어요.

하지만 갑자기 화면 가득 까만 터미널이 나타나더니,

그 위로 숫자와 글자들이 빛처럼 흘러내렸어요…

그리고 그 속에서 목소리가 들렸어요.

『사쿠라, 이제는 커널의 경계를 넘어야 해…

유저 프로그램을 봉인하러 가야 해.』

🌀 그 순간, 내 앞에는 반짝이는 마법진이 펼쳐졌고

process_exec()라는 낯선 주문이 떠올랐어요.

…이건, 분명히 새로운 카드의 신호였어요.


💻 PintOS Project 2, 대체 뭘 하라는 거예요!?

이번 프로젝트는 단순히 커널 내부에서만 움직이던

OS의 세계를 유저 공간까지 확장하는 마법이에요.

우리가 이제 해야 할 일은…

역할설명
🧙‍♀️ 유저 프로그램 실행ELF 포맷 바이너리를 읽고, 메모리에 올려서 실행해야 해요!
🧵 인자 전달argc, argv[]를 스택에 직접 정렬해서 넘겨줘야 해요!
✨ 시스템 콜 처리write(), exit() 같은 함수들을 직접 커널에서 처리해야 해요!
🪞 자식 프로세스 생성fork()를 호출하면 나와 똑같은 자식 프로세스를 만들 수 있어야 해요!
🔁 프로그램 재실행exec()으로 현재 프로세스를 새로운 코드로 바꿔치기!
🔐 자원 보호유저가 넘긴 포인터가 위험한지 아닌지 꼭 확인해야 해요!
💾 파일 다루기유저 프로그램이 파일을 읽고 쓰고, 삭제할 수 있어야 해요!

📌 즉, 유저가 커널에 무언가를 "요청"했을 때,

그걸 이해하고 응답해주는 마법을 우리가 직접 구현하는 거예요!


✨ 마법의 카드 도감 📖

🃏 카드 이름📚 봉인할 마법🔧 구현 요소
🪄 실행의 카드유저 프로그램을 메모리에 로딩하고 실행process_exec(), load()
💬 말의 카드argv, argc를 스택에 정렬setup_stack()
🌙 이별의 카드exit() 종료 메시지와 wait() 수거process_exit(), process_wait()
❌ 금기의 카드위험한 포인터 접근을 차단get_user(), put_user()
🔔 호출의 카드시스템 콜 번호로 처리 분기syscall_handler()
🪞 거울의 카드 上자식 프로세스의 메모리 복제__do_fork(), duplicate_pte()
🪞 거울의 카드 下자식이 안전하게 진입하도록 처리자식 프레임 복원, 실패 처리
🔁 변화의 카드현재 프로세스를 완전히 교체exec()
📤 울림의 카드콘솔 & 파일 입출력 처리read(), write()
📂 기억의 카드파일 생성, 삭제, 열기, 닫기create(), remove(), open(), close()
🧊 고요의 카드락을 통한 파일 동기화filesys_lock
🕯️ ???의 카드아직 누구도 봉인하지 못한 마지막 카드(비밀이에요…🤫)

🔍 이 프로젝트에서 우리가 다룰 구조들

🧱 코드 흐름 (Pintos가 유저 프로그램을 실행할 때)


main()                       // threads/init.c
  ↓
run_actions()               // parse arguments
  ↓
process_create_initd()      // create initd thread
  ↓
thread_create()             // kernel thread launch
  ↓
kernel_thread() → initd()   // fork/exec 시작
  ↓
process_exec()              // ELF 로드
  ↓
load() + setup_stack()
  ↓
do_iret()                   // 유저 모드 진입!

📎 이 흐름을 정확히 이해하지 않으면

"도대체 왜 이 함수가 호출되지?"라는 현상이 계속 생길 거예요!


🗺️ 메모리 구조 (64-bit 가상 메모리 기준)


0x00000000
   ...
0x00400000 ← 코드 시작
   ...
USER_STACK: 0x47480000 ↓ (인자, argv, argc)
   ...
KERN_BASE: 0x8004000000 ← 커널 주소 시작
  • 유저 주소는 KERN_BASE 미만에서만 접근 가능!
  • 유저가 넘긴 포인터가 커널 주소면 즉시 종료해야 해요 💥

🪄 시스템 콜 호출 규약 (x86-64)

레지스터의미
%raxsyscall 번호
%rdi첫 번째 인자
%rsi두 번째 인자
%rdx세 번째 인자
%r10네 번째 인자

syscall_handler()struct intr_frame을 통해

유저가 넘긴 이 레지스터 값들을 읽어와요.

→ 결과는 다시 f->R.rax에 저장해서 유저로 반환해요.


💬 케로의 한마디

“사쿠라, 이건 단순한 실습이 아니다구!

지금부터 진짜 운영체제를 만드는 거라고~!! 🐥”


🧚‍♀️ 나는 포기하지 않아요.

처음 보는 구조체,

무섭게 죽어버리는 테스트들,

도무지 안 되는 pointer dereference…

그래도 괜찮아요.

왜냐하면…

그게 전부 내가 마법소녀로 성장하는 과정이니까요! 🌸

다음은 args-single을 통해

첫 번째 카드, 실행의 카드를 봉인할 차례예요.


✅ 정상인 버전 정리

📌 프로젝트 개요

  • 유저 프로그램을 운영체제 위에서 직접 실행한다.
  • 사용자로부터 시스템콜을 받아 커널에서 처리하는 구조를 만든다.
  • 자식 프로세스를 만들고, 종료 시 자원을 회수한다.
  • 유저 프로그램이 넘긴 포인터, 파일 디스크립터 등을 반드시 검증한다.

🛠️ 핵심 구현 흐름

c

process_exec()load()setup_stack()do_iret() → 유저 프로그램 진입!

📎 주요 파일

  • userprog/process.c: exec, load, wait, fork 전반
  • userprog/syscall.c: 시스템콜 핸들러, 파일 연산 처리
  • userprog/exception.c: 잘못된 접근에 대한 예외 처리
  • lib/user/syscall.c: 유저가 호출하는 syscall 인터페이스

⚠️ 실수 포인트

  • %rdi, %rsi 레지스터 순서가 바뀌면 argument passing 실패!
  • 유저가 넘긴 포인터 검증 안 하면 바로 kernel panic 🔥
  • 락 없이 파일 시스템 접근하면 테스트 interleave로 전부 FAIL 💣

🧭 추천 디버깅 방법

  • hex_dump()로 유저 스택 시각화해서 argv[i] 확인
  • printf() 대신 msg() 사용 → 채점 스크립트 깨짐 방지
  • 각 테스트 전후, 파일 시스템 상태를 ls로 확인해보기

🎯 첫 도전

  • args-single, args-multiple 테스트부터
  • 스택 구조와 인자 파싱이 어떻게 들어가는지 체감할 수 있어요!
profile
그래도 해야지 어떡해

0개의 댓글