Wewnętrzne przełączniki kontekstowe

Chcę się uczyć i uzupełniać braki w mojej wiedzy za pomocą tego pytania

Więc, użytkownik uruchamia wątek (na poziomie jądra) i teraz wywołuje yield (wywołanie systemowe, jak przypuszczam) Scheduler musi teraz zapisać kontekst bieżącego wątku w TCB (który jest gdzieś przechowywany w jądrze) i wybrać inny wątek do uruchomienia, załadować jego kontekst i przejść do CS:EIP. Aby zawęzić sprawy pracuję nad Linuksem działającym na architekturze x86. Teraz chcę dostać się do szczegóły:

Więc najpierw mamy wywołanie systemowe:

1) funkcja wrapper dla yield wypchnie argumenty wywołania systemowego na stos. Wciśnij adres zwrotny i wywołaj przerwanie z numerem wywołania systemowego wciśniętym do jakiegoś rejestru (powiedzmy EAX).

2) przerwanie zmienia tryb PROCESORA z Użytkownika na jądro i przeskakuje do tablicy wektorów przerwań, a stamtąd do rzeczywistego wywołania systemowego w jądrze.

3) wydaje mi się, że scheduler jest wywoływany teraz i teraz musi Zapisz bieżący stan w TCB. Oto mój dylemat. Ponieważ, scheduler będzie używał stosu jądra, a nie stosu użytkownika do wykonywania swojej operacji (co oznacza, że SS i SP muszą zostać zmienione), w jaki sposób zapisuje stan użytkownika bez modyfikowania żadnego rejestru w procesie. Czytałem na forach, że są specjalne instrukcje sprzętowe do zapisywania stanu, ale potem jak scheduler uzyskuje do nich dostęp i kto uruchamia te instrukcje i kiedy?

4) The scheduler now zapisuje stan do TCB i ładuje inny TCB

5) gdy scheduler uruchomi oryginalny wątek, Kontrola wraca do funkcji wrapper, która czyści stos i wątek wznawia

Pytania poboczne: czy scheduler działa jako wątek tylko dla jądra (tzn. wątek, który może uruchamiać tylko kod jądra)? Czy istnieje oddzielny stos jądra dla każdego wątku jądra lub każdego procesu?

Author: Bruce, 2012-09-28

3 answers

Na wysokim poziomie, istnieją dwa oddzielne mechanizmy do zrozumienia. Pierwszym z nich jest mechanizm wejścia/wyjścia jądra: przełącza on pojedynczy działający wątek z uruchomionego kodu usermode na uruchomiony kod jądra w kontekście tego wątku i z powrotem. Drugim jest sam mechanizm przełączania kontekstowego, który przełącza się w trybie jądra z pracy w kontekście jednego wątku na drugi.

Tak więc, gdy wątek a wywoła sched_yield() i zostanie zastąpiony przez wątek B, co się dzieje jest:

  1. wątek A wchodzi do jądra, zmieniając tryb użytkownika na tryb jądra;
  2. wątek a w kontekście jądra-przełącza się na wątek B w jądrze;
  3. wątek B opuszcza jądro, zmieniając tryb jądra z powrotem na tryb użytkownika.

Każdy wątek użytkownika ma zarówno stos trybu użytkownika, jak i stos trybu jądra. Gdy wątek wchodzi do jądra, bieżąca wartość stosu trybu użytkownika (SS:ESP) i wskaźnika instrukcji (CS:EIP) są zapisywane w trybie jądra wątku stos, a procesor przełącza się na stos trybu jądra-za pomocą mechanizmu syscall int $80, robi to sam procesor. Pozostałe wartości rejestru i flagi są następnie zapisywane na stosie jądra.

Gdy wątek powróci z jądra do trybu użytkownika, wartości rejestru i flagi są wyświetlane ze stosu trybu jądra, a następnie wartości trybu użytkownika i wskaźnika instrukcji są przywracane z zapisanych wartości ze stosu trybu jądra.

Gdy kontekst wątku-przełącza się, to wywołania do schedulera (scheduler nie działa jako osobny wątek - zawsze działa w kontekście bieżącego wątku). Kod harmonogramu wybiera proces, który ma zostać uruchomiony, i wywołuje funkcję switch_to(). Ta funkcja zasadniczo przełącza stosy jądra - zapisuje bieżącą wartość wskaźnika stosu do TCB dla bieżącego wątku (nazywanego struct task_struct w Linuksie) i ładuje wcześniej zapisany wskaźnik stosu z TCB dla następnego wątku. W tym momencie również zapisuje i przywraca inny stan wątku, który zwykle nie jest używany przez jądro - takie rzeczy jak rejestry zmiennoprzecinkowe/SSE. Jeśli przełączane wątki nie mają tej samej przestrzeni pamięci wirtualnej(np. są w różnych procesach), tabele stron są również przełączane.

