🌙 나는 오늘도 평범한 하루를 보낼 줄 알았어요.
하지만 갑자기 화면 가득 까만 터미널이 나타나더니,
그 위로 숫자와 글자들이 빛처럼 흘러내렸어요…
그리고 그 속에서 목소리가 들렸어요.
『사쿠라, 이제는 커널의 경계를 넘어야 해…
유저 프로그램을 봉인하러 가야 해.』
🌀 그 순간, 내 앞에는 반짝이는 마법진이 펼쳐졌고
process_exec()
라는 낯선 주문이 떠올랐어요.
…이건, 분명히 새로운 카드의 신호였어요.
이번 프로젝트는 단순히 커널 내부에서만 움직이던
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 |
🕯️ ???의 카드 | 아직 누구도 봉인하지 못한 마지막 카드 | (비밀이에요…🤫) |
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() // 유저 모드 진입!
📎 이 흐름을 정확히 이해하지 않으면
"도대체 왜 이 함수가 호출되지?"라는 현상이 계속 생길 거예요!
0x00000000
...
0x00400000 ← 코드 시작
...
USER_STACK: 0x47480000 ↓ (인자, argv, argc)
...
KERN_BASE: 0x8004000000 ← 커널 주소 시작
KERN_BASE
미만에서만 접근 가능!레지스터 | 의미 |
---|---|
%rax | syscall 번호 |
%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 실패!hex_dump()
로 유저 스택 시각화해서 argv[i]
확인printf()
대신 msg()
사용 → 채점 스크립트 깨짐 방지ls
로 확인해보기args-single
, args-multiple
테스트부터