Przykłady możliwe do uruchomienia
Z technicznego punktu widzenia program działający bez systemu operacyjnego to system operacyjny. Zobaczmy więc, jak stworzyć i uruchomić kilka maleńkich systemów operacyjnych witaj na świecie.
Kod wszystkich poniższych przykładów znajduje się w tym repozytorium GitHub .
Sektor rozruchowy
Na x86 najprostszym i najniższym poziomem, co możesz zrobić, jest utworzenie głównego sektora rozruchowego (MBR) , który jest rodzajem sektora rozruchowego , a następnie zainstalowanie go na dysku.
Tutaj tworzymy jedną za pomocą jednego printf
połączenia:
printf '\364%509s\125\252' > main.img
sudo apt-get install qemu-system-x86
qemu-system-x86_64 -hda main.img
Wynik:
Testowane na Ubuntu 18.04, QEMU 2.11.1.
main.img
zawiera następujące elementy:
\364
in octal == 0xf4
in hex: kodowanie hlt
instrukcji, która mówi CPU, aby przestał działać.
Dlatego nasz program nic nie zrobi: zacznij i zatrzymaj.
Używamy ósemki, ponieważ \x
liczby szesnastkowe nie są określone przez POSIX.
Możemy łatwo uzyskać to kodowanie za pomocą:
echo hlt > a.asm
nasm -f bin a.asm
hd a
ale 0xf4
kodowanie jest również udokumentowane w podręczniku Intela.
%509s
wyprodukuj 509 miejsc. Konieczne do wypełnienia pliku do bajtu 510.
\125\252
in octal ==, 0x55
po którym następuje 0xaa
: magiczne bajty wymagane przez sprzęt. Muszą to być bajty 511 i 512.
Jeśli nie jest obecny, sprzęt nie będzie traktował tego jako dysku rozruchowego.
Pamiętaj, że nawet bez robienia kilku znaków jest już wydrukowanych na ekranie. Są one drukowane przez oprogramowanie układowe i służą do identyfikacji systemu.
Uruchom na prawdziwym sprzęcie
Emulatory są fajne, ale sprzęt to prawdziwa okazja.
Zauważ jednak, że jest to niebezpieczne i możesz pomyłkowo wyczyścić dysk: rób to tylko na starych komputerach, które nie zawierają krytycznych danych! Lub jeszcze lepiej, devboardy takie jak Raspberry Pi, patrz przykład ARM poniżej.
W przypadku typowego laptopa musisz zrobić coś takiego:
Wypal obraz na pamięci USB (zniszczy twoje dane!):
sudo dd if=main.img of=/dev/sdX
podłącz USB do komputera
włącz to
każ mu uruchomić z USB.
Oznacza to, że oprogramowanie układowe wybiera USB przed dyskiem twardym.
Jeśli nie jest to domyślne zachowanie twojego komputera, po włączeniu naciskaj Enter, F12, ESC lub inne takie dziwne klawisze, aż pojawi się menu rozruchu, w którym możesz wybrać rozruch z USB.
Często w tych menu można skonfigurować kolejność wyszukiwania.
Na przykład na moim starym Lenovo Thinkpad T430, UEFI BIOS 1.16 widzę:
Witaj świecie
Teraz, gdy stworzyliśmy minimalny program, przejdźmy do cześć świata.
Oczywiste pytanie brzmi: jak zrobić IO? Kilka opcji:
- poproś oprogramowanie układowe, np. BIOS lub UEFI, aby zrobiło to dla nas
- VGA: specjalny region pamięci, który zostanie wydrukowany na ekranie, jeśli zostanie zapisany. Może być używany w trybie chronionym.
- napisz sterownik i porozmawiaj bezpośrednio ze sprzętem wyświetlającym. Jest to „właściwy” sposób na zrobienie tego: mocniejszy, ale bardziej złożony.
port szeregowy . Jest to bardzo prosty znormalizowany protokół, który wysyła i pobiera znaki z terminala hosta.
Źródło .
Niestety nie jest narażony na większość współczesnych laptopów, ale jest powszechną drogą do tworzenia płyt deweloperskich, patrz przykłady ARM poniżej.
To naprawdę szkoda, ponieważ takie interfejsy są naprawdę przydatne na przykład do debugowania jądra Linuksa .
użyj funkcji debugowania układów. ARM na przykład nazywa ich semihosting . Na prawdziwym sprzęcie wymaga dodatkowej obsługi sprzętu i oprogramowania, ale na emulatorach może być bezpłatną wygodną opcją. Przykład .
Tutaj zrobimy przykład BIOS-u, ponieważ jest prostszy na x86. Pamiętaj jednak, że nie jest to najbardziej niezawodna metoda.
sieć elektryczna
.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)
}
}
Złóż i połącz z:
gcc -c -g -o main.o main.S
ld --oformat binary -o main.img -T linker.ld main.o
Wynik:
Testowane na: Lenovo Thinkpad T430, UEFI BIOS 1.16. Dysk wygenerowany na hoście Ubuntu 18.04.
Oprócz standardowych instrukcji montażu dla użytkownika, mamy:
.code16
: informuje GAS, aby wyprowadził 16-bitowy kod
cli
: wyłącz przerwania programowe. Mogą one spowodować, że procesor zacznie ponownie działać pohlt
int $0x10
: wykonuje połączenie BIOS. To właśnie drukuje znaki jeden po drugim.
Ważne flagi linków to:
--oformat binary
: wypisuje nieprzetworzony kod binarnego zestawu, nie wypaczaj go w pliku ELF, jak ma to miejsce w przypadku zwykłych plików wykonywalnych dla użytkownika
Użyj C zamiast montażu
Ponieważ C kompiluje się w asemblerze, używanie C bez standardowej biblioteki jest dość proste, w zasadzie potrzebujesz:
- skrypt linkera, aby umieścić rzeczy w pamięci we właściwym miejscu
- flagi, które mówią GCC, aby nie korzystał ze standardowej biblioteki
- mały punkt wejścia zespołu, który ustawia wymagany stan C
main
, w szczególności:
DO ZROBIENIA: link, więc przykład x86 na GitHub. Oto ARM, który stworzyłem .
Sprawa staje się jeszcze przyjemniejsza, jeśli chcesz korzystać ze standardowej biblioteki, ponieważ nie mamy jądra Linux, które implementuje większość standardowych funkcji biblioteki C za pośrednictwem POSIX .
Kilka możliwości, bez przechodzenia na w pełni funkcjonalny system operacyjny, taki jak Linux, to:
RAMIĘ
W ARM ogólne pomysły są takie same. Przesłałem:
W przypadku Raspberry Pi https://github.com/dwelch67/raspberrypi wygląda jak najpopularniejszy dostępny obecnie samouczek.
Niektóre różnice w stosunku do x86 obejmują:
IO odbywa się poprzez bezpośrednie pisanie na magiczne adresy, nie ma instrukcji in
i out
instrukcji.
Nazywa się to We / Wy mapowanym na pamięć .
w przypadku niektórych prawdziwych urządzeń, takich jak Raspberry Pi, możesz samodzielnie dodać oprogramowanie układowe (BIOS) do obrazu dysku.
To dobrze, ponieważ sprawia, że aktualizacja oprogramowania układowego jest bardziej przejrzysta.
Oprogramowanie układowe
W rzeczywistości sektor rozruchowy nie jest pierwszym oprogramowaniem działającym na procesorze systemu.
Najpierw działa tak zwane oprogramowanie układowe , które jest oprogramowaniem:
- wykonane przez producentów sprzętu
- zwykle zamknięte źródło, ale prawdopodobnie oparte na C.
- przechowywane w pamięci tylko do odczytu, a zatem trudniejsze / niemożliwe do modyfikacji bez zgody dostawcy.
Dobrze znane oprogramowanie wewnętrzne obejmuje:
- BIOS : stare, wszechstronne oprogramowanie x86. SeaBIOS jest domyślną implementacją open source używaną przez QEMU.
- UEFI : następca systemu BIOS, lepiej ustandaryzowany, ale bardziej wydajny i niesamowicie rozdęty.
- Coreboot : szlachetna próba otwartego oprogramowania typu cross arch
Oprogramowanie wewnętrzne działa na przykład:
zapętlaj każdy dysk twardy, USB, sieć itp., aż znajdziesz coś rozruchowego.
Kiedy uruchamiamy QEMU, -hda
mówi, że main.img
jest to dysk twardy podłączony do sprzętu i
hda
jest pierwszą próbą i jest używana.
załaduj pierwsze 512 bajtów na adres pamięci RAM 0x7c00
, umieść tam RIP procesora i pozwól mu działać
pokaż na ekranie takie rzeczy jak menu startowe lub wywołania drukowania BIOS-u
Oprogramowanie układowe oferuje funkcje podobne do systemu operacyjnego, od których zależy większość systemów operacyjnych. Np. Podzestaw Python został przeniesiony do działania w systemie BIOS / UEFI: https://www.youtube.com/watch?v=bYQ_lq5dcvM
Można argumentować, że oprogramowanie układowe jest nierozróżnialne od systemów operacyjnych i że oprogramowanie układowe jest jedynym „prawdziwym” programowaniem bez systemu operacyjnego.
Jak to ujął to deweloper CoreOS :
Trudna część
Po włączeniu komputera układy scalone (mostek północny, mostek południowy i SuperIO) nie są jeszcze poprawnie zainicjowane. Mimo że ROM ROM BIOS jest tak daleko od procesora, jak to możliwe, jest on dostępny dla procesora, ponieważ tak musi być, w przeciwnym razie procesor nie miałby instrukcji do wykonania. Nie oznacza to, że pamięć ROM systemu BIOS jest całkowicie zmapowana, zwykle nie. Ale tylko tyle jest zmapowane, aby rozpocząć proces rozruchu. Wszelkie inne urządzenia, po prostu zapomnij.
Po uruchomieniu Coreboot w QEMU możesz eksperymentować z wyższymi warstwami Coreboot i ładunkami, ale QEMU oferuje niewiele możliwości eksperymentowania z kodem startowym niskiego poziomu. Po pierwsze, pamięć RAM działa od samego początku.
Stan początkowy po BIOS
Podobnie jak wiele rzeczy w sprzęcie, standaryzacja jest słaba, a jedną z rzeczy, na których nie należy polegać, jest stan początkowy rejestrów, gdy kod zaczyna działać po BIOS.
Więc zrób sobie przysługę i użyj kodu inicjalizacji, takiego jak: https://stackoverflow.com/a/32509555/895245
Rejestruje się %ds
i %es
ma ważne skutki uboczne, więc powinieneś je wyzerować, nawet jeśli nie używasz ich wyraźnie.
Zauważ, że niektóre emulatory są ładniejsze niż prawdziwy sprzęt i zapewniają dobry stan początkowy. Potem, gdy idziesz na prawdziwym sprzęcie, wszystko się psuje.
GNU GRUB Multiboot
Sektory rozruchowe są proste, ale nie są zbyt wygodne:
- możesz mieć tylko jeden system operacyjny na dysk
- kod ładowania musi być naprawdę mały i zmieścić się w 512 bajtach. Można to rozwiązać za pomocą wewnętrznego wywołania BIOS 0x13 .
- musisz sam dużo uruchomić, na przykład przejść do trybu chronionego
Z tych powodów GNU GRUB stworzył wygodniejszy format pliku o nazwie multiboot.
Minimalny przykład działania: https://github.com/cirosantilli/x86-bare-metal-examples/tree/d217b180be4220a0b4a453f31275d38e697a99e0/multiboot/hello-world
Używam go również w moim repozytorium przykładów GitHub, aby móc z łatwością uruchamiać wszystkie przykłady na prawdziwym sprzęcie bez milionowego spalania USB. Na QEMU wygląda to tak:
Jeśli przygotujesz system operacyjny jako plik z wieloma uruchomieniami, GRUB będzie mógł go znaleźć w zwykłym systemie plików.
To właśnie robi większość dystrybucji, umieszczając obrazy systemu operacyjnego /boot
.
Pliki Multiboot są w zasadzie plikiem ELF ze specjalnym nagłówkiem. Są one określone przez GRUB pod adresem : https://www.gnu.org/software/grub/manual/multiboot/multiboot.html
Możesz zmienić plik z wieloma uruchomieniami na dysk rozruchowy za pomocą grub-mkrescue
.
El Torito
Format, który można wypalić na płytach CD: https://en.wikipedia.org/wiki/El_Torito_%28CD-ROM_standard%29
Możliwe jest również utworzenie obrazu hybrydowego działającego na ISO lub USB. Można to zrobić za pomocą grub-mkrescue
( przykład ), a także przy make isoimage
użyciu jądra Linux isohybrid
.
Zasoby