Więc możesz zobaczyć, że stan trybu użytkownika rdzenia wątku nie jest zapisywany i przywracany w czasie przełączania kontekstu - jest zapisywany i przywracany do stosu jądra wątku po wejściu i wyjściu z jądra. The context-switch code nie musi się martwić o zbieranie wartości rejestru trybu użytkownika-są one już bezpiecznie zapisywane w stosie jądra do tego momentu.

 93
Author: caf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-29 02:23:59

To, co przegapiłeś w Kroku 2, to to, że stos jest przełączany ze stosu na poziomie użytkownika wątku (gdzie pchałeś args) na stos na poziomie chronionym wątku. Aktualny kontekst wątku przerwanego przez syscall jest faktycznie zapisywany na tym chronionym stosie. Wewnątrz ISR i tuż przed wejściem do jądra, ten chroniony stos jest ponownie przełączany na stos jądra, o którym mówisz. W jądrze funkcje jądra, takie jak funkcje schedulera, ostatecznie wykorzystują kernel-stack. Później wątek zostaje wybrany przez scheduler, a system wraca do ISR, przełącza się z powrotem ze stosu jądra na nowo wybrany (lub pierwszy, jeśli nie jest aktywny wątek o wyższym priorytecie) stos wątku chronionego, który ostatecznie zawiera nowy kontekst wątku. Dlatego kontekst jest przywracany z tego stosu za pomocą kodu automatycznie (w zależności od podstawowej architektury). Na koniec specjalna instrukcja przywraca najnowsze drażliwe Rezystory, takie jak wskaźnik stosu i wskaźnik instrukcji. Z powrotem w kraju użytkownika...

Podsumowując, wątek ma (ogólnie) dwa stosy, a samo jądro ma jeden. Stos jądra jest usuwany na końcu każdego wejścia jądra. Warto zwrócić uwagę, że od wersji 2.6 samo jądro jest gwintowane dla jakiegoś przetwarzania, dlatego też wątek jądra ma swój własny stos na poziomie chronionym obok ogólnego stosu jądra.

Niektóre źródła:

  • 3.3.3 wykonywanie procesu Switch of Understanding the Linux Kernel , O ' Reilly
  • 5.12.1 procedury obsługi wyjątków lub przerwań z podręcznika Intela 3A (sysprogramowanie) . Numer rozdziaĹ ' u moĹźe siÄ ™ róşniÄ ‡ w zaleĹźnoĹ "ci od edycji, wiÄ ™ c wyszukiwanie w" uĹźyciu stosu na transferach do przerwania i procedur obsĹ 'ugi wyjÄ ... tków" powinno doprowadziÄ ‡ do tego dobrego.

Mam nadzieję, że to pomoże!

 10
Author: Benoit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-03 08:40:04

Samo jądro nie ma żadnego stosu. To samo dotyczy procesu. Nie ma również stosu. Wątki są tylko obywatelami systemu, które są uważane za jednostki wykonawcze. Z tego powodu tylko wątki mogą być zaplanowane i tylko wątki mają stosy. Ale jest jeden punkt, który mocno wykorzystuje kod trybu jądra - każda chwila czasu działa w kontekście aktualnie aktywnego wątku. Dzięki temu jądro może ponownie wykorzystać stos aktualnie aktywnego stosu. Zauważ, że tylko jeden z nich moĹźe wykonaÄ ‡ w tym samym momencie albo kod kernela, albo kod uĹźytkownika. Z tego powodu, gdy Kernel jest wywoływany, po prostu ponownie używa stosu wątków i wykonuje czyszczenie przed powrotem kontroli do przerwanych działań w wątku. Ten sam mechanizm działa w programach obsługi przerwań. Ten sam mechanizm jest wykorzystywany przez urządzenia do obsługi sygnałów.

