Studiuję procesory i wiem, jak odczytuje program z pamięci i wykonuję jego instrukcje. Rozumiem również, że system operacyjny oddziela programy w procesach, a następnie przełącza się między nimi tak szybko, że wydaje się, że działają one w tym samym czasie, ale w rzeczywistości każdy program działa sam na procesorze. Ale jeśli system operacyjny to także fragment kodu działający w procesorze, jak może zarządzać procesami?
Myślałem i jedynym wyjaśnieniem, jakie mogłem wymyślić, jest: kiedy system operacyjny ładuje program z pamięci zewnętrznej do pamięci RAM, dodaje własne instrukcje w środku oryginalnych instrukcji programu, więc program jest wykonywany, program może zadzwonić do systemu operacyjnego i zrobić kilka rzeczy. Wierzę, że istnieje instrukcja, którą system operacyjny doda do programu, która pozwoli procesorowi na powrót do kodu systemu operacyjnego na jakiś czas. Ponadto uważam, że gdy system operacyjny ładuje program, sprawdza, czy istnieją jakieś zabronione instrukcje (które przeskakują do zabronionych adresów w pamięci) i wtedy eliminuje.
Czy myślę o sztywności? Nie jestem studentem CS, ale tak naprawdę studentem matematyki. Jeśli to możliwe, chciałbym mieć dobrą książkę na ten temat, ponieważ nie znalazłem nikogo, kto wyjaśniłby, w jaki sposób system operacyjny może zarządzać procesem, jeśli system operacyjny jest również zbiorem kodu działającego w procesorze i nie może działać w tym samym czasie czas programu. Książki mówią tylko, że system operacyjny może zarządzać rzeczami, ale teraz jak.
źródło
Odpowiedzi:
Nie. System operacyjny nie zadziera z kodem programu wstrzykującym do niego nowy kod. Miałoby to szereg wad.
Byłoby to czasochłonne, ponieważ system operacyjny musiałby przeskanować cały plik wykonywalny, wprowadzając zmiany. Zwykle część pliku wykonywalnego jest ładowana tylko w razie potrzeby. Ponadto wkładanie jest drogie, ponieważ musisz przenieść ładunek rzeczy na bok.
Z powodu nierozstrzygalności problemu zatrzymania nie można wiedzieć, gdzie wstawić instrukcje „Powrót do systemu operacyjnego”. Na przykład, jeśli kod zawiera coś podobnego
while (true) {i++;}
, zdecydowanie musisz wstawić hak do tej pętli, ale warunki w pętli (true
tutaj) mogą być dowolnie skomplikowane, więc nie możesz zdecydować, jak długo pętla będzie. Z drugiej strony wstawianie haczyków do każdej pętli byłoby bardzo nieefektywne : na przykład przeskakiwanie z powrotem do systemu operacyjnegofor (i=0; i<3; i++) {j=j+i;}
znacznie spowolniłoby ten proces. Z tego samego powodu nie można wykryć krótkich pętli, aby zostawić je w spokoju.Z powodu nierozstrzygalności problemu zatrzymania nie można ustalić, czy wstrzyknięcia kodu zmieniły znaczenie programu. Załóżmy na przykład, że używasz wskaźników funkcji w swoim programie C. Wstrzyknięcie nowego kodu spowodowałoby przesunięcie lokalizacji funkcji, więc po wywołaniu go przez wskaźnik przeskoczyłbyś w niewłaściwe miejsce. Gdyby programista był na tyle chory, aby użyć skoków obliczeniowych, również one by się nie udały.
Odgrywałoby to wesołe piekło z każdym systemem antywirusowym, ponieważ zmieniałby również kod wirusa i zniszczył wszystkie sumy kontrolne.
Możesz obejść problem z zatrzymaniem, symulując kod i wstawiając haki w dowolnej pętli, która wykonuje więcej niż określoną stałą liczbę razy. Wymagałoby to jednak wyjątkowo kosztownej symulacji całego programu, zanim mógł on zostać uruchomiony.
Właściwie, jeśli chcesz wstrzyknąć kod, kompilator byłby naturalnym miejscem do zrobienia tego. W ten sposób musiałbyś to zrobić tylko raz, ale nadal nie działałoby z drugiego i trzeciego powodu podanego powyżej. (I ktoś mógłby napisać kompilator, który nie grał razem).
Istnieją trzy główne sposoby odzyskania kontroli przez system z procesów.
W systemach współpracujących (lub nieprzekazujących) istnieje
yield
funkcja, którą proces może wywołać w celu przywrócenia kontroli nad systemem operacyjnym. Oczywiście, jeśli jest to twój jedyny mechanizm, polegasz na procesach, które zachowują się ładnie, a proces, który nie daje rezultatu, spowoduje zatrzymanie procesora aż do jego zakończenia.Aby uniknąć tego problemu, stosuje się przerwanie timera. Procesory pozwalają systemowi operacyjnemu rejestrować wywołania zwrotne dla wszystkich rodzajów przerwań realizowanych przez procesor. System operacyjny używa tego mechanizmu do rejestrowania wywołania zwrotnego dla przerwania czasomierza uruchamianego okresowo, co umożliwia mu wykonanie własnego kodu.
Za każdym razem, gdy proces próbuje odczytać z pliku lub wchodzić w interakcje ze sprzętem w jakikolwiek inny sposób, prosi system operacyjny o wykonanie pracy. Gdy system operacyjny zostanie poproszony o zrobienie czegoś przez proces, może zdecydować o wstrzymaniu tego procesu i uruchomieniu innego. Może to zabrzmieć nieco makiawelicznie, ale jest to słuszne: dyskowe operacje we / wy są powolne, więc równie dobrze możesz pozwolić procesowi B na uruchomienie, podczas gdy proces A czeka na wirujące bryły metalu w odpowiednim miejscu. Sieć we / wy jest jeszcze wolniejsza. Klawiatura I / O jest lodowata, ponieważ ludzie nie są istotami gigahercowymi.
źródło
1
, procesor zatrzymuje wszystko, co robi, i rozpoczyna przetwarzanie przerwania (co w zasadzie oznacza „zachowaj stan i przeskocz do adresu w pamięci”). Sama obsługa przerwań nie jest żadnymx86
kodem, jest dosłownie podłączona. Po skoku ponownie wykonuje (dowolny)x86
kod. Wątki są znacznie wyższą abstrakcją.Chociaż odpowiedź Davida Richerby'ego jest dobra, w pewien sposób rzuca światło na to, w jaki sposób nowoczesne systemy operacyjne zatrzymują istniejące programy. Moja odpowiedź powinna być dokładna dla architektury x86 lub x86_64, która jest jedyną powszechnie używaną w komputerach stacjonarnych i laptopach. Inne architektury powinny mieć podobne metody osiągnięcia tego celu.
Podczas uruchamiania systemu operacyjnego tworzona jest tabela przerwań. Każdy wpis w tabeli wskazuje trochę kodu w systemie operacyjnym. Kiedy dochodzi do przerwań, które są kontrolowane przez CPU, patrzy na tę tabelę i wywołuje kod. Istnieje wiele przerwań, takich jak dzielenie przez zero, niepoprawny kod i niektóre zdefiniowane przez system operacyjny.
W ten sposób proces użytkownika komunikuje się z jądrem, na przykład, jeśli chce czytać / zapisywać na dysku lub coś innego, co kontroluje jądro systemu operacyjnego. System operacyjny skonfiguruje również licznik czasu, który wywołuje przerwanie po zakończeniu, więc działający kod jest siłą zmieniany z programu użytkownika na jądro systemu operacyjnego, a jądro może wykonywać inne czynności, takie jak kolejkowanie innych programów do uruchomienia.
Z pamięci, kiedy tak się dzieje, jądro systemu operacyjnego musi zapisać tam, gdzie był kod, a kiedy jądro skończy robić to, co trzeba, przywraca poprzedni stan programu. Dlatego program nawet nie wie, że został przerwany.
Proces nie może zmienić tabeli przerwań z dwóch powodów. Pierwszy polega na tym, że działa w środowisku chronionym, więc jeśli spróbuje wywołać określony kod chronionego zestawu, procesor wyzwoli kolejne przerwanie. Drugim powodem jest pamięć wirtualna. Lokalizacja tabeli przerwań ma wartość od 0x0 do 0x3FF w rzeczywistej pamięci, ale w procesach użytkownika lokalizacja ta zwykle nie jest mapowana, a próba odczytania niezapisanej pamięci spowoduje kolejne przerwanie, więc bez chronionej funkcji i możliwości zapisu do prawdziwej pamięci RAM , proces użytkownika nie może tego zmienić.
źródło
Jądro systemu operacyjnego odzyskuje kontrolę nad działającym procesem z powodu procedury obsługi przerwań zegara procesora, a nie poprzez wstrzyknięcie kodu do procesu.
Powinieneś przeczytać o przerwaniach, aby uzyskać więcej wyjaśnień na temat ich działania oraz sposobu, w jaki jądra systemu operacyjnego je obsługują i implementują różne funkcje.
źródło
Nie jest to metoda podobna do tego, co można opisać: wielozadaniowość spółdzielczej . System operacyjny nie wstawia instrukcji, ale każdy program musi być napisany w celu wywołania funkcji systemu operacyjnego, które mogą wybrać uruchomienie innego procesu współpracy. Ma to wady, które opisujesz: awaria jednego programu usuwa cały system. Windows do wersji 3.0 włącznie włącznie działał w ten sposób; 3.0 w „trybie chronionym” i wyżej nie.
Zapobiegawcza wielozadaniowość (obecnie jest normalna) opiera się na zewnętrznym źródle przerwań. Przerwania zastępują normalny przepływ kontroli i zwykle zapisują gdzieś rejestry, więc CPU może zrobić coś innego, a następnie przejrzyście wznowić program. Oczywiście system operacyjny może zmienić rejestr „kiedy wychodzisz z przerwań, wznów tutaj”, więc wznawia się w innym procesie.
(Niektóre systemy zrobić przepisywania instrukcjami w ograniczonym stopniu na obciążenia program o nazwie „thunking”, a procesor Transmeta dynamicznie zrekompilowane do własnego zestawu instrukcji)
źródło
Wielozadaniowość nie wymaga wprowadzania kodu. W systemie operacyjnym, takim jak Windows, jest element kodu systemu operacyjnego zwany harmonogramem, który polega na przerwaniu sprzętowym wywołanym przez zegar sprzętowy. Jest to wykorzystywane przez system operacyjny do przełączania się między różnymi programami i samym sobą, co sprawia, że wszystko wydaje się naszym ludzkim wyobrażeniem, że dzieje się to jednocześnie.
Zasadniczo system operacyjny programuje sprzętowy zegar tak, aby uruchamiał się tak często ... może 100 razy na sekundę. Kiedy licznik czasu się wyłącza, generuje przerwanie sprzętowe - sygnał, który informuje procesor, aby przerwał to, co robi, zapisać swój stan na stosie, zmienić tryb na bardziej uprzywilejowany i wykonać kod, który znajdzie w specjalnie wyznaczonym miejsce w pamięci. Ten kod bywa częścią harmonogramu, który decyduje o tym, co należy zrobić dalej. Może być konieczne wznowienie jakiegoś innego procesu, w którym to przypadku będzie on musiał wykonać tak zwany „przełącznik kontekstowy” - zastępując cały jego obecny stan (w tym tabele pamięci wirtualnej) stanem innego procesu. Wracając do procesu, musi przywrócić cały kontekst tego procesu,
„Specjalnie wyznaczone” miejsce w pamięci nie musi być znane tylko systemowi operacyjnemu. Implementacje są różne, ale ich sedno polega na tym, że procesor zareaguje na różne przerwania, wykonując wyszukiwanie w tabeli; lokalizacja tabeli znajduje się w określonym miejscu w pamięci (określonym przez konstrukcję sprzętową procesora), zawartość tabeli jest ustalana przez system operacyjny (zazwyczaj w czasie rozruchu), a „rodzaj” przerwania określa, który wpis w tabeli ma służyć jako „procedura obsługi przerwań”.
Żadna z tych czynności nie obejmuje „wstrzykiwania kodu” ... opiera się na kodzie zawartym w systemie operacyjnym we współpracy z funkcjami sprzętowymi CPU i jego obwodów pomocniczych.
źródło
Myślę, że najbliższym przykładem tego, co opisujesz, jest jedna z technik używanych przez VMware , pełna wirtualizacja z wykorzystaniem tłumaczenia binarnego .
VMware działa jak warstwa pod co najmniej jednym systemem wykonującym jednocześnie na tym samym sprzęcie.
Większość wykonywanych instrukcji (np. W zwykłych aplikacjach) można zwirtualizować za pomocą sprzętu, ale samo jądro systemu operacyjnego korzysta z instrukcji, których nie można zwirtualizować, ponieważ jeśli kod maszynowy zgadywanego systemu operacyjnego zostałby wykonany niezmodyfikowany, „wybuchłby ”kontroli hosta VMware. Na przykład system operacyjny gościa musiałby działać w najbardziej uprzywilejowanym pierścieniu ochronnym i skonfigurować tabelę przerwań. Gdyby było to dozwolone, VMware straciłoby kontrolę nad sprzętem.
VMware przepisuje te instrukcje w kodzie systemu operacyjnego przed ich wykonaniem, zastępując je skokami do kodu VMware, który symuluje pożądany efekt.
Ta technika jest więc nieco analogiczna do tego, co opisujesz.
źródło
Istnieje wiele przypadków, w których system operacyjny może „wstrzyknąć kod” do programu. Wersje systemu Apple Macintosh oparte na 68000 budują tabelę wszystkich punktów wejścia segmentu (znajdujących się bezpośrednio przed statycznymi zmiennymi globalnymi, IIRC). Kiedy program się uruchamia, każdy wpis w tabeli składa się z instrukcji pułapki, po której następuje numer segmentu i przesunięcie do segmentu. Jeśli pułapka zostanie wykonana, system sprawdzi słowa po instrukcji pułapki, aby zobaczyć, jaki segment i przesunięcie jest wymagane, załaduj segment (jeśli jeszcze nie jest), dodaj adres początkowy segmentu do odsunięcia i następnie zastąp pułapkę skokiem do nowo obliczonego adresu.
W starszych programach komputerowych, chociaż technicznie nie było to zrobione przez „system operacyjny”, często budowano kod z instrukcjami pułapki zamiast instrukcji matematycznych koprocesora. Jeśli nie zainstalowano koprocesora matematycznego, moduł obsługi pułapek go emulował. Jeśli koprocesor został zainstalowany, to przy pierwszym wzięciu pułapki program obsługi zastąpi instrukcję pułapki instrukcją koprocesora; przyszłe wykonania tego samego kodu będą korzystać bezpośrednio z instrukcji koprocesora.
źródło