Znaczenie asemblera

Cały skompilowany kod jądra jest zależny od architektury. Asemblera używa się w tych przypadkach, w których sam język C nie daje możliwości wykorzystania pewnych własności sprzętu. Te fragmenty kodu źródłowego są zależne od architektury, dlatego stwarzają problemy - każda nowa platforma sprzętowa wymaga oddzielnej implementacji kodu zajmującego się tym samym. By łatwiej było je odnaleźć, zostały wyizolowane w określonych katalogach.

Pliki które są bezpośrednio kompilowane znajdują się w podkatalogach arch/architektura katalogu źródeł jądra.

Pliki nagłowkowe natomiast można znaleźć w katalogu include/asm. Katalog include może zawierać podkatalogi odpowiadające różnym architekturom. Wtedy ich nazwy mają postać include/asm-architektura, a include/asm jest miękkim dowiązaniem do podkatalogu występującej architektury.

Architektura Intel 80x86 jest oznaczona symbolem i386, a architektura Intel Itanium, z którą będziemy ją porównywać to ia64.

Powrót

Synchronizacja

Synchronizacja nie jest możliwa bez wsparcia ze strony sprzętu. Język C nie udostępnia składni do wykonywania operacji atomowo, dlatego muszą one być zaimplementowane w asemblerze. W architekturze 80x86 proste operacje zapisu i odczytu są atomowe na jednym procesorze. W systemie wieloprocesorowym może się jednak zdarzyć, że dwa procesory będą próbowały jednocześnie wykonać operację na tej samej komórce pamięci. Aby temu zapobiec, używa się polecenia LOCK. Polecenie to powoduje przydzielenie procesowi magistrali na wyłączność, na czas wykonania kolejnej operacji.

Polecenia atomowe

W pliku include/asm/atomic.h zdefiniowany jest typ atomic_t. Zakłada się, że atomic_t przechowuje co najmniej 24-bitową wartość całkowitą. W plikach include/asm/atomic.h i include/asm/bitops.h zdefiniowany jest zestaw makr i funkcji pozwalających operować atomowo na zmiennych tego typu.

Bariery pamięci

W synchronizacji czasami zachodzi potrzeba, aby instrukcje wykonywały się w określonej kolejności. Zarówno kompilator, jak i procesor może się starać zmieniac kolejność instrukcji maszynowych w celu optymalizacji kodu. Żeby temu zapobiec, stosuje się bariery pamięci - fragmenty kodu, które wyraźnie oddzielają kod, który powinien wykonać się przed nimi od tego, który powinien wykonać się później. W pliku include/asm/system.h zdefiniowane są makra/funkcje mb(), rmb(), wmb() oraz ich odpowiedniki poprzedzone prefiksem smp_, które szeregują instrukcje w systemach wieloprocesorowych. Druga i trzecia wersja wpływają tylko na instrukcje, odpowiednio, czytające z pamięci i zapisujące do niej.

Blokady wirujące

Blokady wirujące - zwykłe i typu czytelnicy-pisarze - są zaimplementowane w asemblerze, ponieważ są one globalne - muszą być zatem chronione przed dostępem wielu procesorów jednocześnie. W 80x86 jest to realizowane przy użyciu bajtu LOCK. Ponadto pożądane jest, aby pętla, w której procesor oczekuje była możliwie krótka - dlatego asembler znajduje tu zastosowanie. Blokady wirujące są zdefiniowane w include/asm/spinlock.h.

Semafory jądra

Semafory to kolejny przykład podstawowych narzędzi synchronizacji, które wykorzystują kod zależny od architektury. Ciekawy jest jednak sposób implementacji operacji up i down - kod asemblerowy (zdefiniowany w include/asm/semaphore.h) zmienia atomowo wewnętrzny licznik semafora po czym wywołuje napisaną w C funkcję, odpowiednio __up lub __down (kod w arch/architektura/kernel/semaphore.c).

Blokowanie przerwań sprzętowych

W architekturze 80x86 do blokowania i odblokowywania przerwań sprzętowych służą polecenia CLI i STI. Ich odpowieniki zdefiniowane w include/system.h to __cli() i __sti(). W najnowszych wersjach jądra są również zdefniowane makra o nazwach niezwiązanych z architekturą: local_irq_disable i local_irq_enable. Zazwyczaj jednak zamiast __sti() używa się makr __save_flags (local_irq_save) oraz __restore_flags (local_irq_restore). W systemach wieloprocesorowych zagadnienie jest trochę bardziej skomplikowane i dla nich zdefiniowane są inne makra.

Powrót

Obsługa przerwań

Procedury obsługujące przerwania wymagają wykonania pewnych dodatkowych czynności przed i po ich właściwym działaniu. Chodzi przede wszystkim o zachowanie i odtworzenie rejestrów. Kod obudowujący wywołanie funkcji napisanych w C znajduje się w asemblerowym pliku arch/architektura/kernel/entry.S. Dotyczy to zarówno przerwań sprzętowych, wyjątków procesora, jak i wywołań systemowych. Kod rozpoczynający obsługę przerwań sprzętowych (na 80x86) jest generowany przez makra zdefiniowane w pliku include/asm/hw_irq.h.

Zestaw wyjątków podnoszonych przez procesor zależy ściśle od architektury. Ich wektory inicjalizuje funkcja trap_init() zdefiniowana w arch/architektura/kernel/traps.c.

Powrót

Przełączanie kontekstu

Najważniejszym elementem kontekstu procesu jest kontekst sprzętowy, czyli zestaw rejestrów - zależy on oczywiście od procesora. Żeby uzyskac dostęp do poszczególnych rejestrów trzeba się posłużyć asmeblerem. Zajmuje się tym makro switch_to zdefiniowane w include/asm/system.h.

Aby zapamiętać zawartość rejestrów, potrzebna jest odpowiednia struktura. Jest to struktura thread_struct, której definicja została umieszczona w include/asm/processor.h.

Powrót