Z kolei stos wątków jest podzielony na dwie odizolowane części, z których jedna nazywa się stosem użytkownika (ponieważ jest używany, gdy wątek wykonuje w trybie użytkownika), oraz drugi nazywa się kernel stack (ponieważ jest używany, gdy wątek uruchamia się w trybie jądra). Gdy wątek przekroczy granicę pomiędzy trybem użytkownika i jądra, procesor automatycznie przełącza go z jednego stosu na drugi. Oba stosy są śledzone przez jądro i procesor w różny sposób. W przypadku stosu jądra procesor stale utrzymuje wskaźnik na szczycie stosu jądra wątku. Jest to łatwe, ponieważ adres ten jest stały dla wątku. Za każdym razem, gdy wątek wchodzi do jądra, znajduje puste jądro stos i za każdym razem, gdy powróci do trybu użytkownika, czyści stos jądra. W tym samym czasie procesor nie zwraca uwagi na wierzchołek stosu użytkownika, gdy wątek działa w trybie jądra. Zamiast tego podczas wprowadzania do jądra, procesor tworzy specjalną ramkę stosu "przerwań" na górze stosu jądra i przechowuje w niej wartość wskaźnika stosu trybu użytkownika. Gdy wątek opuści jądro, CPU przywraca wartość ESP z wcześniej utworzonej ramki stosu "interrupt", natychmiast przed sprzątaniem. (w starszej wersji x86 para instrukcji int / iret obsługuje wejście i wyjście z trybu jądra)

Podczas przechodzenia do trybu jądra, zaraz po utworzeniu ramki stosu "przerwania" przez procesor, jądro wypycha zawartość pozostałych rejestrów procesora do stosu jądra. Zauważ, że is zapisuje wartości tylko dla tych rejestrów, które mogą być używane przez kod jądra. Na przykład kernel nie zapisuje zawartości rejestrów SSE tylko dlatego, że nigdy ich nie dotknie. Podobnie tuż przed prosząc procesor o powrót kontroli do trybu użytkownika, kernel wyskakuje z powrotem do rejestrów wcześniej zapisaną zawartość.

Zauważ, że w takich systemach jak Windows i Linux istnieje pojęcie wątku systemowego (często zwanego wątkiem jądra, wiem, że jest to mylące). Wątki systemowe rodzaj specjalnych wątków, ponieważ wykonują się tylko w trybie jądra i z tego powodu nie mają części użytkownika stosu. Kernel wykorzystuje je do pomocniczych zadań porządkowych.

Przełącznik wątku jest wykonywany tylko w tryb jądra. Oznacza to, że oba wątki wychodzące i przychodzące działają w trybie jądra, oba używają własnych stosów jądra i oba mają stosy jądra mają ramki "przerwań" ze wskaźnikami do góry stosów użytkowników. Kluczowy punkt przełącznika wątków to przełącznik między stosami jąder wątków, tak prosty jak:

pushad; // save context of outgoing thread on the top of the kernel stack of outgoing thread
; here kernel uses kernel stack of outgoing thread
mov [TCB_of_outgoing_thread], ESP;
mov  ESP , [TCB_of_incoming_thread]    
; here kernel uses kernel stack of incoming thread
popad; // save context of incoming thread from the top of the kernel stack of incoming thread

Zauważ, że w jądrze jest tylko jedna funkcja, która wykonuje przełączanie wątku. Dzięki temu za każdym razem, gdy Kernel ma przełączone stosy, może znaleźć kontekst nadchodzącego wątku na szczycie stosu. Tylko dlatego, że za każdym razem przed przełączeniem stosu jądro wypycha kontekst wychodzącego wątku do swojego stosu.

Zauważ również, że za każdym razem po przełączeniu stosu i przed powrotem do trybu użytkownika, jądro przeładowuje umysł procesora o nową wartość górnej części stosu jądra. CzyniÄ ... c to zapewnia, Ĺźe gdy nowy aktywny wątek spróbuje wejĹ "Ä ‡ do kernela w przyszĹ' oĹ "ci, zostanie on przeĹ' Ä ... czony przez CPU na wĹ ' asny stos kernela.

Zauważ również, że nie wszystkie rejestry są zapisywane na stos podczas przełączania wątku, niektóre rejestry takie jak FPU / MMX / SSE są zapisywane w specjalnie dedykowanym obszarze w TCB wychodzącego wątku. Kernel stosuje tutaj odmienną strategię z dwóch powodów. Przede wszystkim nie każdy wątek w systemie z nich korzysta. Popychanie ich treści do i popping go ze stosu dla każdego wątku jest nieefektywne. A drugi są specjalne instrukcje "szybkiego" zapisywania i ładowania ich treści. A te instrukcje nie używają stosu.

Zauważ również, że w rzeczywistości część jądra stosu wątków ma stały rozmiar i jest przydzielana jako część TCB. (prawda dla Linuksa i wierzę Również Dla Windows)

 4
Author: ZarathustrA,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-25 11:59:26