update qemu study
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
*.bin
|
||||||
|
*.img
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# qemu study
|
||||||
|
|
||||||
|
Studying the booting sequence, qemu, bios, kernel, ...etc
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
; boot.asm
|
||||||
|
[BITS 16]
|
||||||
|
[ORG 0x7C00]
|
||||||
|
|
||||||
|
start:
|
||||||
|
; 1. 세그먼트 레지스터 초기화 (DS, ES, SS)
|
||||||
|
xor ax, ax ; AX = 0
|
||||||
|
mov ds, ax ; DS = 0
|
||||||
|
mov es, ax ; ES = 0
|
||||||
|
mov ss, ax ; SS = 0
|
||||||
|
mov sp, 0x7C00 ; 스택 포인터를 0x7C00 밑으로 설정 (안전지대)
|
||||||
|
|
||||||
|
; 2. 커널 로딩 안내 메시지 출력
|
||||||
|
mov si, msg_boot
|
||||||
|
call print_string
|
||||||
|
|
||||||
|
; 3. 디스크에서 커널 읽기 (BIOS INT 0x13 사용)
|
||||||
|
; 우리는 커널을 디스크의 2번째 섹터(Sector 2)에 저장할 것입니다.
|
||||||
|
; 그것을 메모리 주소 0x8000 번지에 로드합시다.
|
||||||
|
|
||||||
|
mov ah, 0x02 ; BIOS 기능: 섹터 읽기
|
||||||
|
mov al, 1 ; 읽을 섹터 수: 1개
|
||||||
|
mov ch, 0 ; 실린더 번호: 0
|
||||||
|
mov cl, 2 ; 섹터 번호: 2 (1번은 부트로더니까 2번부터)
|
||||||
|
mov dh, 0 ; 헤드 번호: 0
|
||||||
|
; DL은 BIOS가 부팅된 드라이브 번호를 자동으로 넣어줍니다. (건드리지 말 것)
|
||||||
|
|
||||||
|
mov bx, 0x8000 ; 데이터를 저장할 메모리 주소 (ES:BX = 0x0000:0x8000)
|
||||||
|
int 0x13 ; 인터럽트 발생! (읽어와라!)
|
||||||
|
|
||||||
|
jc disk_error ; 만약 에러가 나면(Carry Flag=1) 에러 처리로 점프
|
||||||
|
|
||||||
|
; 4. 커널로 점프!
|
||||||
|
; 0x8000 번지에 커널 코드가 도착했으니 거기로 제어권을 넘깁니다.
|
||||||
|
mov si, msg_jump
|
||||||
|
call print_string
|
||||||
|
jmp 0x8000
|
||||||
|
|
||||||
|
disk_error:
|
||||||
|
mov si, msg_err
|
||||||
|
call print_string
|
||||||
|
jmp $ ; 무한 루프
|
||||||
|
|
||||||
|
; --- 문자열 출력 함수 ---
|
||||||
|
print_string:
|
||||||
|
mov ah, 0x0E ; BIOS 기능: 텔레타입 출력 (한 글자 찍기)
|
||||||
|
.loop:
|
||||||
|
lodsb ; [SI]의 데이터를 AL로 로드하고 SI 1 증가
|
||||||
|
cmp al, 0 ; 문자열 끝(NULL)인지 확인
|
||||||
|
je .done
|
||||||
|
int 0x10 ; 화면에 출력 (BIOS INT 0x10)
|
||||||
|
jmp .loop
|
||||||
|
.done:
|
||||||
|
ret
|
||||||
|
|
||||||
|
msg_boot db "Bootloader started...", 0x0D, 0x0A, 0
|
||||||
|
msg_jump db "Jumping to Kernel...", 0x0D, 0x0A, 0
|
||||||
|
msg_err db "Disk Read Error!", 0
|
||||||
|
|
||||||
|
; --- 부트 서명 (MBR 필수) ---
|
||||||
|
times 510-($-$$) db 0 ; 510바이트까지 0으로 채움
|
||||||
|
dw 0xAA55 ; 마지막 2바이트는 무조건 0x55, 0xAA (매직 넘버)
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
; kernel.asm
|
||||||
|
[BITS 16] ; 여전히 16비트입니다.
|
||||||
|
[ORG 0x8000] ; 부트로더가 우리를 0x8000에 내려줬으므로 기준 주소를 맞춥니다.
|
||||||
|
|
||||||
|
kernel_entry:
|
||||||
|
; 커널이 실행되었다는 메시지 출력
|
||||||
|
mov si, msg_kernel
|
||||||
|
call print_string
|
||||||
|
|
||||||
|
; 할 일 다 했으니 죽기 (무한 루프)
|
||||||
|
cli ; 인터럽트 끄기 (방해 금지)
|
||||||
|
hlt ; CPU 정지 (전력 소모 최소화)
|
||||||
|
jmp $ ; 혹시 깨어나면 다시 제자리 걸음
|
||||||
|
|
||||||
|
; --- 문자열 출력 함수 (부트로더와 동일) ---
|
||||||
|
print_string:
|
||||||
|
mov ah, 0x0E
|
||||||
|
.loop:
|
||||||
|
lodsb
|
||||||
|
cmp al, 0
|
||||||
|
je .done
|
||||||
|
int 0x10
|
||||||
|
jmp .loop
|
||||||
|
.done:
|
||||||
|
ret
|
||||||
|
|
||||||
|
msg_kernel db "Hello from BARE METAL KERNEL!", 0x0D, 0x0A, 0
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# boot & kernel
|
||||||
|
|
||||||
|
## 1. 어셈블리 컴파일 (바이너리 파일 생성)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nasm -f bin boot.asm -o boot.bin
|
||||||
|
nasm -f bin kernel.asm -o kernel.bin
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. 디스크 이미지 만들기
|
||||||
|
|
||||||
|
### boot.bin(512바이트) 뒤에 kernel.bin을 붙입니다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat boot.bin kernel.bin > os.img
|
||||||
|
|
||||||
|
truncate -s 1440k os.img
|
||||||
|
|
||||||
|
qemu-system-x86_64 -drive format=raw,file=os.img -display curses
|
||||||
|
```
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
; mybios.asm
|
||||||
|
[BITS 16]
|
||||||
|
[ORG 0]
|
||||||
|
|
||||||
|
bios_start:
|
||||||
|
; 1. 세그먼트 초기화 (CS=0xF000 이므로 DS, ES도 맞춤)
|
||||||
|
mov ax, 0xF000
|
||||||
|
mov ds, ax
|
||||||
|
mov es, ax
|
||||||
|
|
||||||
|
; mov ss, ax
|
||||||
|
; mov sp, 0xFFF0
|
||||||
|
xor ax, ax ; AX = 0
|
||||||
|
mov ss, ax ; SS = 0x0000 (RAM)
|
||||||
|
mov sp, 0xFFFE ; 0x0000 ~ 0xFFFF 영역 중 가장 끝!
|
||||||
|
; mov sp, 0x7C00 ; SP = 0x7C00 (부트로더가 쓰는 안전한 RAM 공간)
|
||||||
|
|
||||||
|
; 2. 시리얼 포트(COM1) 초기화 (필수!)
|
||||||
|
mov dx, 0x3FB ; Line Control Register
|
||||||
|
mov al, 0x80 ; DLAB bit set
|
||||||
|
out dx, al
|
||||||
|
|
||||||
|
mov dx, 0x3F8 ; Divisor (Low) - 9600 baud
|
||||||
|
mov al, 0x0C
|
||||||
|
out dx, al
|
||||||
|
|
||||||
|
mov dx, 0x3F9 ; Divisor (High)
|
||||||
|
mov al, 0x00
|
||||||
|
out dx, al
|
||||||
|
|
||||||
|
mov dx, 0x3FB ; LCR: 8 bits, no parity, 1 stop bit
|
||||||
|
mov al, 0x03
|
||||||
|
out dx, al
|
||||||
|
|
||||||
|
; 3. 메시지 출력
|
||||||
|
mov si, msg_bios
|
||||||
|
call print_serial
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
; ===========================================================
|
||||||
|
; [ACPI Shutdown Sequence for QEMU PIIX4 Chipset]
|
||||||
|
; Target: Bus 0, Device 1, Function 3 (Power Management)
|
||||||
|
; Address Format: 1(En) | Bus(0) | Dev(1) | Func(3) | Reg
|
||||||
|
; Hex Prefix: 0x80 | 00 | 08 | 03 | ..
|
||||||
|
; Combined: 0x80000B.. (Function 3 is the key)
|
||||||
|
; ===========================================================
|
||||||
|
|
||||||
|
; Step 0: Enable I/O Access (Command Register - 0x04)
|
||||||
|
; We must tell the chipset to allow I/O port access first.
|
||||||
|
mov dx, 0xCF8
|
||||||
|
mov eax, 0x80000B04 ; Address: Func 3, Reg 0x04
|
||||||
|
out dx, eax
|
||||||
|
|
||||||
|
mov dx, 0xCFC
|
||||||
|
mov eax, 0x0001 ; Value: Bit 0 (I/O Space Enable)
|
||||||
|
out dx, eax
|
||||||
|
|
||||||
|
; Step 1: Set ACPI Base Address (PMBA - 0x40)
|
||||||
|
; We map the ACPI registers to I/O port 0xB000.
|
||||||
|
mov dx, 0xCF8
|
||||||
|
mov eax, 0x80000B40 ; Address: Func 3, Reg 0x40
|
||||||
|
out dx, eax
|
||||||
|
|
||||||
|
mov dx, 0xCFC
|
||||||
|
mov eax, 0x0000B001 ; Value: Base 0xB000 | Bit 0 (Hardwired to 1)
|
||||||
|
out dx, eax
|
||||||
|
|
||||||
|
; Step 2: Enable ACPI Function (PMREGMISC - 0x80)
|
||||||
|
mov dx, 0xCF8
|
||||||
|
mov eax, 0x80000B80 ; Address: Func 3, Reg 0x80
|
||||||
|
out dx, eax
|
||||||
|
|
||||||
|
mov dx, 0xCFC
|
||||||
|
mov eax, 0x00000001 ; Value: Bit 0 (ACPI Enable)
|
||||||
|
out dx, eax
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
mov si, msg_off
|
||||||
|
call print_serial
|
||||||
|
|
||||||
|
; ; Exit with return value 0
|
||||||
|
mov dx, 0xB004
|
||||||
|
mov ax, 0x2000 ; SLP_TYP(Soft Off) | SLP_EN
|
||||||
|
out dx, ax
|
||||||
|
|
||||||
|
; ; reboot
|
||||||
|
; mov dx, 0x64
|
||||||
|
; mov al, 0xFE
|
||||||
|
; out dx, al
|
||||||
|
|
||||||
|
; ; exit (isa-debug-exit; need '-device isa-debug-exit,iobase=0x501,iosize=0x04')
|
||||||
|
; mov dx, 0x501
|
||||||
|
; mov al, 0x02 ; exit code (2) -> qemu exits with exit code (al<<1)|1
|
||||||
|
; out dx, al
|
||||||
|
|
||||||
|
; 그래도 안 꺼지면 그냥 멈춰라
|
||||||
|
cli
|
||||||
|
hlt
|
||||||
|
jmp $
|
||||||
|
|
||||||
|
; --- 시리얼 출력 함수 ---
|
||||||
|
print_serial:
|
||||||
|
mov dx, 0x3F8 ; COM1 데이터 포트
|
||||||
|
.loop:
|
||||||
|
lodsb ; [DS:SI] -> AL
|
||||||
|
cmp al, 0
|
||||||
|
je .done
|
||||||
|
out dx, al ; 한 글자 전송
|
||||||
|
jmp .loop
|
||||||
|
.done:
|
||||||
|
ret
|
||||||
|
|
||||||
|
; msg_bios db "Hello! This is a CUSTOM BIOS running on QEMU!", 0x0D, 0x0A, 0
|
||||||
|
msg_bios db "Hello! This is a CUSTOM BIOS running on QEMU!", 0x0D, 0x0A, 0
|
||||||
|
msg_off db "ACPI Enabled. Powering Off NOW!", 0x0D, 0x0A, 0
|
||||||
|
|
||||||
|
|
||||||
|
; Padding
|
||||||
|
times 65520-($-$$) db 0
|
||||||
|
|
||||||
|
reset_vector:
|
||||||
|
jmp 0xF000:0x0000
|
||||||
|
times 16-($-reset_vector) db 0
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
# BIOS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nasm -f bin mybios.asm -o mybios.bin
|
||||||
|
qemu-system-i386 -bios ./mybios.bin -nographic
|
||||||
|
# qemu-system-i386 -bios ./mybios.bin -nographic -device isa-debug-exit,iobase=0x501,iosize=0x04
|
||||||
|
```
|
||||||
|
|
||||||
|
## explanations
|
||||||
|
|
||||||
|
```diagram
|
||||||
|
[ 우리가 만든 파일 ] [ 실제 메모리 주소 (Legacy Area) ]
|
||||||
|
(mybios.bin, 64KB) (ROM 영역, Write Protected)
|
||||||
|
|
||||||
|
파일 시작 (0x0000) +------------------+ +-----------------------+ 0xF0000 (시작점)
|
||||||
|
| bios_start: | | jmp, mov ax... |
|
||||||
|
| (초기화 코드들) | ==> | (우리의 코드 실행됨) |
|
||||||
|
+------------------+ +-----------------------+
|
||||||
|
| | | |
|
||||||
|
| .......... | | |
|
||||||
|
| (0으로 채운 공간) | | |
|
||||||
|
| .......... | | |
|
||||||
|
| | | |
|
||||||
|
파일 끝 (0xFFFF) +------------------+ +-----------------------+ 0xFFFF0 (리셋 벡터)
|
||||||
|
(정확히 65535) | reset_vector: | | jmp 0xF000:0000 | <--- CPU가 눈뜨자마자 여기 실행
|
||||||
|
+------------------+ +-----------------------+ 0xFFFFF (끝)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gemini
|
||||||
|
|
||||||
|
지금 우리가 다루고 있는 **x86 리얼 모드(16-bit)** 기준으로, CPU 안에는 **"초고속 작업 공간(레지스터)"**들이 서랍처럼 정리되어 있습니다.
|
||||||
|
|
||||||
|
가장 중요한 **세그먼트 레지스터**부터 시작해서, CPU의 모든 서랍장을 털어보겠습니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1. 세그먼트 레지스터 (Segment Registers) - "동네 지정"
|
||||||
|
|
||||||
|
**"여기는 내 구역이야!"** 라고 선언하는 레지스터들입니다.
|
||||||
|
16비트 메모리 주소 체계(`Segment:Offset`)의 핵심입니다.
|
||||||
|
|
||||||
|
* **역할:** 메모리의 **시작 위치(Base Address)**를 가리킵니다.
|
||||||
|
* **특징:** **값을 직접 넣을 수 없습니다.** (`mov ds, 0xF000` ❌) 반드시 범용 레지스터를 거쳐야 합니다. (`mov ax, 0xF000` -> `mov ds, ax` ⭕)
|
||||||
|
|
||||||
|
| 이름 | 풀네임 | 역할 & 설명 | 짝꿍 레지스터 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| **CS** | **Code** Segment | **"실행할 코드가 있는 동네"**. CPU는 무조건 `CS:IP`가 가리키는 곳의 명령어를 실행합니다. `jmp` 명령어로만 바꿀 수 있습니다. | **IP** |
|
||||||
|
| **DS** | **Data** Segment | **"변수(데이터)가 있는 동네"**. `mov al, [0x100]`을 하면 `DS`를 기준으로 봅니다. | **SI, BX** |
|
||||||
|
| **SS** | **Stack** Segment | **"스택이 있는 동네"**. `push`, `pop` 할 때 쓰는 공간의 기준점입니다. | **SP, BP** |
|
||||||
|
| **ES** | **Extra** Segment | "추가 데이터 동네". `memcpy` 같은 걸 할 때 '복사할 목적지'로 자주 쓰입니다. | **DI** |
|
||||||
|
| **FS, GS** | (Additional) | 나중에 추가된 여분 세그먼트. (보통 OS 커널 구조체 등을 가리킬 때 씀) | - |
|
||||||
|
|
||||||
|
> **💡 공식:** 물리 주소 = `(Segment * 16) + Offset`
|
||||||
|
> 예: `DS=0xF000`, 오프셋 `0x0050` → 실제 주소 `0xF0050`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. 범용 레지스터 (General Purpose Registers) - "일꾼"
|
||||||
|
|
||||||
|
가장 많이 쓰고, 자유롭게 지지고 볶는 레지스터입니다.
|
||||||
|
16비트(AX)는 8비트(AH, AL)로 쪼개서 쓸 수 있습니다.
|
||||||
|
|
||||||
|
| 이름 | 풀네임 | 주특기 (물론 다른 용도로도 씀) |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| **AX** | **Accumulator** | **"계산기 & 입출력"**. 산술 연산, `IN/OUT` 명령어는 무조건 AX(AL)를 통합니다. |
|
||||||
|
| **BX** | **Base** | **"주소 참조"**. 메모리 주소를 저장하는 용도(`[BX]`)로 유일하게 쓰였던 범용 레지스터. |
|
||||||
|
| **CX** | **Counter** | **"반복 카운터"**. 반복문(`LOOP`)이나 문자열 복사(`REP`)할 때 횟수를 셉니다. |
|
||||||
|
| **DX** | **Data** | **"보조 데이터 & 포트"**. 큰 수 곱셈의 상위 비트 저장, 혹은 **I/O 포트 번호** 저장용. |
|
||||||
|
|
||||||
|
* **구조:** `EAX (32bit)` -> `AX (16bit)` -> `AH (High 8bit) + AL (Low 8bit)`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. 인덱스 & 포인터 레지스터 (Index & Pointer) - "내비게이션"
|
||||||
|
|
||||||
|
메모리의 **특정 위치(Offset)**를 가리키는 용도입니다. 세그먼트 레지스터와 짝을 이룹니다.
|
||||||
|
|
||||||
|
| 이름 | 풀네임 | 역할 | 짝꿍 세그먼트 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| **SP** | **Stack** Pointer | **"스택 꼭대기"**. `push`하면 줄어들고 `pop`하면 늘어납니다. 절대 함부로 건드리면 안 됩니다. | **SS** |
|
||||||
|
| **BP** | **Base** Pointer | **"스택 바닥 기준"**. 함수 호출 시 지역변수에 접근할 때 기준점으로 씁니다. | **SS** |
|
||||||
|
| **SI** | **Source** Index | **"복사할 원본 위치"**. 문자열 복사할 때 '어디서' 가져올지 가리킵니다. | **DS** |
|
||||||
|
| **DI** | **Dest** Index | **"복사할 목적지 위치"**. 문자열 복사할 때 '어디로' 보낼지 가리킵니다. | **ES** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. 명령어 포인터 (Instruction Pointer) - "현재 위치"
|
||||||
|
|
||||||
|
* **IP (Instruction Pointer):**
|
||||||
|
* **"다음에 실행할 명령어의 오프셋"**입니다.
|
||||||
|
* 개발자가 직접 `mov ip, 0x1000` 처럼 수정할 수 **없습니다.**
|
||||||
|
* 오직 `jmp`, `call`, `ret` 같은 명령어를 통해서만 바뀝니다.
|
||||||
|
* 항상 **CS**와 함께 (`CS:IP`) 움직입니다.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. 플래그 레지스터 (FLAGS) - "상태 알림판"
|
||||||
|
|
||||||
|
연산 결과의 상태를 비트 단위로 저장합니다. `if`문(분기)의 핵심입니다.
|
||||||
|
|
||||||
|
| 플래그 | 이름 | 의미 (값이 1일 때) |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| **ZF** | **Zero** Flag | 결과가 **0**이다. (`cmp a, b` 했을 때 같으면 켜짐) |
|
||||||
|
| **CF** | **Carry** Flag | 받아올림/빌림이 발생했다. (unsigned 연산 범위 초과) |
|
||||||
|
| **OF** | **Overflow** Flag | 부호 있는 연산에서 범위가 터졌다. |
|
||||||
|
| **SF** | **Sign** Flag | 결과가 음수(-)다. |
|
||||||
|
| **IF** | **Interrupt** Flag | **"인터럽트 받아요"**. (`cli`하면 0, `sti`하면 1) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. 컨트롤 레지스터 (Control Registers) - "관리자 설정"
|
||||||
|
|
||||||
|
이건 우리가 맨 처음에 얘기했던, **CPU의 모드(System)** 자체를 제어하는 특수 레지스터입니다. (일반 프로그램은 못 건드림)
|
||||||
|
|
||||||
|
| 이름 | 역할 |
|
||||||
|
| --- | --- |
|
||||||
|
| **CR0** | **"모드 스위치"**. 리얼 모드 vs 보호 모드 전환, 페이징 켜기/끄기 등. |
|
||||||
|
| **CR2** | **"사고 현장"**. Page Fault가 났을 때 어떤 주소 때문인지 기록됨. |
|
||||||
|
| **CR3** | **"지도책"**. 페이지 디렉터리(Page Directory)의 물리 주소를 저장. |
|
||||||
|
| **CR4** | **"확장 기능"**. 4MB 페이지 등 최신 기능 활성화. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ✨ 요약: 우리가 짠 코드 해석
|
||||||
|
|
||||||
|
이제 작성하신 BIOS 코드 한 조각이 완벽하게 해석되실 겁니다.
|
||||||
|
|
||||||
|
```nasm
|
||||||
|
mov ax, 0xF000 ; (1) AX(일꾼)에게 0xF000을 줌
|
||||||
|
mov ds, ax ; (2) DS(데이터 동네)를 0xF000으로 설정 (AX를 통해서만 가능)
|
||||||
|
mov es, ax ; (3) ES(추가 동네)도 0xF000으로 설정
|
||||||
|
|
||||||
|
mov ss, 0 ; (X) 에러! SS(세그먼트)에 바로 숫자 못 넣음
|
||||||
|
xor ax, ax ; (4) AX를 0으로 만듦 (일꾼 초기화)
|
||||||
|
mov ss, ax ; (5) SS(스택 동네)를 0으로 설정
|
||||||
|
mov sp, 0x7C00 ; (6) SP(스택 꼭대기 포인터)를 0x7C00으로 설정
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
이 "서랍장 정리"가 끝나야 비로소 CPU가 꼬이지 않고 일을 할 수 있는 것입니다.
|
||||||
|
|
||||||
|
## gemini2
|
||||||
|
|
||||||
|
네, **정확하게 보셨습니다!** 방금 설명해 드린 내용은 **"16비트 리얼 모드(Real Mode)"** 기준입니다.
|
||||||
|
|
||||||
|
컴퓨터가 **32비트(Pintos)**나 **64비트(현대 OS)**로 모드를 전환하면, 레지스터의 **이름**과 **크기**, 그리고 **세그먼트의 역할**이 완전히 달라집니다.
|
||||||
|
|
||||||
|
어떻게 변하는지 딱 정리해 드릴게요.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1. 레지스터 크기의 진화 (러시아 인형 구조)
|
||||||
|
|
||||||
|
모드가 바뀔 때마다 레지스터가 **확장(Extended)**됩니다. 하지만 하위 호환성을 위해 **옛날 이름도 여전히 쓸 수 있습니다.**
|
||||||
|
|
||||||
|
| 구분 | 16-bit (Real Mode) | 32-bit (Protected Mode) | 64-bit (Long Mode) |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| **이름 규칙** | **AX**, BX, CX... | **EAX**, EBX... (E=Extended) | **RAX**, RBX... (R=Register) |
|
||||||
|
| **크기** | 2 Byte (16-bit) | 4 Byte (32-bit) | 8 Byte (64-bit) |
|
||||||
|
| **특징** | 64KB까지만 계산 가능 | 4GB까지 계산 가능 | 1844경(Exabyte)까지 가능 |
|
||||||
|
| **추가된 친구** | 없음 | 없음 | **R8 ~ R15** (8개 추가됨) |
|
||||||
|
|
||||||
|
> **재밌는 점:**
|
||||||
|
> 64비트 모드에서도 `mov al, 0x1`이라고 하면 `RAX` 레지스터의 **맨 끝 1바이트**만 건드립니다. 즉, `RAX` 안에 `EAX`가 있고, 그 안에 `AX`가 있고, 그 안에 `AL`이 들어있는 구조입니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. 세그먼트 레지스터의 대격변 (CS, DS...)
|
||||||
|
|
||||||
|
이게 OS 개발에서 가장 헷갈리는 부분입니다. 레지스터 이름(CS, DS)은 그대로인데 **하는 짓이 완전히 바뀝니다.**
|
||||||
|
|
||||||
|
#### A. 16비트 리얼 모드 (지금 우리가 하는 것)
|
||||||
|
|
||||||
|
* **역할:** **"곱하기 16 계산기"**
|
||||||
|
* **공식:** `(CS * 16) + IP` = 물리 주소
|
||||||
|
* **비유:** "CS값은 10층이야." \rightarrow 실제 높이는 100m.
|
||||||
|
|
||||||
|
#### B. 32비트 보호 모드 (Pintos, 윈도우 XP 등)
|
||||||
|
|
||||||
|
* **역할:** **"ID 카드 (Selector)"**
|
||||||
|
* **공식:** CS에 들어있는 숫자는 **GDT(Global Descriptor Table)**라는 표의 **인덱스(순서)**입니다.
|
||||||
|
* **동작:**
|
||||||
|
1. `CS`에 `0x08`이 들어있다.
|
||||||
|
2. CPU는 메모리에 있는 GDT 테이블의 **1번째 항목(0x08)**을 찾아본다.
|
||||||
|
3. 그 항목에 적힌 **"Base Address(시작 주소)"**와 **"권한(Permission)"**을 읽어온다.
|
||||||
|
4. 그 주소에 `EIP`를 더해서 이동한다.
|
||||||
|
|
||||||
|
|
||||||
|
* **비유:** "CS값은 1번 회원님이야." \rightarrow 회원 명부를 뒤져서 그 사람 집 주소를 찾음.
|
||||||
|
|
||||||
|
#### C. 64비트 롱 모드 (최신 윈도우, 리눅스)
|
||||||
|
|
||||||
|
* **역할:** **"거의 무시됨 (Flat Model)"**
|
||||||
|
* **특징:** `FS`, `GS`를 제외한 `CS`, `DS`, `ES`, `SS`는 베이스 주소가 무조건 **0**으로 고정됩니다.
|
||||||
|
* **이유:** 64비트 주소 공간이 워낙 넓어서 세그먼트로 나누는 게 의미가 없어졌기 때문입니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. 요약: 개발자의 자세
|
||||||
|
|
||||||
|
1. **지금(BIOS 개발):** `mov ds, 0xF000`처럼 세그먼트를 **"위치 지정용"**으로 씁니다.
|
||||||
|
2. **Pintos(32비트) 넘어가면:** `mov ds, 0x10` 같은 코드를 보게 될 텐데, 이건 주소 16번지가 아니라 **"커널 데이터용 ID 카드(Selector)"**를 끼우는 행위입니다.
|
||||||
|
|
||||||
|
지금은 **"16비트 세상의 규칙"**만 확실히 익히시면 됩니다! 잘 따라오고 계십니다. 👍
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
; mybios_ssh.asm
|
||||||
|
[BITS 16]
|
||||||
|
[ORG 0]
|
||||||
|
|
||||||
|
bios_start:
|
||||||
|
; 1. 세그먼트 초기화
|
||||||
|
mov ax, 0xF000
|
||||||
|
mov ds, ax
|
||||||
|
mov es, ax
|
||||||
|
xor ax, ax
|
||||||
|
mov ss, ax
|
||||||
|
mov sp, 0x7C00
|
||||||
|
|
||||||
|
; 2. 시리얼 포트 초기화 (가장 중요!)
|
||||||
|
call init_serial
|
||||||
|
|
||||||
|
; 3. 부팅 메시지 출력
|
||||||
|
mov si, msg_boot
|
||||||
|
call print_serial
|
||||||
|
|
||||||
|
; 4. 메인 루프 (입력 대기)
|
||||||
|
mov si, msg_menu
|
||||||
|
call print_serial
|
||||||
|
|
||||||
|
main_loop:
|
||||||
|
; --- Heartbeat (살아있음을 표시) ---
|
||||||
|
; 입력이 없어도 멈춘 게 아니라는 걸 보여주기 위해 점(.)을 찍을 수도 있지만,
|
||||||
|
; 너무 지저분해지니 생략하고 입력만 기다립니다.
|
||||||
|
|
||||||
|
; [입력 확인] 0x3FD 포트의 0번 비트가 1이면 데이터가 온 것임
|
||||||
|
mov dx, 0x3FD
|
||||||
|
in al, dx
|
||||||
|
test al, 1
|
||||||
|
jz main_loop ; 데이터 없으면 계속 뺑뺑이(Loop)
|
||||||
|
|
||||||
|
; [데이터 읽기] 0x3F8 포트에서 읽음
|
||||||
|
mov dx, 0x3F8
|
||||||
|
in al, dx
|
||||||
|
|
||||||
|
; [내가 누른 키 화면에 보여주기 (Echo)]
|
||||||
|
; 이게 없으면 내가 뭘 눌렀는지 터미널에 안 보임!
|
||||||
|
out dx, al
|
||||||
|
|
||||||
|
; [키 판별]
|
||||||
|
cmp al, '1'
|
||||||
|
je action_shutdown
|
||||||
|
|
||||||
|
cmp al, '2'
|
||||||
|
je action_reboot
|
||||||
|
|
||||||
|
jmp main_loop
|
||||||
|
|
||||||
|
; ===========================================================
|
||||||
|
; [Actions]
|
||||||
|
; ===========================================================
|
||||||
|
|
||||||
|
action_shutdown:
|
||||||
|
mov si, msg_bye
|
||||||
|
call print_serial
|
||||||
|
|
||||||
|
; PIIX4 ACPI Shutdown (Power Off)
|
||||||
|
; 1. I/O Enable
|
||||||
|
mov dx, 0xCF8
|
||||||
|
mov eax, 0x80000B04
|
||||||
|
out dx, eax
|
||||||
|
mov dx, 0xCFC
|
||||||
|
mov eax, 0x0001
|
||||||
|
out dx, eax
|
||||||
|
|
||||||
|
; 2. PMBA -> 0xB000
|
||||||
|
mov dx, 0xCF8
|
||||||
|
mov eax, 0x80000B40
|
||||||
|
out dx, eax
|
||||||
|
mov dx, 0xCFC
|
||||||
|
mov eax, 0x0000B001
|
||||||
|
out dx, eax
|
||||||
|
|
||||||
|
; 3. ACPI Enable
|
||||||
|
mov dx, 0xCF8
|
||||||
|
mov eax, 0x80000B80
|
||||||
|
out dx, eax
|
||||||
|
mov dx, 0xCFC
|
||||||
|
mov eax, 0x00000001
|
||||||
|
out dx, eax
|
||||||
|
|
||||||
|
; 4. Shutdown Command
|
||||||
|
mov dx, 0xB004
|
||||||
|
mov ax, 0x2000
|
||||||
|
out dx, ax
|
||||||
|
|
||||||
|
; 안 꺼지면 대기
|
||||||
|
hlt
|
||||||
|
jmp $
|
||||||
|
|
||||||
|
action_reboot:
|
||||||
|
mov si, msg_reboot
|
||||||
|
call print_serial
|
||||||
|
|
||||||
|
; Reboot Command
|
||||||
|
mov dx, 0x64
|
||||||
|
mov al, 0xFE
|
||||||
|
out dx, al
|
||||||
|
hlt
|
||||||
|
jmp $
|
||||||
|
|
||||||
|
; ===========================================================
|
||||||
|
; [Drivers]
|
||||||
|
; ===========================================================
|
||||||
|
init_serial:
|
||||||
|
mov dx, 0x3FB
|
||||||
|
mov al, 0x80 ; DLAB ON
|
||||||
|
out dx, al
|
||||||
|
mov dx, 0x3F8
|
||||||
|
mov al, 0x0C ; 9600 baud
|
||||||
|
out dx, al
|
||||||
|
mov dx, 0x3F9
|
||||||
|
mov al, 0x00
|
||||||
|
out dx, al
|
||||||
|
mov dx, 0x3FB
|
||||||
|
mov al, 0x03 ; 8N1, DLAB OFF
|
||||||
|
out dx, al
|
||||||
|
ret
|
||||||
|
|
||||||
|
print_serial:
|
||||||
|
mov dx, 0x3F8
|
||||||
|
.loop:
|
||||||
|
lodsb
|
||||||
|
cmp al, 0
|
||||||
|
je .done
|
||||||
|
out dx, al
|
||||||
|
jmp .loop
|
||||||
|
.done:
|
||||||
|
ret
|
||||||
|
|
||||||
|
; ===========================================================
|
||||||
|
; [Data]
|
||||||
|
; ===========================================================
|
||||||
|
; 줄바꿈(0x0D, 0x0A)을 명확히 넣었습니다.
|
||||||
|
msg_boot db 0x0D, 0x0A, "===============================", 0x0D, 0x0A
|
||||||
|
db " SSH Serial BIOS v1.0 ", 0x0D, 0x0A
|
||||||
|
db "===============================", 0x0D, 0x0A, 0
|
||||||
|
|
||||||
|
msg_menu db 0x0D, 0x0A, "COMMANDS: [1] Shutdown [2] Reboot", 0x0D, 0x0A
|
||||||
|
db "Input > ", 0
|
||||||
|
|
||||||
|
msg_bye db 0x0D, 0x0A, ">>> System is HALTING...", 0x0D, 0x0A, 0
|
||||||
|
msg_reboot db 0x0D, 0x0A, ">>> REBOOTING...", 0x0D, 0x0A, 0
|
||||||
|
|
||||||
|
times 65520-($-$$) db 0
|
||||||
|
reset_vector:
|
||||||
|
jmp 0xF000:0x0000
|
||||||
|
times 16-($-reset_vector) db 0
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
; mybios_real.asm
|
||||||
|
[BITS 16]
|
||||||
|
[ORG 0]
|
||||||
|
|
||||||
|
bios_start:
|
||||||
|
; 1. 세그먼트 초기화
|
||||||
|
mov ax, 0xF000
|
||||||
|
mov ds, ax
|
||||||
|
mov es, ax
|
||||||
|
xor ax, ax
|
||||||
|
mov ss, ax
|
||||||
|
mov sp, 0x7C00
|
||||||
|
|
||||||
|
; ==========================================================
|
||||||
|
; [VGA BIOS (Option ROM) 실행하기]
|
||||||
|
; QEMU는 0xC0000 번지에 표준 VGA BIOS를 올려둡니다.
|
||||||
|
; 이걸 실행해야 화면이 켜지고 curses가 작동합니다.
|
||||||
|
; ==========================================================
|
||||||
|
|
||||||
|
; 2-1. VGA BIOS가 있는지 확인 (Signature Check)
|
||||||
|
; 0xC000:0000 = 0x55, 0xC000:0001 = 0xAA 여야 함
|
||||||
|
mov ax, 0xC000
|
||||||
|
mov es, ax
|
||||||
|
|
||||||
|
cmp byte [es:0], 0x55
|
||||||
|
jne no_vga
|
||||||
|
cmp byte [es:1], 0xAA
|
||||||
|
jne no_vga
|
||||||
|
|
||||||
|
; 2-2. VGA BIOS 실행 (Far Call)
|
||||||
|
; 보통 3번째 바이트부터 코드가 시작됩니다.
|
||||||
|
; PUSH CS, PUSH IP 방식으로 돌아올 주소를 저장하고 점프합니다.
|
||||||
|
push cs ; 돌아올 세그먼트 (0xF000)
|
||||||
|
push word .vga_ret ; 돌아올 오프셋 (Label 주소)
|
||||||
|
|
||||||
|
; 0xC000:0003 으로 점프!
|
||||||
|
push 0xC000 ; VGA Segment
|
||||||
|
push 0x0003 ; VGA Entry Point
|
||||||
|
retf ; (Far Return을 이용한 점프 기법)
|
||||||
|
|
||||||
|
.vga_ret:
|
||||||
|
; 여기까지 오면 VGA BIOS가 화면을 초기화하고 돌아온 상태입니다!
|
||||||
|
; 이제 다시 우리의 세그먼트 복구
|
||||||
|
mov ax, 0xF000
|
||||||
|
mov ds, ax
|
||||||
|
mov es, ax
|
||||||
|
|
||||||
|
; 3. 화면 파란색으로 칠하기 (이제 먹힘!)
|
||||||
|
mov ax, 0xB800
|
||||||
|
mov es, ax
|
||||||
|
|
||||||
|
xor di, di
|
||||||
|
mov cx, 2000
|
||||||
|
mov ax, 0x1F20 ; 파란바탕 + 공백
|
||||||
|
rep stosw
|
||||||
|
|
||||||
|
; 4. 메시지 출력
|
||||||
|
mov si, msg_title
|
||||||
|
mov di, 0
|
||||||
|
call print_vga
|
||||||
|
|
||||||
|
mov si, msg_menu
|
||||||
|
mov di, 160
|
||||||
|
call print_vga
|
||||||
|
|
||||||
|
; 5. 메인 루프 (키보드 0x60)
|
||||||
|
main_loop:
|
||||||
|
in al, 0x64
|
||||||
|
test al, 1
|
||||||
|
jz main_loop
|
||||||
|
|
||||||
|
in al, 0x60
|
||||||
|
|
||||||
|
cmp al, 0x02 ; '1'
|
||||||
|
je action_shutdown
|
||||||
|
cmp al, 0x03 ; '2'
|
||||||
|
je action_reboot
|
||||||
|
jmp main_loop
|
||||||
|
|
||||||
|
; --- 에러 처리: VGA BIOS가 없을 때 ---
|
||||||
|
no_vga:
|
||||||
|
; 시리얼로 에러 메시지라도 보내야 함 (여기선 생략하고 무한루프)
|
||||||
|
cli
|
||||||
|
hlt
|
||||||
|
jmp $
|
||||||
|
|
||||||
|
; ===========================================================
|
||||||
|
; [Actions] (이전과 동일)
|
||||||
|
; ===========================================================
|
||||||
|
action_shutdown:
|
||||||
|
mov si, msg_bye
|
||||||
|
mov di, 320
|
||||||
|
call print_vga
|
||||||
|
|
||||||
|
; ACPI PIIX4 Shutdown
|
||||||
|
mov dx, 0xCF8
|
||||||
|
mov eax, 0x80000B04
|
||||||
|
out dx, eax
|
||||||
|
mov dx, 0xCFC
|
||||||
|
mov eax, 0x0001
|
||||||
|
out dx, eax
|
||||||
|
mov dx, 0xCF8
|
||||||
|
mov eax, 0x80000B40
|
||||||
|
out dx, eax
|
||||||
|
mov dx, 0xCFC
|
||||||
|
mov eax, 0x0000B001
|
||||||
|
out dx, eax
|
||||||
|
mov dx, 0xCF8
|
||||||
|
mov eax, 0x80000B80
|
||||||
|
out dx, eax
|
||||||
|
mov dx, 0xCFC
|
||||||
|
mov eax, 0x00000001
|
||||||
|
out dx, eax
|
||||||
|
mov dx, 0xB004
|
||||||
|
mov ax, 0x2000
|
||||||
|
out dx, ax
|
||||||
|
hlt
|
||||||
|
jmp $
|
||||||
|
|
||||||
|
action_reboot:
|
||||||
|
mov si, msg_reboot
|
||||||
|
mov di, 320
|
||||||
|
call print_vga
|
||||||
|
mov dx, 0x64
|
||||||
|
mov al, 0xFE
|
||||||
|
out dx, al
|
||||||
|
hlt
|
||||||
|
jmp $
|
||||||
|
|
||||||
|
; ===========================================================
|
||||||
|
; [Drivers]
|
||||||
|
; ===========================================================
|
||||||
|
print_vga:
|
||||||
|
mov ah, 0x1F
|
||||||
|
.loop:
|
||||||
|
lodsb
|
||||||
|
cmp al, 0
|
||||||
|
je .done
|
||||||
|
stosw
|
||||||
|
jmp .loop
|
||||||
|
.done:
|
||||||
|
ret
|
||||||
|
|
||||||
|
; ===========================================================
|
||||||
|
; [Data]
|
||||||
|
; ===========================================================
|
||||||
|
msg_title db "=== REAL BIOS (VGA Initialized) ===", 0
|
||||||
|
msg_menu db "Press [1] to Shutdown, [2] to Reboot", 0
|
||||||
|
msg_bye db "System Halted.", 0
|
||||||
|
msg_reboot db "Rebooting...", 0
|
||||||
|
|
||||||
|
; --- BIOS 크기 맞추기 (64KB) ---
|
||||||
|
times 65520-($-$$) db 0
|
||||||
|
reset_vector:
|
||||||
|
jmp 0xF000:0x0000
|
||||||
|
times 16-($-reset_vector) db 0
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
; mybios_boot.asm
|
||||||
|
[BITS 16]
|
||||||
|
[ORG 0x7C00] ; BIOS가 우리를 0x7C00에 로딩해줍니다.
|
||||||
|
|
||||||
|
start:
|
||||||
|
; 1. 세그먼트 초기화
|
||||||
|
xor ax, ax ; AX = 0
|
||||||
|
mov ds, ax ; DS = 0
|
||||||
|
mov es, ax ; ES = 0
|
||||||
|
mov ss, ax ; SS = 0
|
||||||
|
mov sp, 0x7C00 ; 스택 설정
|
||||||
|
|
||||||
|
; 2. 화면 초기화 (블루 스크린)
|
||||||
|
; BIOS가 이미 VGA를 텍스트 모드로 켜둔 상태입니다!
|
||||||
|
; 우리는 메모리에 색칠만 하면 됩니다.
|
||||||
|
mov ax, 0xB800
|
||||||
|
mov es, ax ; ES = 비디오 메모리 세그먼트
|
||||||
|
|
||||||
|
xor di, di ; DI = 0
|
||||||
|
mov cx, 2000 ; 80x25 = 2000 글자
|
||||||
|
mov ax, 0x1F20 ; 파란바탕(1F) + 공백(20)
|
||||||
|
rep stosw ; 화면 지우기
|
||||||
|
|
||||||
|
; 3. 메시지 출력
|
||||||
|
mov si, msg_title
|
||||||
|
mov di, 0
|
||||||
|
call print_vga
|
||||||
|
|
||||||
|
mov si, msg_menu
|
||||||
|
mov di, 160
|
||||||
|
call print_vga
|
||||||
|
|
||||||
|
; 4. 키보드 입력 대기
|
||||||
|
main_loop:
|
||||||
|
in al, 0x64
|
||||||
|
test al, 1
|
||||||
|
jz main_loop
|
||||||
|
|
||||||
|
in al, 0x60 ; 키보드 스캔코드 읽기
|
||||||
|
|
||||||
|
cmp al, 0x02 ; 키 '1'
|
||||||
|
je action_shutdown
|
||||||
|
|
||||||
|
cmp al, 0x03 ; 키 '2'
|
||||||
|
je action_reboot
|
||||||
|
|
||||||
|
jmp main_loop
|
||||||
|
|
||||||
|
; ===========================================================
|
||||||
|
; [Actions]
|
||||||
|
; ===========================================================
|
||||||
|
|
||||||
|
action_shutdown:
|
||||||
|
mov si, msg_bye
|
||||||
|
mov di, 320
|
||||||
|
call print_vga
|
||||||
|
|
||||||
|
; ACPI Shutdown (PIIX4) - 부트로더 상태에서도 똑같이 동작합니다.
|
||||||
|
mov dx, 0xCF8
|
||||||
|
mov eax, 0x80000B04
|
||||||
|
out dx, eax
|
||||||
|
mov dx, 0xCFC
|
||||||
|
mov eax, 0x0001
|
||||||
|
out dx, eax
|
||||||
|
|
||||||
|
mov dx, 0xCF8
|
||||||
|
mov eax, 0x80000B40
|
||||||
|
out dx, eax
|
||||||
|
mov dx, 0xCFC
|
||||||
|
mov eax, 0x0000B001
|
||||||
|
out dx, eax
|
||||||
|
|
||||||
|
mov dx, 0xCF8
|
||||||
|
mov eax, 0x80000B80
|
||||||
|
out dx, eax
|
||||||
|
mov dx, 0xCFC
|
||||||
|
mov eax, 0x00000001
|
||||||
|
out dx, eax
|
||||||
|
|
||||||
|
mov dx, 0xB004
|
||||||
|
mov ax, 0x2000
|
||||||
|
out dx, ax
|
||||||
|
|
||||||
|
hlt
|
||||||
|
jmp $
|
||||||
|
|
||||||
|
action_reboot:
|
||||||
|
mov si, msg_reboot
|
||||||
|
mov di, 320
|
||||||
|
call print_vga
|
||||||
|
|
||||||
|
mov dx, 0x64
|
||||||
|
mov al, 0xFE
|
||||||
|
out dx, al
|
||||||
|
hlt
|
||||||
|
jmp $
|
||||||
|
|
||||||
|
; ===========================================================
|
||||||
|
; [Drivers]
|
||||||
|
; ===========================================================
|
||||||
|
|
||||||
|
print_vga:
|
||||||
|
mov ah, 0x1F ; 파란색 속성 고정
|
||||||
|
.loop:
|
||||||
|
lodsb
|
||||||
|
cmp al, 0
|
||||||
|
je .done
|
||||||
|
stosw
|
||||||
|
jmp .loop
|
||||||
|
.done:
|
||||||
|
ret
|
||||||
|
|
||||||
|
; ===========================================================
|
||||||
|
; [Data]
|
||||||
|
; ===========================================================
|
||||||
|
msg_title db "=== SSH Curses Bootloader (Blue Screen) ===", 0
|
||||||
|
msg_menu db "Press [1] to Shutdown, [2] to Reboot", 0
|
||||||
|
msg_bye db "System Halted.", 0
|
||||||
|
msg_reboot db "Rebooting...", 0
|
||||||
|
|
||||||
|
; --- 부트 섹터 마법 (512바이트 맞추기) ---
|
||||||
|
times 510-($-$$) db 0 ; 510바이트까지 0으로 채움
|
||||||
|
dw 0xAA55 ; 부트 서명 (Boot Signature)
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# read&write
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nasm -f bin mybios.asm -o mybios.bin
|
||||||
|
qemu-system-i386 -bios mybios.bin -nographic
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nasm -f bin myboot.asm -o myboot.bin
|
||||||
|
qemu-system-i386 -M pc -drive file=myboot.bin,format=raw,index=0,media=disk -display curses
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user