이 저장소는 책의 실습 subsection을 그대로 따라가며 MMOS를 단계적으로 만들어 가기 위한 연습용 저장소입니다.
핵심 원칙:
- 작은 단위로 커밋을 쌓는다.
- 각 커밋은 책의 특정 subsection과 1:1로 대응되도록 유지한다.
- 빌드/실행은 컨테이너 내부 기준으로 맞춘다.
Ubuntu 기반 WSL2에서는 저장소의 설정 스크립트로 rootless Podman과 이미지까지 준비할 수 있다.
저장소는 Windows 드라이브(/mnt/c 등)가 아니라 WSL의 Linux 파일시스템 아래에 두는 것을 권장한다.
cd /home/nicesj/mmrnd/mmos/mmos-practice
./scripts/setup-wsl2-podman.sh스크립트의 패키지 설치 단계에서는 sudo 비밀번호를 WSL 터미널에 직접 입력해야 한다. 준비가 끝나면 다음과 같이 검증한다.
./docker.run
# container inside
make test
make mmos.bin PROFILE=release
make run-nographic PROFILE=release RUN_SECONDS=8Virtual Platform에 전달할 Foundation 배포 입력 산출물은 컨테이너 안에서 한 번에 빌드할 수 있다.
make dist PROFILE=release이 타깃은 release kernel ELF(target/riscv64gc-unknown-none-elf/release/mmos-practice)와
Foundation disk image(build/foundation.img)를 만들고 이미지 안의 /sbin/init, /bin/sh,
/lib/libmmos_user_libc.so 배치를 검증한다. .mvpkg 패키징과 서명은 Virtual Platform이
이 두 immutable input을 측정한 뒤 담당한다.
Podman이 이미 설치되어 있다면 패키지 설치를 생략할 수 있다.
./scripts/setup-wsl2-podman.sh --skip-installWSL2에서는 rootless Podman 네트워크로 slirp4netns를 사용한다. Podman 5의 기본값인
pasta가 Windows나 VPN에서 전달된 많은 host route를 복제하다
netlink: Too many routes to duplicate로 실패하는 경우를 피하기 위한 설정이다.
이전에 rootful Docker가 만든 target, build, user/*/target 산출물이 있으면 설정
스크립트가 해당 생성 디렉터리만 현재 WSL 사용자 소유로 교정한다.
docs/practice-roadmap.md에서 현재 장의 목표를 확인한다.- 해당 단계 커밋을 checkout하여 상태를 재현한다.
- 같은 흐름으로 로컬에서 다시 구현하고 커밋한다.
run-u-boot는 BOOT_CHAIN 변수로 두 가지 체인을 선택할 수 있다.
# practice 기본 방식: OpenSBI -> u-boot.bin -> MMOS
make run-u-boot PROFILE=release BOOT_CHAIN=smode
# mmos 유사 방식: SPL -> u-boot.itb -> MMOS
make run-u-boot PROFILE=release BOOT_CHAIN=splBOOT_CHAIN=smode:qemu-riscv64_smode_defconfig기반u-boot.bin사용- QEMU
-bios default+-kernel build/u-boot/u-boot.bin
BOOT_CHAIN=spl:- OpenSBI
fw_dynamic.bin+qemu-riscv64_spl_defconfig기반 SPL/ITB 사용 - QEMU
-bios build/u-boot/spl/u-boot-spl+-device loader,file=build/u-boot/u-boot.itb,addr=0x80200000
- OpenSBI
실제 user ELF를 만드는 최소 파이프라인은 아래 명령으로 확인할 수 있다.
cd /home/nicesj/mmrnd/mmos/mmos-practice
make user-init PROFILE=release
make boot-disk PROFILE=releasebuild/user-init.elf:no_std + no_main + _start기반 최소 user app ELFbuild/drive0.img:/sbin/init로 user ELF가 주입된 ext4 부트 이미지
후반부 user runtime 흐름은 CRT와 minimal libc를 분리해서 설명한다.
user/crt: final user ELF의_start를 제공하고 initial stack에서argc/argv/envp를 읽어 libc startup entry인__mmos_libc_start_main()으로 넘긴다.user/libc: startup entry에서 appmain()을 호출하고 return code를exit()로 처리한다. syscall wrapper, stdio bootstrap, startup C-string 출력, environment lookup helper도 libc가 제공한다.- 현재 링크 모델은
init=ET_EXEC,crt=rlib,libc=rlib,shared-object=no이다. - shared object loader 실습에서 runtime dependency로 다루는 대상은 CRT가 아니라
/lib/libmmos_user_libc.so이다.
108장은 실제 QEMU console 입력이 user program의 read(0, ...)까지 도달하는 경로를 연결한다.
SerialDevice::read()는 선택된 console backend에서 한 byte를 받고, 최초 user process가 사용하는
간단한 syscall dispatcher도 SYS_READ를 user buffer copyout과 함께 처리한다.
기존 /sbin/init 회귀 검증은 입력을 기다리지 않도록 유지한다. 별도 user/stdin-probe ELF를
/sbin/init 위치에 넣은 build/stdin-probe.img로 interactive 경로를 독립 검증한다.
make run-stdin-probe PROFILE=release INPUT_BYTE=K RUN_SECONDS=6성공 기준은 [stdin-probe] received=K와 [user-init] exit=0이다. 다음 단계에서는 이 raw byte 입력을
line reader와 shell prompt로 확장한다.
user/shell은 128 byte 고정 line buffer를 사용해 console input을 한 줄로 모은다.
CR/LF, backspace, 최대 길이를 처리하고 help, echo, exit builtin을 제공한다.
아직 init child process 연결 전이므로 독립 build/shell.img에서는 shell ELF를 /sbin/init 위치에 넣어 실행한다.
make run-shell PROFILE=release RUN_SECONDS=8scripted input으로 help, echo hello shell, exit를 차례로 전달해 prompt 반복과 정상 종료를 확인한다.
다음 단계에서 같은 ELF를 /bin/sh에 설치하고 /sbin/init이 child process로 실행하도록 전환한다.
현재 런타임 통합 경로는 메모리 안에서 만든 데모 ext4 이미지를 주 경로로 사용하지 않는다.
QEMU에 연결된 build/drive0.img의 virtio-blk 장치를 커널이 직접 읽고,
MBR 첫 파티션을 PartitionBlockDevice로 보정한 뒤 ext4/VFS에서 /sbin/init을 로딩한다.
QEMU 실행 타깃은 virtio-mmio.force-legacy=false로 modern virtio-mmio transport를 고정하고,
커널 드라이버는 VIRTIO_F_VERSION_1을 협상한 뒤 polling virtqueue로 512바이트 섹터를 읽는다.
교육용 ext4 reader가 현재 1024바이트 블록만 다루므로 scripts/build-boot-disk.sh는
mkfs.ext4 -b 1024로 파일시스템 블록 크기를 명시한다.
ELF 파일처럼 중간 offset에 비어 있는 구간이 생기는 sparse 파일은 ext4 direct block의
0번 hole을 0으로 채워 읽는다.
make user-init
make boot-disk
QEMU virtio-blk drive0
-> VirtioBlockDevice
-> PartitionBlockDevice(drive0p1)
-> Vfs::mount_ext4()
-> load_init_exec_image("/sbin/init")
src/demo_user_init.rs는 여전히 단위 테스트 fixture로 남겨 둔다.
상위 VFS/ELF 로더를 빠르게 검증하기 위한 샘플 이미지 생성기이며, 런타임 주 경로는 실제 boot disk를 사용한다.
HEAD 런타임에서는 12장 smoke test였던 run_user_init_demo()를 더 이상 호출하지 않는다.
부팅 중 user app 실행은 virtio-blk -> ext4 -> /sbin/init -> ELF load -> sret 경로 하나로 확인한다.
- 기본 브랜치:
main - 실습은
main에 선형 히스토리로 유지
챕터별 검증 세트를 선택해서 한 번에 실행하려면 아래 스크립트를 사용합니다.
cd /home/nicesj/mmrnd/mmos/mmos-practice
./scripts/verify_practice_tui.pyTUI 없이 특정 챕터를 바로 확인하려면 --cli를 붙입니다.
각 단계는 해당 챕터 커밋으로 checkout한 뒤 컨테이너 안에서 실행되고,
책에서 기대하는 로그/출력 시그니처가 빠지면 OVERALL FAIL로 종료합니다.
./scripts/verify_practice_tui.py --list
./scripts/verify_practice_tui.py --cli ch28_first_user_sret
./scripts/verify_practice_tui.py --cli ch24_user_app_build --quiet
./scripts/verify_practice_tui.py --cli --all --keep-going --quiet기능:
- 대상 호스트 선택:
localhost// - 챕터별 검증 세트 독립 선택/실행
- 실행 명령, 중간 로그, 최종 결과를 분할 화면에서 실시간 갱신
R실행 전 preflight 수행:- repo sync(
git fetch origin --prune,git pull --ff-only origin main) - 컨테이너 이미지 재빌드(
podman build -t mmos-practice:latest .)
- repo sync(
- 선택한 챕터 기준
git history/changes(diff)를 TUI에서 바로 조회 - 선택한 챕터의 변경 파일을 전체 소스로 열람하고, 추가/수정 라인을 배경색으로 구분
- 소스 뷰어에서 기본 syntax highlighting(언어별 키워드/문자열/주석/숫자) 제공
- 각 검증 명령은
podman run ... mmos-practice:latest컨테이너 내부에서 실행 - 이미지가 없으면 자동 빌드, 펌웨어 서브모듈이 없으면
./scripts/init-submodules.sh자동 실행
조작 키:
Up/Down: 챕터 이동Space: 선택/해제A: 전체 선택C: 전체 해제H: 호스트 순환Y: preflight repo sync ON/OFFI: preflight image rebuild ON/OFFR: 선택한 검증 실행L: 선택한 챕터의 git history 조회D: 선택한 챕터의 changes(diff/patch) 조회F: 선택한 챕터의 변경 파일 소스 뷰어 열기S: 실행 중지 요청PgUp/PgDn: Live Logs 위/아래 스크롤Home/End: Live Logs 맨 위/최신(tail) 이동/: Live Logs 검색어 입력n/N: 다음/이전 검색 결과로 이동Q: 종료(실행 중이 아닐 때)
소스 뷰어 조작 키:
Left/Right: 이전/다음 파일Up/Down: 한 줄 스크롤PgUp/PgDn: 페이지 스크롤Home/End: 파일 맨 위/아래B/Esc/Q: 뷰어 닫기
환경변수(선택):
MMOS_PRACTICE_LOCAL: localhost 실행 시 repo 경로 overrideMMOS_PRACTICE_REMOTE: 원격 실행 시 repo 경로 override