W jaki sposób procesor znajduje kod jądra po przerwie?

13

Gdy wystąpi przerwanie, procesor wstrzymuje bieżący proces i wywołuje kod jądra, aby obsłużyć przerwanie. Skąd procesor wie, gdzie wejść do jądra?

Rozumiem, że istnieją procedury obsługi przerwań, które można zainstalować dla każdej linii przerwań. Ale ponieważ procesor wykonuje tylko „logikę przewodową”, musi istnieć pewne predefiniowane miejsce, które wskazuje albo na samą procedurę obsługi przerwań, albo jakiś kod, który wykonuje się przed funkcją obsługi (ponieważ może istnieć wiele procedur obsługi dla jednej linii przerwania, zakładam, że końcowy).

Philipp Murry
źródło

Odpowiedzi:

13

Podczas uruchamiania jądro zainicjuje tablicę wektorów przerwań (zwaną tablicą deskryptorów przerwań lub IDT na x86), która wskazuje obsługę przerwań dla każdej linii.

Przed 80286 IDT był zawsze przechowywany pod stałym adresem; zaczynając od 80286, IDT jest ładowany za pomocą LIDTinstrukcji.

Tabele wektorów przerwań wskazują na pojedynczy moduł obsługi na linię przerwania; to powiedziawszy, jądro może na przykład udostępnić moduł obsługi przerwania, który uruchamia kilka innych procedur przerwania, lub pojedynczy moduł obsługi, który obejmuje niektóre lub wszystkie przerwania. Linux robi te rzeczy, udostępniając ogólną procedurę obsługi przerwań, która określa, która linia przerwania została wywołana i znajduje odpowiednią procedurę obsługi dalszego połączenia.

Adam Maras
źródło
1
więc procesor używa linii przerwania jako indeksu IDT, umieszcza wpis w komputerze i zaczyna wykonywać? ale czy nie istnieje ogólna funkcja działająca przed wszystkimi modułami obsługi przerwań? dla Linuksa byłoby to do_IRQ (). czy jest to funkcja wskazywana przez każde wejście IDT, bez względu na linię przerwania?
Philipp Murry,
@PhilippMurry tak. Jądro następnie używa własnego zestawu procedur obsługi przerwań (których może być więcej niż jeden na linię), aby faktycznie obsłużyć przerwanie, zanim powróci do poprzednio wykonywanego kodu.
Adam Maras,
okej, więc w rzeczywistości istnieją dwa typy programów obsługi przerwań: te, które wywołuje procesor (zawsze do_IRQ ()), i te, które wywołuje jądro (ten, który zarejestrowałem przez request_irq ()). czy mógłbyś dodać to do swojej odpowiedzi? myślę, że wtedy to zaakceptuję :) wielkie dzięki
Philipp Murry
1
@PhilippMurry Nie jestem ekspertem od tego, jak Linux obsługuje przerwania (i jak programiści jądra korzystają z tego systemu), ale dodałem kilka szerszych informacji na temat tego, w jaki sposób jądra mogą mieć własne zarządzanie ISR.
Adam Maras,
12

Tak, istnieje predefiniowane miejsce zawierające adres kodu, na który należy przejść: wektor przerwań . W zależności od procesora może to być określone miejsce w pamięci fizycznej (8088), określone miejsce w pamięci wirtualnej, rejestr procesora, miejsce w pamięci wskazane przez rejestr (ARM, 386),…

Szczegóły różnią się w zależności od procesora, ale głównymi wspólnymi elementami obsługi przerwań w procesorze są:

  • Maska przerwań (aby każde kolejne przerwanie musiało poczekać).
  • Ustaw tryb procesora na tryb jądra lub przerwania (jeśli procesor ma takie tryby).
  • Zapisz wartość licznika programu w znanym miejscu (rejestrze lub pamięci).
  • Ewentualnie zapisz wartość innych rejestrów lub przełączaj się między bankami rejestrów).
  • Wykonaj następną instrukcję (przy nowej wartości komputera).
Gilles „SO- przestań być zły”
źródło
1

Pozostałe dwie odpowiedzi (w momencie pisania) mówią o przerwaniach i IDT. Jest to jednak poprawne w przypadku nowoczesnego procesora Intel, nie ma mniej niż trzy sposoby na wywołanie jądra.

Metoda nr 1: Przerwania.

To wyjaśniono powyżej. Ustawiasz pozycję w tabeli deskryptorów przerwań / wektorze przerwań, a następnie wykonujesz przerwanie programowe, aby wejść do jądra.

Główną zaletą tej metody jest to, że typowe jądro musi być w stanie obsłużyć przerwania i działa na archaicznym sprzęcie.

Metoda nr 2: Zadzwoń do bram.

Bramka jest specjalnym rodzajem selektora segmentu. Cel połączenia musi zostać załadowany do globalnej lub lokalnej tabeli deskryptorów segmentów (odpowiednio GDT i LDT). Jeśli następnie wykonasz instrukcję dalekiego połączenia, używając bramki jako segmentu (przesunięcie połączenia jest ignorowane), pozwala to na wywołanie bardziej uprzywilejowanego kodu. Bramki telefoniczne są niezwykle elastyczne; architektura IA-32 ma cztery poziomy przywilejów, a bramki wywoływania pozwalają na wywołanie dowolnego poziomu.

Nie wierzę, że Linux kiedykolwiek używał bramek połączeń, ale Windows 95 tak. Usługi jądra Win95 ( krnl386.exei kernel.dll) faktycznie działały w trybie użytkownika (pierścień 3). Najwyższy poziom uprawnień (pierścień 0) został użyty tylko dla sterowników i mikrojądra, które wykonywało tylko przełączanie procesów. Wzywanie kierowców odbywało się za pomocą bramek. Umożliwiło to starszemu 16-bitowemu kodowi (którego było dużo!) Używanie sterowników Win95 tylko za pomocą standardowego dalekiego wywołania, tak jak zawsze.

Niewystarczająca ochrona globalnej tabeli deskryptorów była przyczyną wielu exploitów Windows 95, którym udało się zainstalować własne bramki wywoływania, zapisując w pamięci.

Metoda nr 3: SYSCALL / SYSRET i SYSENTER / SYSEXIT

Są to dwa zestawy instrukcji, wymyślone niezależnie przez AMD i Intela, ale zasadniczo robią to samo. SYSCALL / SYSRET był pierwszy i był tylko AMD, SYSENTER / SYSEXIT był Intel, ale AMD go teraz wdraża. Więc opiszę SYSENTER / SYSEXIT.

W przeciwieństwie do bramek telefonicznych, SYSENTER może być użyty tylko do przeniesienia na dzwonek 0 i może być przeniesiony tylko do jednej lokalizacji. Ma jednak tę zaletę, że charakteryzuje się wyjątkowo niskim opóźnieniem, ponieważ w przeciwieństwie do połączenia lub przerwania nie dotyka stosu.

Lokalizacja przesyłania jest konfigurowana za pomocą trzech rejestrów specyficznych dla modelu: jednego dla informacji o segmencie i jednego dla wskaźnika instrukcji i wskaźnika stosu kodu jądra. Ponieważ nic nie jest „wypychane” na stos, kod trybu użytkownika jest odpowiedzialny za poinformowanie jądra, do którego ma wrócić, przekazując wskaźnik instrukcji return i wskaźnik stosu w rejestrach. Jądro jest odpowiedzialne za odtwarzanie wskaźnika stosu, a instrukcja SYSEXIT przywraca wskaźnik instrukcji.

Dalsze informacje na temat instrukcji SYSENTER i SYSEXIT.

Pseudonim
źródło