Ładowanie systemu

Asembler znajduje zastosowanie przy pisaniu oprogramowania odpowiedzialnego za ładowanie systemu operacyjnego do pamięci komputera (bootowanie) i przygotowywanie środowiska przed uruchomieniem samego jądra. W tym rozdziale zostaną omówione cztery pliki wchodzące w skład źródeł jądra 2.4.18, zawierające kod wykonujący wyżej wymienione zadania:

W przypadku plików bootsect.S i setup.S ciekawostką jest to, że zostały one napisane w asemblerze 16-bitowym, z czym raczej nie spotkamy się w żadnym innym miejscu w źródłach Linuxa. Pozostałe dwa pliki są już napisane w asemblerze dla procesorów 32-bitowych.

Bootsect

Na początku zajmiemy się standardowym boot loaderem Linuxa, czyli programem bootsect. Źródła tego programu znajdują się w pliku bootsect.S. Jak to jest wspomniane powyżej jest on napisany w asemblerze 16-bitowym, z 20-bitowym adresowaniem pamięci w trybie rzeczywistym (a więc wszystkie adresy w kodzie można traktować jako adresy fizyczne). Do dyspozycji jest, jak z tego wynika, tylko 1MB pamięci. Adresy zapisuje się w postaci SEGMENT:OFFSET, czyli na przykład liniowy adres 0x90200 może być zapisany jako 0x9000:0x0200. Adresy segmentów, w których bootsect umieszcza podczas swojego działania poszczególne dane lub części kodu, zdefiniowane są tam jako stałe w pliku include/asm-i386/boot.h.

Program bootsect jest używany do linuxa ładowania z dyskietki. W przypadku ładowania z dysku zwykle posługujemu się bardziej wyrafinowanymi boot loaderami, jak LILO czy GRUB. Jak wiadomo, jądro może być zapisane na dyskietce startowej w formie spakowanego obrazu. Wersja o której tu mowa radzi sobie tylko z obrazami jądra o rozmiarze do 508kB, czyli takimi jakie zapisuje się w pliku zImage, nie radzi sobie natomiast z dużym obrazem jądra (bzImage); to samo dotyczy części setup. Dla dużego jądra przeznaczone są wersje, których pliki źródłowe mają nazwy poprzedzone dodatkową literką "b" - bbootsect.S, bsetup.S. Różnica jest dość znaczna, w szczególności kod z pliku bootsect.S nie musi adresować pamięci powyżej 1MB - ten z pliku bbootsect.S - musi.

Skompilowany bootsect jest zapisywany na samym początku pliku zImage i umieszczany w pierwszym sektorze dyskietki startowej w czasie jej tworzenia. Każdy boot loader w architekturze Intelowskiej musi zajmować dokładnie jeden 512 - bajtowy sektor. Na końcu tego sektora musi się dodatkowo znaleźć słowo 0x55AA, dzięki któremu BIOS potrafi stwierdzić, że dany sektor jest boot sectorem. BIOS ładuje pod adres 0x7C00 zawartość pierwszego napotkanego boot sectora (czyli w naszym przypadku program bootsect), potem przekazuje do załadowanego kodu sterowanie. Bootsect po przejęciu sterowania wykonuje między innymi następujące czynności:

Podczas tych wszystkich czynności do dyspozycji są tylko przerwania BIOSu (a używane są tylko dwa: 0x10 - do wypisywania na ekranie komunikatów i 0x13 - do obsługi dysku).

Duży obraz jądra (bzImage) jest ładowany pod adres liniowy 0x100000, czyli boot loader musi mieć możliwość adresowania pamięci powyżej 1MB.

Setup

Setup po skompilowaniu jest umieszczany w pliku z obrazem jądra zaraz za bootsectem; również do pamięci operacyjnej jest ładowany zaraz za nim - pod adres liniowy 0x90200 (inne boot loadery same mogą zajmować inne miejsca w pamięci, ale setup i obraz jądra są zawsze umieszczane pod tymi samymi adresami bezwzględnymi). Odpowiada on za przejęcie z BIOSu wszystkich dostępnych informacji na temat sprzętu i umieszczenie ich w miejscu niepotrzebnego już bootsecta, czyli w zakresie adresów 0x90000 - 0x901FF. Jest to znów realizowane przy użyciu przerwań BIOSu. Potem przełącza procesor w tryb chroniony i przechodzi do wykonania startup_32.

