4.1 부팅과 부트로더
- 부트로더는 OS의 나머지 코드를 메모리에 복사해 실행시킨다. 부트로더는 대부분 512바이트의 코드다.
- 부팅은 PC가 켜지고 OS가 실행되기 전까지 수행되는 일련의 작업 과정이다. 프로세서 초기화, 메모리와 외부 디바이스 검사 및 초기화, 부트로더를 메모리에 복사하고 OS를 시작하는 과정등이 포함된다.
- 부팅 과정에서 하드웨어 관련 작업은 BIOS가 담당하고, 여기서 하는 각종 테스트나 초기화를 POST라고 한다.
- BIOS는 메인보드에 포함된 펌웨어의 일종으로, 보드에 롬이나 플래시 메모리 형태로 존재하고, 전원이 켜지면 프로세서가 가장 먼저 실행하는 코드다.
- 여기서 가장 중요한 작업은 부트로더 이미지를 메모리로 복사하는 단계다. 이 단계는 우리가 BIOS에서 처음으로 제어를 넘겨받는 부분이다.
- 부트로더는 저장 매체의 가장 앞 부분(MBR. Master Boot Record)에 존재한다. (섹터 하나는 512바이트로 구성된다. 즉, 부트로더는 맨 처음 섹터에 존재해야한다.) BIOS는 POST 작업 완료 후 여러 장치를 검사하여 앞 부분에 부트로더가 있는지 확인한다. 존재한다면 해당 코드를 0x7C00 어드레스에 복사한 후 프로세서가 0x7C00 어드레스부터 실행하도록 한다.
- BIOS는 첫번째 섹터가 부트로더란 것을 어떻게 알까? -> 섹터의 가장 마지막 2바이트가 0x55 (01010101), 0xAA (10101010)면 부트로더다.
내가 궁금해서 직접 찾은 내용들
1. 왜 부트로더의 코드를 메모리의 0x7C00 어드레스로 이동할까?
OS를 메모리의 0x7C00 주소에 올리는 것은 x86 아키텍처에서 부트로더가 OS를 실행하기 위해 사용하는 일반적인 관행입니다. 이 관행은 초기 부트로더에서 사용되는 부트 섹터의 주소인 0x7C00을 기준으로 한 것입니다.
부트로더는 컴퓨터가 부팅될 때 가장 먼저 실행되는 프로그램입니다. 부트로더는 보통 부트 섹터(Boot Sector) 또는 MBR(Master Boot Record)에 위치하며, 부트로더는 OS를 로드하고 실행하는 역할을 담당합니다.
부트로더는 컴퓨터의 BIOS(Basic Input/Output System) 또는 UEFI(Unified Extensible Firmware Interface)에 의해 로드되며, 일반적으로 512바이트 크기의 부트 섹터를 차지합니다. 이때 부트 섹터는 컴퓨터의 부팅 가능한 디스크의 첫 번째 섹터에 위치하며, BIOS가 해당 섹터를 로드하여 실행합니다.
0x7C00 주소는 부트로더가 부트 섹터의 시작 주소를 가리키는 기본적인 주소입니다. 이 주소는 전통적으로 x86 아키텍처에서 사용되어 왔으며, 부트로더가 OS를 메모리에 로드하기 위한 시작점으로 사용됩니다.
부트로더는 0x7C00 주소에 위치한 코드를 실행하여 OS 이미지를 로드하고, 초기화 작업을 수행한 후에는 OS의 다른 주소로 제어를 이동시킵니다. 이렇게 0x7C00 주소를 부트로더의 시작 주소로 사용함으로써, 부트로더와 OS 간의 인터페이스를 단순화하고 코드의 이동 및 실행을 쉽게 만들 수 있습니다.
하지만 주소 0x7C00은 다른 목적으로 예약되어 있는 주소가 아니며, 특정 상황이나 시스템에 따라 다른 주소로 OS를 로드하는 것도 가능합니다. 따라서 부트로더와 OS 간의 인터페이스와 호환성을 유지하기 위해 일반적으로 0x7C00 주소를 사용합니다.
2. 왜 마지막 2바이트로 0x55와 0xAA를 사용할까?
0x55와 0xAA가 마지막 비트 2개로 정해진 이유는 부트로더와 BIOS(또는 UEFI) 사이의 호환성을 유지하기 위한 규약이었습니다.
부트로더가 부트 섹터를 로드할 때, BIOS(또는 UEFI)는 데이터의 유효성을 확인하기 위해 마커 패턴을 사용합니다. 0x55와 0xAA는 이러한 마커 패턴 중 하나로 선택되었으며, 여러 가지 요인으로 인해 이 값들이 선택되었습니다.
1. 신호 특성: 0x55와 0xAA는 이진 표현에서 번갈아가며 1과 0이 반복되는 패턴입니다. 이러한 패턴은 신호 특성 상에서 잘 구분되고, 신호의 노이즈에도 잘 대응할 수 있습니다.
2. 에러 감지: 0x55와 0xAA는 패턴이 반복되면서 서로 보완하는 특성을 가지고 있습니다. 이는 데이터의 에러나 손실을 감지하는 데 도움을 줄 수 있습니다. 예를 들어, 데이터의 중간에 오류가 발생하여 0x55만 제대로 읽혔을 경우, 0xAA가 기대값이므로 오류가 있음을 인식할 수 있습니다.
3. 이전 시스템의 호환성: 초기 x86 시스템에서는 0x55와 0xAA를 마커로 사용하는 규약이 형성되었습니다. 이 규약은 초기 BIOS의 설계 및 구현에서 선택되었으며, 그 이후의 시스템과의 호환성을 유지하기 위해 계속 사용되어 왔습니다.
이러한 이유로 0x55와 0xAA가 마지막 비트 2개로 정해져 부트로더의 유효성을 검사하는 마커 패턴이 되었습니다. 이를 통해 BIOS(또는 UEFI)는 부트로더가 올바르게 로드되었는지 확인하고, 부팅 프로세스를 계속 진행하거나 오류를 감지하여 중단할 수 있습니다.
4.2 부트로더 제작을 위한 준비
이 부분은 무리 없이 책에 있는 내용 그대로 진행했으며, 잘 동작 했다.
1. 폴더 구조 세팅함
2. makefile 만듦
4.3 부트로더 제작과 테스트
- 부트 로더를 메모리에 정상적으로 복사하기 위한 가장 중요한 방법은 부트 섹터 512바이트에서 마지막 2바이트를 각각 0x55, 0xAA로 맞추는 것이다.
- BootLoader.asm 파일을 생성하고 간단한 어셈블리어로 512바이트를 채우고 테스트한다.
- qemu 명령어는 수정해서 사용했음. 더 이상 `localtime` 이라는 옵션이 없나봄. -> qemu-system-x86_64 -L . -m 64 -fda disk.img -rtc base=localtime -M pc
화면 버퍼와 화면 제어
- 문자를 출력하려면 현재 동작중인 화면 모드와 관련된 비디오 메모리의 어드레스를 알아야 함
- 가로 80, 세로 25문자가 들어갈 수 있음. 비디오 메모리 어드레스는 0xB8000에서 시작.
- 1문자는 문자값 1바이트 + 속성값 1바이트로 구성되며 총 메모리 크기는 80x25x2 = 4000 바이트임.
- 속성값 1바이트의 하위 4비트는 전경색 + 상위 4비트는 배경색으로 구분됨.
세그먼트 레지스터 초기화
4장에서 제일 이해하기 어려웠던 내용. 일단 목표는 환영 메시지를 출력하는 것이다.
- 세그먼트 레지스터를 초기화하는 이유는 BIOS가 부트 로더를 실행했을 때 세그먼트 레지스터에는 BIOS가 사용하던 값이 들어 있기 떄문. 엉뚱한 어드레스에 접근할 수 있으므로 미리 초기화하고 사용한다.
- 0x07C0으로 초기화 해준다. 이유는 BIOS가 부트 로더를 디스크에서 읽어 메모리에 복사하는 위치가 0x7C00이기 때문이다. (리얼모드에서는 세그먼트 레지스터의 값에 16을 곱한 값을 세그먼트의 기준 주소로 사용하기 때문이다. 0x07C0 * 16 = 0x7C00)
- CS, DS 레지스터를 0x07C0으로 초기화 하고, ES를 화면 출력에 관련된 세그먼트로 사용하려고 0xB800으로 설정한다.
- 나머지 부분은 화면 버퍼와 화면 제어에서 잠깐 나온 문자를 나타내는 코드를 어셈블리어로 작성하여 나타내는 것이 끝이다. 그럼 아래와 같은 화면이 나타난다.
내가 궁금해서 직접 찾은 내용들
1. 왜 비디오 메모리 어드레스는 0xB8000에서 시작할까?
리얼 모드(Real Mode)는 x86 아키텍처의 초기 실행 모드로, 주로 MS-DOS 등의 오래된 운영 체제에서 사용되었습니다. 리얼 모드에서는 메모리 주소에 직접 접근하고, 세그먼트와 오프셋을 이용하여 메모리를 참조합니다.
0xB8000은 리얼 모드에서 텍스트 모드 비디오 버퍼의 시작 주소입니다. 텍스트 모드에서는 문자 기반의 화면 표시를 위해 사용되는 메모리 영역이 있습니다. 이 메모리 영역은 텍스트 모드에서 문자, 색상 정보 등을 저장하는 용도로 사용됩니다.
0xB8000 주소는 CGA(Color Graphics Adapter)와 호환되는 그래픽 카드가 설치된 시스템에서 사용되었습니다. CGA 그래픽 카드는 80x25 문자 텍스트 모드를 지원하며, 이를 위한 비디오 버퍼의 시작 주소가 0xB8000으로 정의되었습니다.
80x25 문자 텍스트 모드에서 각 문자는 문자 코드와 문자 색상 정보로 구성됩니다. 이러한 정보는 2바이트로 표현되고, 0xB8000부터 시작하여 일정한 메모리 공간에 저장됩니다. 따라서 해당 주소를 통해 텍스트 모드 비디오 버퍼에 직접 접근하여 문자 표시와 관련된 작업을 수행할 수 있습니다.
리얼 모드에서의 비디오 어드레스는 특정 하드웨어나 그래픽 모드와의 호환성, 오래된 시스템의 제약 등에 의해 정해진 것이며, 텍스트 모드에서의 0xB8000 주소는 그러한 관례적인 선택으로 알려져 있습니다.
0xB8000 주소를 사용한 CGA(Color Graphics Adapter)와 호환되는 최초의 컴퓨터는 IBM Personal Computer (PC)입니다. IBM PC는 1981년에 IBM에서 발표한 개인용 컴퓨터로서, CGA 그래픽 카드를 사용하는 텍스트 모드와 그래픽 모드를 지원했습니다.
CGA는 IBM PC 시스템에서 사용되는 그래픽 표준으로 개발되었으며, 80x25 문자 텍스트 모드와 320x200 그래픽 모드를 지원했습니다. 이러한 그래픽 모드에서는 4개의 색상과 텍스트 모드에서와 같이 문자를 표시할 수 있었습니다.
IBM PC는 x86 아키텍처를 기반으로 한 최초의 개인용 컴퓨터로서, 많은 영향력을 가지고 있습니다. CGA와 함께 텍스트 모드 비디오 버퍼의 시작 주소로 0xB8000을 사용하였고, 이는 다른 호환성을 가진 컴퓨터 시스템에서도 널리 사용되었습니다. 따라서 0xB8000 주소는 CGA 호환 그래픽 시스템에서 문자 표시와 관련된 프로그래밍 작업을 수행하는 데 사용되는 주소입니다.
2. 세그먼트 레지스터에 16비트 리터럴 값을 직접 지정할 수 없는 이유
x86 아키텍처에서 세그먼트 레지스터에 즉시 값을 로드하는 명령어가 제공되지 않는 이유는 초기의 x86 아키텍처 설계 시 고려된 다양한 요인들 때문입니다. 몇 가지 이유는 다음과 같습니다:
1. 역사적인 이유: 초기의 x86 아키텍처는 16비트 아키텍처이며, 초기 개발 단계에서 세그먼트 레지스터를 16비트로 설계하였습니다. 이로 인해 즉시 값을 로드하는 명령어를 추가하기보다는 중간에 레지스터 간 데이터 이동을 통해 값을 전달하는 방식이 채택되었습니다.
2. 명령어 형식의 간소화: x86 아키텍처의 명령어 형식은 다양한 종류의 명령어를 지원하기 위해 복잡한 구조를 가지고 있습니다. 명령어 형식을 간소화하고 명령어 집합을 단순화하기 위해, 즉시 값을 로드하는 명령어를 추가하는 대신 레지스터 간 데이터 이동 명령어를 활용하였습니다.
3. 하드웨어 제약: 초기의 x86 아키텍처에서는 제약된 하드웨어 리소스를 고려해야 했습니다. 세그먼트 레지스터에 즉시 값을 로드하는 명령어를 추가하기 위해서는 추가적인 하드웨어 회로나 복잡한 인스트럭션 세트가 필요했을 것입니다. 이는 제약된 리소스와 단순한 설계를 유지하기 위해 피연산자 레지스터를 사용하여 값을 전달하는 방식이 선택된 이유 중 하나입니다.
이러한 이유들로 인해 x86 아키텍처에서는 세그먼트 레지스터에 값을 로드할 때 중간에 다른 레지스터를 사용하여 값을 전달하는 방식이 채택되었습니다. 이러한 설계 결정은 초기의 x86 아키텍처에서부터 계속 사용되어 왔고, 이후의 호환성과 역사적인 이유로 인해 현재의 x86 아키텍처에서도 계속 유지되고 있습니다.
3. CS 세그먼트에 데이터 이동 명령 (ex - mov cs ax)이 불가능한 이유
CS 레지스터는 코드 세그먼트 레지스터로서, 현재 실행 중인 코드의 세그먼트(base) 주소를 저장하는 역할을 합니다. CS 레지스터는 읽기 전용이며, 명령어 실행 도중에 직접 값을 변경할 수 없습니다. 이는 x86 아키텍처의 제약 사항 중 하나입니다.
CS 레지스터는 코드의 실행 위치와 주소 계산에 중요한 역할을 합니다. CS 레지스터의 값은 코드 세그먼트(base) 주소를 나타내며, 코드 세그먼트에 대한 접근을 관리합니다. CPU는 CS 레지스터의 값을 사용하여 명령어의 실제 주소를 계산하고, 해당 주소에서 명령어를 가져와 실행합니다.
CS 레지스터의 값은 세그먼트 선택자(Segment Selector)를 통해 변경할 수 있습니다. 세그먼트 선택자는 코드 세그먼트를 식별하고, 해당 세그먼트의 기준(base) 주소를 CS 레지스터에 로드하는 역할을 합니다. 이를 위해 x86 아키텍처는 세그먼트 디스크립터(Segment Descriptor)와 GDT(Global Descriptor Table)를 사용하여 세그먼트 선택자를 해석하고, CS 레지스터에 올바른 값으로 설정합니다.
따라서 CS 레지스터는 읽기 전용이며, 직접 값을 변경할 수 없습니다. 코드의 실행 위치를 변경하기 위해서는 세그먼트 선택자를 통해 CS 레지스터에 올바른 값으로 설정해야 합니다. 이는 보호 모드(Protected Mode)에서 주로 사용되는 기능으로, 보호 모드에서는 CS 레지스터를 프로그램의 코드 세그먼트 주소로 변경하여 코드 실행 위치를 조작할 수 있습니다.
참고:
https://product.kyobobook.co.kr/detail/S000001223755
저자 깃허브:
https://github.com/kkamagui/mint64os-examples
'운영체제 정리 > 64비트 OS 만들기 실습' 카테고리의 다른 글
[64비트 멀티코어 OS 원리와 구조] 7장 - C언어로 코드를 작성하자 (0) | 2023.12.07 |
---|---|
[64비트 멀티코어 OS 원리와 구조] 6장 - 32비트 보호모드로 전환하자 (0) | 2023.07.29 |
[64비트 멀티코어 OS 원리와 구조] 5장 - 플로피 디스크에서 OS 이미지를 로딩하자 (0) | 2023.07.23 |
[64비트 멀티코어 OS 원리와 구조] 2장 - OS 개발 환경을 구축하자 (0) | 2022.12.30 |
[64비트 멀티코어 OS 원리와 구조] 1장 - OS 개발을 위한 힘찬 첫걸음 (0) | 2022.10.10 |