ps·ls가 돌아갑니다. 비결은 EverDrive 플래시카트가 빌려주는 4MB 램과, 저자가 손수 만든 부트로더·드라이버·초소형 유저랜드입니다. (저장소: LinuxMD/linuxmd · 겉은 Shell/Makefile, 실제 코드는 8개 서브모듈의 C · ★45 · TrendShift 17위 · 라이선스 명시 없음 — 서브모듈별 상이)이 레포가 무엇을 하는 물건인가.
README의 첫 질문이 "이거 농담이야?(Is this a joke?)" 이고, 저자의 답은 딱 한 단어 "No" 입니다. 정말로 도는 물건이라는 뜻입니다.
linuxmd는 세가 메가드라이브(북미명 세가 제네시스)에서 리눅스 커널을 부팅시키는 프로젝트입니다. 메가드라이브의 두뇌는 모토로라 68000, 즉 1980년대 워크스테이션과 초기 매킨토시에도 쓰였던 16/32비트 CPU입니다. 이 칩은 리눅스가 원래 요구하는 MMU (Memory Management Unit, 메모리 관리 장치)가 없습니다. 그래서 보통의 리눅스는 여기서 아예 못 돕니다.
linuxmd가 신기한 건 "리눅스처럼 생긴 것"을 흉내 낸 게 아니라, 진짜 리눅스 커널(버전 7.1.0-rc6)을 그대로 빌드해 올린다는 점입니다. 부팅이 끝나면 압축 루트 파일시스템이 마운트되고, 로그인 셸이 뜨고, ls·ps 같은 명령이 동작합니다. 다만 저자 스스로 "지금은 미치도록 느리다(insanely slow)"라고 적어둘 만큼 체감 속도는 굼뜹니다 — 부팅 로그의 계산 속도가 0.72 BogoMIPS로 찍힙니다(요즘 PC는 수천~수만).
트렌딩 이유 · 비슷한 시도 대비 무엇이 다른가.
이 프로젝트는 실용품이 아니라 "할 수 있으니까 한다(because I can)" 계열의 해킹 성취입니다. 그런데도 사람들이 별을 누르고 퍼가는 이유는 두 가지입니다. 첫째, 장난감이 아니라 메인라인 리눅스를 그대로 올렸다는 기술적 무게. 둘째, 그 과정에서 부트로더·커널 드라이버·파일시스템·유저랜드를 전부 손으로 짜 붙였다는 완성도입니다. 저자 Daniel Palmer는 잘 알려지지 않은 옛 칩과 SoC에 리눅스를 올려온 개발자로, 이 저장소의 부팅 로그에도 그의 이름(daniel@thingy.jp)이 커널 저작권 줄에 박혀 있습니다.
레트로 게임기에 "무언가를 띄우는" 홈브루는 많습니다. 하지만 대부분은 게임기 하드웨어를 직접 두드리는 베어메탈 (bare-metal, 운영체제 없이 하드웨어를 직접 제어) 프로그램입니다. linuxmd는 그 위에 통째로 운영체제 커널을 얹었다는 점에서 결이 다릅니다.
| 항목 | 흔한 레트로 홈브루 | linuxmd |
|---|---|---|
| 실행 방식 | 베어메탈 — OS 없이 하드웨어 직접 제어 | 진짜 리눅스 커널이 프로세스·파일시스템·드라이버를 관리 |
| 메모리 | 기본 64KB 안에서 알뜰하게 | EverDrive가 빌려주는 4MB를 커널이 통째로 관리 |
| 재사용성 | 그 게임기 전용 코드 | 리눅스 m68k·nommu 생태계를 그대로 활용 |
| 배우는 것 | 해당 기기 하드웨어 지식 | 부트 체인·커널 포팅·드라이버·파일시스템 통째 |
리눅스 커널만 해도 코드가 1MB 안팎입니다. 메가드라이브 본체의 작업용 램은 64KB — 커널 한 조각도 못 담습니다. 하드웨어를 못 바꾸면 여기서 끝입니다.
Mega EverDrive라는 고급 플래시카트(원래는 롬 파일로 게임을 돌리는 장치)의 특수 기능을 빌립니다. SSF2 매퍼가 4MB 램을 열어주고, EverDrive의 프로토콜로 SD카드에서 파일(커널·루트FS)을 읽어오며, EverDrive가 제공하는 타이머 레지스터로 커널이 시간을 잽니다. 부팅 로그에 DRAM: 3.8 MiB로 찍히는 게 바로 이 램입니다.
정리하면, linuxmd의 매력은 "게임기에서 리눅스"라는 표면적 재미 밑에, 제약이 심한 하드웨어에 운영체제를 올리는 전 과정이 교과서처럼 펼쳐져 있다는 데 있습니다. 다음 섹션부터 그 부품들을 하나씩 뜯어봅니다.
겉의 저장소는 Shell·Makefile이지만, 실제 알맹이는 8개의 서브모듈(각각 별도 저장소)로 흩어져 있습니다.
linuxmd 저장소 자체는 지휘자에 가깝습니다. 빌드 스크립트 몇 개와, git submodule (깃 서브모듈, 다른 저장소를 폴더처럼 끼워 넣는 기능)로 연결된 8개의 하위 프로젝트로 이루어져 있습니다. 진짜 C 코드는 그 서브모듈들 안에 있습니다.
| 레이어 | 기술 / 출처 | 역할 |
|---|---|---|
| 타깃 하드웨어 | 세가 메가드라이브 + Mega EverDrive Pro/Core | 모토로라 68000 CPU(~7.6MHz). EverDrive가 4MB 램·SD 접근·타이머 제공. |
| 부트로더 | Das U-Boot (LinuxMD/u-boot 포크) | 전원이 켜지면 가장 먼저 도는 프로그램. 커널을 램에 올리고 압축을 풀어 실행. |
| 커널 | Linux 7.1.0-rc6 (LinuxMD/linux 포크, m68k·nommu) | 운영체제 핵심. 메가드라이브·EverDrive용 커스텀 드라이버 포함. |
| 루트 파일시스템 | erofs (읽기 전용 압축 FS) | SD카드의 m68k.erofs 이미지를 루트(/)로 마운트. |
| 유저랜드 | smolutils (LinuxMD/smolutils, GPL-3.0) | 자체 제작 init·셸(smolsh)·미니 coreutils. BusyBox보다도 작게. |
| 툴체인 | buildroot (LinuxMD/buildroot 포크) | m68k-linux 크로스 컴파일러만 뽑아 씀(루트FS 빌드엔 안 씀). |
| 루트FS 빌더 | tarwak (LinuxMD/tarwak, meson · libarchive · cJSON · libcap) | JSON으로 적은 파일 트리 → tar → erofs. 권한(capability)까지 부여. |
| PC 연결 도구 | medtool (LinuxMD/medtool) | PC에서 EverDrive의 USB 시리얼로 콘솔·메모리에 접근. |
| 에뮬레이터 | QEMU 포크 (fifteenhex/qemu, -M megadrive) | 실물 없이도 부팅을 체험하도록 메가드라이브+EverDrive를 흉내. |
| 이미지 도구 | png2tile (yuv422/png2tile) | PNG(터널 마스코트 Tux 로고)를 VDP 타일 포맷으로 변환. |
먼저 전체 그림 두 장(빌드할 때 / 부팅할 때)을 보고, 그다음 "전원을 켜면" 흐름 한 줄기를 끝까지 따라갑니다.
linuxmd에는 두 개의 서로 다른 시간대가 있습니다. 하나는 PC에서 빌드할 때(재료를 굽는 부엌), 다른 하나는 메가드라이브에서 부팅할 때(손님상에 나가는 순간)입니다. 초보자가 헷갈리는 지점이 정확히 여기라, 그림을 나눠 그립니다.
핵심은 buildroot를 "루트 파일시스템"이 아니라 "컴파일러"를 얻는 데만 쓴다는 점입니다. README도 "buildroot로 rootfs를 만들지는 않는다"고 못 박습니다 — 68000용 바이너리를 뽑는 크로스 컴파일러 (cross compiler, 다른 CPU용 프로그램을 만들어주는 번역기)를 구하는 가장 덜 아픈 방법이라서 빌려 쓸 뿐입니다.
가장 대표적인 동작, 즉 부팅을 처음부터 끝까지 손으로 따라가 봅니다. 곁가지(에러 처리·타이밍 조율)는 생략하고 핵심 길(happy path)만 봅니다.
① 메가드라이브 메뉴에서 u-boot.bin을 골라 "start game"을 누르면, 게임 대신 U-Boot가 뜹니다. ② U-Boot가 SD카드에서 압축된 커널 vmlinux.lz4(약 744KB)를 램에 올리고, 압축을 풀어(약 1.27MB) 실행합니다. 커널을 압축해 두는 건 느린 SD 읽기·좁은 램을 아끼려는 전형적 수법입니다. ③ 커널이 깨어나면서 clocksource (클럭소스, 커널이 시간을 재는 기준 시계)로 everdrive-timer를 등록합니다 — 시계가 없으면 커널은 스케줄링을 못 하니 이게 1순위입니다. ④ 이어 everdrive-blk 드라이버가 SD카드의 m68k.erofs를 /dev/edblk라는 블록 장치(디스크)로 등록하고, 커널은 그걸 루트 파일시스템으로 마운트합니다. ⑤ 마지막으로 커널은 /sbin/init(=smolutils)을 첫 프로세스로 실행합니다. init은 커널이 넘겨준 명령줄 인자 smolinit.getty=...를 읽어 콘솔 두 곳(USB 시리얼 + TV 화면)에 로그인 셸을 띄웁니다.
왜 부품을 8개 저장소로 쪼갰나? U-Boot·리눅스·QEMU는 원래 거대한 오픈소스입니다. linuxmd는 이들을 통째로 복사하는 대신, 각 원본을 살짝 고친 포크(fork)를 서브모듈로 "핀 고정"해 둡니다. 요리로 치면 밀가루·버터를 직접 만들지 않고, 특정 브랜드의 특정 로트 번호를 지정해 사 오는 것과 같습니다 — 누가 언제 빌드해도 똑같은 재료가 모이도록.
겉 저장소는 "빌드 스크립트 + 8개 서브모듈"이 전부입니다. 각 폴더가 무슨 일을 하는지 봅니다.
| 파일/폴더 | 역할 |
|---|---|
build*.sh | 다섯 개의 빌드 단계. 번호 순서(툴체인→u-boot→medtool→linux→rootfs)가 곧 의존 관계다. 각 스크립트는 10줄 안팎으로 짧다. |
Makefile | 부팅 로고 파이프라인. tuxhead.png를 png2tile로 VDP 타일·팔레트로 바꾼 뒤 xxd -i로 C 헤더(tuxhead.h)로 구워 커널에 심는다. |
linux/ | 핵심 ★ — 리눅스 커널 포크. 아키텍처 폴더 arch/m68k에 megadrive_defconfig와 everdrive 드라이버들이 들어간다. |
smolutils/ | 핵심 ★ — 부팅 후 유저 공간 전체. init.c·smolsh.c·ls.c… 30여 개의 작은 C 파일. |
tarwak/ | rootfs.tarwak.json에 적은 파일 트리를 tar로 만든다. meson으로 빌드하는 PC측 도구. |
medtool/·qemu/ | 개발 편의 도구. medtool은 PC에서 콘솔을 잡고, qemu는 실물 없이 돌려보게 해준다. |
.gitmodules | 8개 서브모듈의 주소와 커밋을 못 박아 둔 목록. "재료 주문서". |
이 레포에서 진짜로 배울 수 있는 것 + 어느 파일을 열어보면 되는지.
smolutils README가 그 이유를 정확히 설명합니다. MMU가 없으면 물리 페이지 공유가 안 됩니다. 일반 리눅스에선 여러 프로그램이 같은 라이브러리(libc 등)를 물리 메모리 한 벌만 두고 나눠 씁니다. nommu에선 그게 불가능해, 프로세스마다 libc·BusyBox 사본을 통째로 또 싣습니다. 하나가 100KB라도 몇 개만 띄우면 4MB가 순식간에 바닥납니다.
그래서 smolutils는 nolibc (노-libc, 리눅스 커널이 제공하는 헤더만으로 된 초경량 libc)에 기대어, 표준 라이브러리 없이(-nostdlib) 아주 작은 정적 실행파일들을 만듭니다. 어디를 볼까: smolutils/common.mk(빌드할 프로그램 목록)와 Makefile.68000(컴파일 플래그).
# common.mk — 만들 프로그램 목록 (BusyBox의 축소판)
PROGS_SYSTEM = init getty startup
PROGS_USER = smolsh dmesg ls ps cat sha256sum xxd \
man less uname cp touch chmod chown kill df mount
# Makefile.68000 — 68000 전용, 극한 다이어트 플래그
COPTS += -m68000 -mstrict-align # 68000 명령어만 사용
_COPTS += -nostdlib -std=c99 -Os -flto # 표준libc 없이, 크기 최우선
여러 명령을 한 실행파일에 몰아넣고 이름으로 갈라 부르는 멀티콜 바이너리 (multicall binary, BusyBox가 쓰는 그 방식) 패턴을 씁니다. 심볼릭 링크로 mkdir·rmdir·mv를 touch 하나에 연결하는 식으로 코드·용량을 아낍니다. 설정은 커널의 KConfig를 흉내 낸 매크로 한 뭉치(config.h)로 처리하는데, 저자 스스로 주석에 "This is like KConfig, but awful(KConfig 같은데 허접함)"이라 적어둔 게 이 프로젝트의 유머 코드입니다.
// multicall.h — 이름을 비교해 해당 함수로 분기
for (i = 0; i < ARRAY_SIZE(progs); i++)
if (strcmp(progs[i].progname, argv0) == 0)
return progs[i].progcb(argc, argv, envp);
부팅 로그에 등장하는 everdrive-timer·everdrive-blk·ttyVDP0·ttyED0는 전부 이 프로젝트가 새로 짠 커널 드라이버입니다. 특히 ttyVDP0는 메가드라이브의 VDP (Video Display Processor, 화면을 그리는 칩)를 이용해 TV 화면에 진짜 텍스트 콘솔을 띄웁니다 — 스크롤도 되고, 우상단 초록 상자가 "심장박동"(커널 살아있음), 그 아래 빨간 상자가 디스크 활동 표시등입니다. 시리얼(ttyED0)이 PC와의 통로라면, ttyVDP0는 게임기 혼자서도 화면에 뭔가 보여주는 통로입니다.
루트 파일시스템을 실제 폴더로 쌓아두지 않고, JSON 한 장으로 기술한 뒤 tar로 굽습니다(그다음 erofs로 변환). 압권은 capability (케이퍼빌리티, root 전체 권한을 쪼갠 세밀한 권한)를 파일의 확장 속성(xattr)으로 심는 부분입니다. 예컨대 ping에는 원시 소켓 권한만 콕 집어 부여합니다 — root로 안 돌려도 되게.
// rootfs.tarwak.json — ping에 필요한 권한만 최소로
"ping": {
"feature": "net",
"xattrs": { "security.capability": "cap_net_raw+ep" }
}
buildlinux.sh는 커널을 빌드한 뒤 strip(디버그 정보 제거)하고 lz4 --best로 압축해 vmlinux.lz4를 만듭니다. 부팅 때 U-Boot가 이걸 램에 올리고 풀어서 실행하죠(로그: 744178 → 1270696 bytes). LZ4 (엘지포, 압축·해제가 아주 빠른 알고리즘)를 고른 건 68000처럼 느린 CPU에서도 해제가 부담 없어서입니다. "느린 저장장치 읽기"와 "느린 CPU 해제" 사이의 균형을 맞춘 선택입니다.
두 갈래 길이 있습니다 — 실물로 갈지, 에뮬레이터로 갈지.
| 경로 | 필요한 것 | 메모 |
|---|---|---|
| 실물 하드웨어 | 세가 메가드라이브 본체 + Mega EverDrive Pro 또는 Core + USB 케이블 + PC(minicom·medtool) | EverDrive는 필수. README상 Pro는 미검증(untested), Core로 확인됨. 일반 에뮬레이터로는 거의 불가. |
| 에뮬레이터(입문 추천) | 포함된 QEMU 포크를 빌드 → runqemu.sh | 실물 없이 부팅 체험 가능. 단, CPU가 실물보다 너무 빨라 "진짜 느낌"은 안 남. |
| 빌드 호스트 | 리눅스 PC (CI 기준 Ubuntu) | 서브모듈 다수를 컴파일하므로 디스크·시간 여유 필요. |
| 빌드 의존 패키지 | build-essential · erofs-utils · meson · libarchive-dev · libcjson-dev · libcap-dev · libglib2.0-dev · libsdl2-dev | CI 워크플로우(build.yml)에 그대로 적혀 있음. 그대로 apt install하면 됨. |
EverDrive는 원래 "게임 롬 파일을 SD카드에 넣어 돌리는 만능 카트리지"입니다. linuxmd는 이 카트리지를 게임 대신 "램 확장 + SD 리더 + 시계"로 개조해 쓰는 셈입니다 — 만능 멀티탭에 원래 없던 콘센트를 찾아내 꽂아 쓰는 것과 비슷합니다.
실물 게임기가 없어도 대부분 QEMU로 손에 익힐 수 있습니다.
리눅스 PC에서 의존 패키지를 깔고, buildtoolchain.sh부터 순서대로 실행한 뒤 buildqemu.sh·runqemu.sh를 돌립니다. 목표는 딱 하나 — 터미널에 smolsh / > 프롬프트가 뜨는 걸 두 눈으로 보는 것. 실물 하드웨어 없이 "게임기 리눅스"를 만나는 가장 빠른 길입니다.
README에 붙은 부팅 로그를 위에서 아래로 읽으며, 각 줄을 이 문서의 4번(부트 체인)과 맞춰봅니다. DRAM: 3.8 MiB(EverDrive 램), clocksource: everdrive-timer(시계 등록), Mounted root (erofs ...)(루트 마운트), Run /sbin/init(첫 프로세스)를 직접 짚어보세요. 커널이 깨어나는 순서가 손에 잡힙니다.
echo나 pwd 같은 간단한 명령을 직접 만들어 봅니다. ① echo.c를 작성하고 ② common.mk의 PROGS_USER에 이름을 추가한 뒤 ③ rootfs.tarwak.json의 /bin에 등록하면, 다시 빌드했을 때 셸에서 불러 쓸 수 있습니다. nolibc 환경이라 표준 라이브러리가 없다는 제약을 몸으로 느끼는 게 핵심 수확입니다.
configlinux.sh는 menuconfig(커널 설정 메뉴)를 띄운 뒤, 바뀐 설정을 megadrive_defconfig로 다시 저장합니다. 필요 없어 보이는 기능을 하나 꺼서 커널을 더 작게 만들어 보고, vmlinux.lz4 크기가 어떻게 변하는지 관찰하세요. "임베디드에서 커널을 다이어트한다"의 첫 경험입니다.
linux 서브모듈을 클론해 arch/m68k 아래에서 everdrive-blk·ttyVDP 드라이버 소스를 찾아 읽습니다. "SD카드 이미지가 어떻게 /dev/edblk라는 디스크로 둔갑하는지", "VDP 타일이 어떻게 콘솔 글자가 되는지"를 코드에서 확인해 보세요. 리눅스 드라이버 모델(등록·probe·읽기/쓰기 콜백)을 실물 예제로 익히는 최고의 교재입니다.
linuxmd를 발판 삼아 "제약된 하드웨어 + 리눅스"를 5주에 훑는 계획.
| 주차 | 주제 | 학습 자료 / 실습 |
|---|---|---|
| 1주차 | 68000·메가드라이브 하드웨어와 nommu 개념 | 이 문서 1·2번 → "MMU가 없다는 것"의 의미 정리. m68k·uClinux 개요 검색. |
| 2주차 | 부트 체인: U-Boot → 커널 → init | 이 문서 4번 + 부팅 로그. U-Boot 공식 문서의 boot 흐름. |
| 3주차 | 루트FS·nolibc·유저랜드 | smolutils 소스 정독(init.c·smolsh.c). erofs·nolibc 개념 학습. |
| 4주차 | 커널 드라이버 & 디바이스 트리 | everdrive 드라이버 읽기(실습 5). devicetree(하드웨어 배치도) 기초. |
| 5주차 | 에뮬레이터로 하드웨어 모델링 이해 | QEMU 포크의 -M megadrive·md-everdrive 장치 코드 훑기. |
본문에 나온 용어 빠른 참조.
| 용어 | 의미 |
|---|---|
| 모토로라 68000 | 메가드라이브의 CPU. 1980년대 널리 쓰인 16/32비트 칩. MMU가 없다. |
| MMU | Memory Management Unit. 가상 주소↔실제 주소 번역 + 메모리 보호 담당 부품. 68000엔 없음. |
| nommu / uClinux | MMU 없는 CPU에서 도는 리눅스. 메모리 보호·주소 가상화가 없다. |
| Flat 바이너리 | 어느 주소에 놓여도 돌아가게 만든 nommu용 실행 형식. |
| EverDrive | 메가드라이브용 고급 플래시카트. 여기선 4MB 램·SD 접근·타이머 공급원. |
| SSF2 매퍼 | EverDrive가 큰 메모리를 은행처럼 나눠 보여주는 방식. 4MB 램의 열쇠. |
| VDP | Video Display Processor. 메가드라이브의 화면 그리는 칩. ttyVDP 콘솔이 이걸 씀. |
| U-Boot | 임베디드 표준 부트로더. 커널을 램에 올려 실행권을 넘긴다. |
| erofs | 읽기 전용 압축 파일시스템. 작고 빨라 임베디드에 적합. |
| nolibc | 리눅스 커널이 제공하는 헤더만으로 된 초경량 libc. 표준 라이브러리 대체. |
| 멀티콜 바이너리 | 여러 명령을 한 실행파일에 넣고 이름으로 갈라 부르는 방식(BusyBox식). |
| clocksource | 커널이 시간을 재는 기준 시계 드라이버. everdrive-timer가 이 역할. |
| capability | root 전권을 잘게 쪼갠 세밀 권한. ping엔 cap_net_raw만 부여. |
| 크로스 컴파일러 | 내 PC에서 다른 CPU(68000)용 프로그램을 만들어주는 번역기. |
| LZ4 | 해제가 매우 빠른 압축 알고리즘. 느린 CPU에서 커널 압축에 적합. |
| BogoMIPS | 커널이 부팅 때 재는 대략적 CPU 속도. linuxmd는 0.72(매우 느림). |