Kolejno, setup wykonuje następujące czynności:

Potem następują czynności związane z przejściem do trybu chronionego:

Teraz następuje samo przejście do trybu chronionego, co sprowadza się do ustawienia bitu PE w słowie stanu procesora:

	movw	$1, %ax		# protected mode (PE) bit
	lmsw	%ax		# This is it!

W końcu jest wywoływana funkcja startup_32 (z pliku arch/i386/boot/compressed/head.S w przypadku spakowanego jądra!).

Startup_32 - dekompresja jądra

Kod asemblerowy funkcji startup_32, o której mowa w tym akapicie, jest umieszczony w pliku arch/i386/boot/compressed/head.S. Skompilowany kod wykonywalny znajduje się (po załadowaniu przez boot loadera) pod adresem liniowym 0x1000 w przypadku zImage, lub pod 0x100000 w przypadku bzImage.

Funkcja o identycznej nazwie jest zdefiniowana w arch/i386/kernel/head.S. Ta druga została skrótowo omówiona poniżej. Zbieżność nazw nie służy wyłącznie zmyleniu osób próbujących zagłębić się w temat bootowania Linuxa. Funkcja z pliku arch/i386/boot/compressed/head.S jest wykonywana w przypadku, gdy mamy do czynienia ze spakowanym obrazem jądra. Przygotowuje ona warunki do rozpakowania jądra i wywołuje tą operację. Następnie jądro zostaje rozpakowane, a pod adresem 0x100000 (czyli w przypadku bzImage w tym samym miejscu pamięci, gdzie była "stara" funkcja startup_32) pojawia się "nowa" funkcja startup_32 i zostaje wywołana. Jeśli jądro nie było spakowane, to pod 0x100000 mamy od razu właśnie tą "nową" funkcję startup_32 i tylko ona zostaje wywołana.

Startup_32 wykonuje między innymi następujące czynności:

Startup_32 - przygotowanie środowiska dla jądra

Startup_32 z pliku arch/i386/kernel/head.S przygotowuje środowisko do uruchomienia funkcji start_kernel (init/main.c), napisanej w C i kończącej bootstrap. Pewne czynności wykonane w funkcji setup trzeba powtórzyć, żeby wszystko działało zgodnie z oczekwaniami przy 32-bitowym adresowaniu, trybie chronionym i ze wszystkimi innymi dobrodziejstwami architektur nowszej daty. Startup_32 między innymi:

Dopiero w start_kernel w tablicy IDT zostają umieszczone sensowne wartości i przerwania zostają aktywowane. Wykonywane są też dalsze czynności przygotowujące środowisko dla pracy systemu, jednak funkcja start_kernel() jest już napisana w C (kod znajduje się w init/main.c).

LILO

LILO (Linux Loader) - jego zasadniczą i podstawową częścią jest program umieszczany w Master Boot Recordzie (pierwszym sektorze) twardego dysku, lub Boot Sectorze partycji (aktywnej), gdzie na 512 bajtach musi koegzystować z tablicą partycji (o maksymalnie czterech pozycjach; partycja również ma w swoim Boot Sectorze własną tablicę partycji), oraz specjanym słowem 0x55AA. Programik ten jest ładowany przez BIOS pod adres 0x7C00, podobnie jak to było w przypadku bootsecta.

Przepisuje on siebie w pamięci pod adres 0x9A000, ustawia stos trybu rzeczywistego i ładuje kolejną część LILO.

Druga część LILO pozwala użytkownikowi wybrać system operacyjny do załadowania (jeśli jest kilka dostępnych). Potem może załadować Boot Sector odpowiedniej partycji (czyli boot loader wybranego systemu innego niż Linux) do pamięci (pod adres 0x7C00) i przekazać mu sterowanie. Może też sama skopiować wybrany obraz jądra Linuxa do pamięci. W obu przypadkach dochodzi w końcu do wywołania funkcji setup.

Ta część LILO jest oczywiście napisana w asemblerze.