Użycie liczb zmiennoprzecinkowych w jądrze Linuksa

83

Czytam „Linux Kernel Development” Roberta Love'a i natknąłem się na następujący fragment:

Brak (łatwego) użycia zmiennoprzecinkowego punktu

Kiedy proces przestrzeni użytkownika używa instrukcji zmiennoprzecinkowych, jądro zarządza przejściem z trybu liczb całkowitych do trybu zmiennoprzecinkowego. To, co jądro musi zrobić podczas używania instrukcji zmiennoprzecinkowych, różni się w zależności od architektury, ale jądro zwykle łapie pułapkę, a następnie inicjuje przejście z trybu liczb całkowitych do trybu zmiennoprzecinkowego.

W przeciwieństwie do przestrzeni użytkownika, jądro nie ma luksusu płynnej obsługi zmiennoprzecinkowej, ponieważ nie może łatwo przechwytywać siebie. Używanie zmiennoprzecinkowych wewnątrz jądra wymaga, między innymi, ręcznego zapisywania i przywracania rejestrów zmiennoprzecinkowych. Krótka odpowiedź brzmi: nie rób tego! Z wyjątkiem rzadkich przypadków, w jądrze nie ma operacji zmiennoprzecinkowych.

Nigdy nie słyszałem o tych trybach „całkowitych” i „zmiennoprzecinkowych”. Czym dokładnie są i dlaczego są potrzebne? Czy to rozróżnienie istnieje w głównych architekturach sprzętowych (takich jak x86), czy też jest specyficzne dla bardziej egzotycznych środowisk? Co dokładnie oznacza przejście z trybu liczb całkowitych do trybu zmiennoprzecinkowego, zarówno z punktu widzenia procesu, jak i jądra?

NPE
źródło
2
Książka trochę myli sprawę, mówiąc o „trybie”. Instrukcje liczb całkowitych są zawsze dostępne, ale FPU można wyłączyć całkowicie lub częściowo. Żadna użyteczna funkcja nigdy nie składała się wyłącznie z operacji FP, na przykład wszystkie instrukcje sterujące są traktowane jako „liczby całkowite”. Więcej informacji poniżej.
DigitalRoss
@DigitalRoss: Zgadzam się z terminologią. Dzięki za odpowiedź BTW, wszystko stało się jasne.
NPE
Byłoby interesujące wiedzieć, z czego wynika chęć wykonywania operacji zmiennoprzecinkowych w jądrze. Kuszące jest powiedzenie „kiepski projekt” w sensie próby zrobienia czegoś w jądrze, co powinno być zrobione poza nim, ale być może są rzeczy, które jądro naprawdę powinno robić, gdzie wykorzystanie FPU byłoby innowacyjnym rozwiązaniem?
Chris Stratton,
2
Ponieważ nikt o tym nie wspomniał, jeśli używasz FP (lub SIMD) wewnątrz jądra, musisz wywołać kod kernel_fpu_begin()/ kernel_fpu_end()przed / po kodzie, aby upewnić się, że stan FPU przestrzeni użytkownika nie jest uszkodzony. To właśnie mdrobi kod Linuksa dla RAID5 / RAID6.
Peter Cordes

Odpowiedzi:

86

Dlatego...

  • wiele programów nie używa zmiennoprzecinkowych lub nie używa ich w żadnym przedziale czasu; i
  • zapisywanie rejestrów FPU i innych stanów FPU wymaga czasu; w związku z tym

... jądro systemu operacyjnego może po prostu wyłączyć FPU. Presto, brak stanu do zapisania i przywrócenia, a zatem szybsze przełączanie kontekstów. (To oznaczał tryb, oznaczał tylko, że FPU jest włączony.)

Jeśli program spróbuje wykonać operację FPU, program wpadnie do jądra, jądro włączy FPU, przywróci każdy zapisany stan, który może już istnieć, a następnie powróci do ponownego wykonania operacji FPU.

W czasie przełączania kontekstu wie, że faktycznie przechodzi przez logikę zapisywania stanu. (A następnie może ponownie wyłączyć FPU.)

Nawiasem mówiąc, uważam, że wyjaśnienie w książce, dla którego jądra (i nie tylko Linux) unikają operacji FPU, jest ... nie do końca dokładne. 1

Jądro może uwięzić się w sobie i robi to w wielu sytuacjach. (Zegary, błędy stron, przerwania urządzeń, inne). Prawdziwym powodem jest to, że jądro nie potrzebuje szczególnie operacji FPU, a także musi w ogóle działać na architekturach bez FPU. Dlatego po prostu unika złożoności i czasu wykonywania wymaganego do zarządzania własnym kontekstem FPU, nie wykonując operacji, dla których zawsze istnieją inne rozwiązania programowe.

Warto zauważyć, jak często stan FPU musiałby być zapisywany, gdyby jądro chciał użyć FP . . . każde wywołanie systemowe, każde przerwanie, każde przełączenie między wątkami jądra. Nawet gdyby okazjonalnie istniała potrzeba jądra FP, 2 , prawdopodobnie byłoby szybciej zrobić to w oprogramowaniu.


1. To znaczy bardzo źle.
2. Jest kilka przypadków, które znam, w których oprogramowanie jądra zawiera implementację arytmetyki zmiennoprzecinkowej . Niektóre architektury implementują tradycyjne operacje FPU w sprzęcie, ale pozostawiają skomplikowane operacje IEEE FP oprogramowaniu. (Pomyśl: arytmetyka denormalna.) Kiedy zdarza się jakiś dziwny przypadek narożny IEEE, pułapka na oprogramowanie, które zawiera pedantycznie poprawną emulację operacji, które mogą pułapki.

DigitalRoss
źródło
16

W niektórych projektach jądra rejestry zmiennoprzecinkowe nie są zapisywane, gdy zadanie „jądra” lub „systemu” jest przełączane. (Dzieje się tak, ponieważ rejestry FP są duże, a ich zapisanie zajmuje zarówno czas, jak i miejsce). Jeśli więc spróbujesz użyć FP, wartości będą losowo zmieniane na „poof”.

Ponadto, niektóre sprzętowe schematy zmiennoprzecinkowe polegają na jądrze do obsługi "dziwnych" sytuacji (np. Dzielenia przez zero) poprzez pułapkę, a wymagany mechanizm pułapki może być na wyższym "poziomie" niż zadanie jądra aktualnie uruchomione.

Z tych powodów (i kilku innych) niektóre sprzętowe schematy FP będą pułapki, gdy użyjesz instrukcji FP po raz pierwszy w zadaniu. Jeśli masz pozwolenie na użycie FP, to w zadaniu zostanie włączona flaga zmiennoprzecinkowa, jeśli nie, zostaniesz ostrzelany przez pluton egzekucyjny.

Hot Licks
źródło
W systemie Linux użyj kernel_fpu_begin()/ kernel_fpu_end()przed / po kodzie, aby wyzwolić zapisywanie / przywracanie stanu FPU przestrzeni użytkownika (i domyślam się, że stan FPU jądra jest przeciwny wywłaszczaniu).
Peter Cordes