실행 가능한 예제
기술적으로 OS없이 실행되는 프로그램은 OS입니다. 이제 몇 가지 작은 hello world OS를 만들고 실행하는 방법을 살펴 보겠습니다.
아래 모든 예제 코드는 이 GitHub 리포지토리에 있습니다.
부트 섹터
x86에서 수행 할 수있는 가장 단순하고 가장 낮은 수준의 작업은 부팅 섹터 유형 인 MBR (Master Boot Sector)을 만든 다음 디스크에 설치하는 것입니다.
여기서 우리는 단일 printf
호출로 하나를 만듭니다 .
printf '\364%509s\125\252' > main.img
sudo apt-get install qemu-system-x86
qemu-system-x86_64 -hda main.img
결과:
Ubuntu 18.04, QEMU 2.11.1에서 테스트되었습니다.
main.img
다음을 포함합니다 :
\364
8 진수 == 0xf4
16 진수 : hlt
명령어 인코딩으로 CPU가 작동을 중지하도록 지시합니다.
따라서 우리의 프로그램은 아무것도하지 않을 것입니다 : 시작과 중지 만.
\x
POSIX에서 16 진수를 지정하지 않기 때문에 8 진수를 사용 합니다.
이 인코딩을 쉽게 얻을 수 있습니다 :
echo hlt > a.asm
nasm -f bin a.asm
hd a
그러나 0xf4
인코딩은 인텔 설명서에도 문서화되어 있습니다.
%509s
509 개의 공간을 생성합니다. 바이트 510까지 파일을 채워야합니다.
\125\252
octal == 0x55
다음에 0xaa
: 하드웨어에 필요한 매직 바이트. 바이트 511 및 512 여야합니다.
존재하지 않으면 하드웨어는이 디스크를 부팅 가능한 디스크로 취급하지 않습니다.
아무 것도하지 않아도 화면에 이미 몇 개의 문자가 인쇄되어 있습니다. 펌웨어로 인쇄되며 시스템을 식별하는 역할을합니다.
실제 하드웨어에서 실행
에뮬레이터는 재미 있지만 하드웨어는 정말 중요합니다.
그러나 이것은 위험하므로 실수로 디스크를 지울 수 있습니다. 중요한 데이터가 포함되지 않은 오래된 시스템에서만이 작업을 수행하십시오! 또는 Raspberry Pi와 같은 devboard는 아래 ARM 예를 참조하십시오.
일반적인 랩톱의 경우 다음과 같은 작업을 수행해야합니다.
이미지를 USB 스틱에 굽습니다 (데이터가 손상 될 수 있습니다).
sudo dd if=main.img of=/dev/sdX
컴퓨터에 USB를 연결
전원을 켜십시오
USB에서 부팅하도록 지시하십시오.
즉, 펌웨어가 하드 디스크보다 먼저 USB를 선택하도록합니다.
이것이 시스템의 기본 동작이 아닌 경우 전원을 켠 후 Enter 키, F12, ESC 또는 기타 이상한 키를 계속 누르면 USB에서 부팅하도록 선택할 수있는 부팅 메뉴가 나타납니다.
해당 메뉴에서 검색 순서를 구성하는 것이 종종 가능합니다.
예를 들어, 이전 Lenovo Thinkpad T430, UEFI BIOS 1.16에서 다음을 볼 수 있습니다.
안녕하세요 세계
최소한의 프로그램을 만들었으므로 이제 hello world로 넘어 갑시다.
분명한 질문은 IO를 수행하는 방법입니다. 몇 가지 옵션 :
- 펌웨어를 요청하십시오 (예 : BIOS 또는 UEFI)
- VGA : 쓰면 화면에 인쇄되는 특수 메모리 영역입니다. 보호 모드에서 사용할 수 있습니다.
- 드라이버를 작성하고 디스플레이 하드웨어와 직접 대화하십시오. 이것은 "적절한"방법으로보다 강력하지만 복잡합니다.
직렬 포트 . 이것은 호스트 터미널에서 문자를 보내고 검색하는 매우 간단한 표준화 된 프로토콜입니다.
소스 .
불행히도 대부분의 최신 랩톱에는 노출되지 않지만 개발 보드를 사용하는 일반적인 방법입니다 (아래 ARM 예 참조).
이러한 인터페이스는 예를 들어 Linux 커널을 디버깅하는 데 실제로 유용 하기 때문에 이것은 정말 부끄러운 일 입니다.
칩의 디버그 기능을 사용하십시오. ARM은 예를 들어 세미 호스팅 을 호출합니다 . 실제 하드웨어에서는 추가 하드웨어 및 소프트웨어 지원이 필요하지만 에뮬레이터에서는 무료로 편리한 옵션이 될 수 있습니다. 예 .
여기서는 x86에서 더 간단한 BIOS 예제를 수행 할 것입니다. 그러나 가장 강력한 방법은 아닙니다.
main.S
.code16
mov $msg, %si
mov $0x0e, %ah
loop:
lodsb
or %al, %al
jz halt
int $0x10
jmp loop
halt:
hlt
msg:
.asciz "hello world"
link.ld
SECTIONS
{
. = 0x7c00;
.text :
{
__start = .;
*(.text)
. = 0x1FE;
SHORT(0xAA55)
}
}
조립 및 연결 :
gcc -c -g -o main.o main.S
ld --oformat binary -o main.img -T linker.ld main.o
결과:
테스트 대상 : Lenovo Thinkpad T430, UEFI BIOS 1.16. Ubuntu 18.04 호스트에서 생성 된 디스크.
표준 사용자 랜드 어셈블리 지침 외에도 다음과 같은 이점이 있습니다.
.code16
: 16 비트 코드를 출력하도록 GAS에 지시
cli
: 소프트웨어 인터럽트를 비활성화합니다. 그 후 프로세서가 다시 실행되기 시작할 수 있습니다.hlt
int $0x10
: BIOS 호출을 수행합니다. 문자를 하나씩 인쇄합니다.
중요한 링크 플래그는 다음과 같습니다.
--oformat binary
: 원시 이진 어셈블리 코드를 출력합니다. 일반 사용자 실행 파일의 경우와 같이 ELF 파일 내에서 워프하지 마십시오.
조립 대신 C 사용
C는 어셈블리로 컴파일되기 때문에 표준 라이브러리없이 C를 사용하는 것은 매우 간단하므로 기본적으로 다음이 필요합니다.
- 올바른 장소에 메모리에 물건을 넣는 링커 스크립트
- GCC가 표준 라이브러리를 사용하지 않도록 지시하는 플래그
- 에 필요한 C 상태를 설정하는 작은 어셈블리 진입 점
main
, 특히
TODO : GitHub에서 x86 예제를 링크하십시오. 여기 내가 만든 ARM이 있습니다.
POSIX를 통해 많은 C 표준 라이브러리 기능 을 구현하는 Linux 커널이 없기 때문에 표준 라이브러리를 사용하려는 경우 상황이 더 재미있어집니다 .
리눅스와 같은 본격적인 OS를 사용하지 않고 몇 가지 가능성은 다음과 같습니다.
팔
ARM에서는 일반적인 아이디어가 동일합니다. 나는 업로드했다 :
Raspberry Pi의 경우 https://github.com/dwelch67/raspberrypi 는 오늘날 가장 인기있는 자습서처럼 보입니다.
x86과의 차이점은 다음과 같습니다.
IO 직접 마법 주소로 작성하여 수행됩니다, 어떤이없는 in
및 out
지침을.
이것을 메모리 매핑 된 IO 라고 합니다 .
Raspberry Pi와 같은 실제 하드웨어의 경우 디스크 이미지에 펌웨어 (BIOS)를 직접 추가 할 수 있습니다.
펌웨어 업데이트가 더 투명 해지기 때문에 좋은 일입니다.
펌웨어
사실, 부트 섹터는 시스템의 CPU에서 실행되는 최초의 소프트웨어가 아닙니다.
실제로 가장 먼저 실행 되는 것은 소위 펌웨어 이며 소프트웨어입니다.
- 하드웨어 제조업체가 만든
- 일반적으로 비공개 소스이지만 아마도 C 기반
- 읽기 전용 메모리에 저장되므로 공급 업체의 동의 없이는 수정하기가 어렵거나 불가능합니다.
잘 알려진 펌웨어는 다음과 같습니다.
- BIOS : 기존의 모든 x86 펌웨어 SeaBIOS는 QEMU에서 사용하는 기본 오픈 소스 구현입니다.
- UEFI : BIOS의 후속 버전으로,보다 표준화되었지만, 성능이 뛰어나고 엄청나게 부 풀었습니다.
- Coreboot : 고귀한 크로스 아치 오픈 소스 시도
펌웨어는 다음과 같은 작업을 수행합니다.
부팅 가능한 것을 찾을 때까지 각 하드 디스크, USB, 네트워크 등을 반복합니다.
QEMU를 실행 하면 하드웨어에 연결된 하드 디스크 -hda
라고합니다.main.img
hda
시도 할 첫 번째이며 사용됩니다.
처음 512 바이트를 RAM 메모리 주소에로드하고 0x7c00
CPU의 RIP를 거기에 놓고 실행 시키십시오.
부팅 메뉴 또는 BIOS 인쇄 호출과 같은 것을 디스플레이에 표시
펌웨어는 대부분의 OS가 의존하는 OS와 유사한 기능을 제공합니다. 예를 들어 Python 하위 세트가 BIOS / UEFI에서 실행되도록 포팅되었습니다 : https://www.youtube.com/watch?v=bYQ_lq5dcvM
펌웨어는 OS와 구별 할 수 없으며 펌웨어가 유일하게 할 수있는 유일한 "진정한"베어 메탈 프로그래밍이라고 주장 할 수 있습니다.
이 CoreOS 개발자는 다음과 같이 말합니다 .
어려운 부분
PC 전원을 켤 때 칩셋 (노스 브리지, 사우스 브리지 및 SuperIO)을 구성하는 칩이 아직 제대로 초기화되지 않았습니다. BIOS ROM이 가능한 한 CPU에서 제거되었지만 CPU가 액세스 할 수 있어야합니다. 그렇지 않으면 CPU가 실행할 명령이 없기 때문입니다. 그렇다고 BIOS ROM이 완전히 매핑되지 않았다는 의미는 아닙니다. 그러나 부팅 프로세스를 진행하기에 충분한 매핑입니다. 다른 장치는 잊어 버리십시오.
QEMU에서 Coreboot를 실행하면 더 높은 계층의 Coreboot와 페이로드를 실험 할 수 있지만 QEMU는 낮은 수준의 시작 코드를 실험 할 기회가 거의 없습니다. 우선, RAM은 처음부터 바로 작동합니다.
BIOS 초기 상태
하드웨어의 많은 것들과 마찬가지로, 표준화는 약하고, 의존 하지 말아야 할 것 중 하나는 BIOS 후에 코드가 실행되기 시작할 때 레지스터의 초기 상태입니다.
따라서 자신에게 호의를 베풀고 다음과 같은 초기화 코드를 사용 하십시오 . https://stackoverflow.com/a/32509555/895245
레지스터 는 중요한 부작용을 좋아 %ds
하고 %es
있으므로 명시 적으로 사용하지 않더라도 제로를 제거해야합니다.
일부 에뮬레이터는 실제 하드웨어보다 좋고 초기 상태가 좋습니다. 그런 다음 실제 하드웨어에서 실행하면 모든 것이 깨집니다.
GNU GRUB 멀티 부트
부팅 섹터는 간단하지만 매우 편리하지는 않습니다.
- 디스크 당 하나의 OS 만 가질 수 있습니다
- 로드 코드는 실제로 작고 512 바이트에 맞아야합니다. 이것은 int 0x13 BIOS 호출 로 해결할 수 있습니다 .
- 보호 모드로 이동하는 것과 같이 많은 시작을 직접해야합니다
이런 이유로 GNU GRUB 은 multiboot라는보다 편리한 파일 형식을 만들었습니다.
최소 작업 예 : https://github.com/cirosantilli/x86-bare-metal-examples/tree/d217b180be4220a0b4a453f31275d38e697a99e0/multiboot/hello-world
또한 GitHub 예제 리포지토리 에서 사용하여 USB를 백만 번 불 태우지 않고도 실제 하드웨어에서 모든 예제를 쉽게 실행할 수 있습니다. QEMU에서는 다음과 같습니다.
OS를 멀티 부팅 파일로 준비하면 GRUB은 일반 파일 시스템에서 OS를 찾을 수 있습니다.
이것이 대부분의 배포판에서하는 일이며 OS 이미지는 아래에 /boot
있습니다.
멀티 부트 파일은 기본적으로 특수 헤더가있는 ELF 파일입니다. GRUB은 https://www.gnu.org/software/grub/manual/multiboot/multiboot.html 에서 지정합니다.
을 사용하여 멀티 부팅 파일을 부팅 가능한 디스크로 전환 할 수 있습니다 grub-mkrescue
.
엘 토리 토
CD에 구울 수있는 형식 : https://en.wikipedia.org/wiki/El_Torito_%28CD-ROM_standard%29
ISO 또는 USB에서 작동하는 하이브리드 이미지를 생성 할 수도 있습니다. 이것은 grub-mkrescue
( 예 )를 make isoimage
사용하여 수행 할 수 있으며 Linux 커널을 사용하여 수행 할 수도 있습니다 isohybrid
.
자원