Files
kernel-study/qemu/second/readme.md
T
2025-12-30 08:33:13 +00:00

10 KiB

BIOS

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

[ 우리가 만든 파일 ]                 [ 실제 메모리 주소 (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 코드 한 조각이 완벽하게 해석되실 겁니다.

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. CS0x08이 들어있다.
  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비트 세상의 규칙"**만 확실히 익히시면 됩니다! 잘 따라오고 계십니다. 👍