206 lines
10 KiB
Markdown
206 lines
10 KiB
Markdown
# 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비트 세상의 규칙"**만 확실히 익히시면 됩니다! 잘 따라오고 계십니다. 👍 |