Jestem teraz bardzo ciekawy. Jestem programistą Python, a to pytanie mnie zaskoczyło: piszesz system operacyjny. Jak to działa? Trzeba go jakoś uruchomić, a ten sposób jest w innym systemie operacyjnym?
Jak aplikacja może działać bez systemu operacyjnego? Jak nakazać komputerowi uruchomienie, powiedzmy, C i wykonanie tych poleceń na ekranie, jeśli nie ma systemu operacyjnego do uruchomienia?
Czy ma to związek z jądrem UNIX? Jeśli tak, to co to jest jądro systemu Unix, czy ogólnie jądro?
Jestem pewien, że systemy operacyjne są bardziej skomplikowane, ale jak to działa?
kernel
operating-systems
Thor Correia
źródło
źródło
Odpowiedzi:
Istnieje wiele stron internetowych, które przechodzą proces rozruchu (np. Jak uruchomić komputery ). Krótko mówiąc, jest to wieloetapowy proces, który stopniowo buduje system, aż w końcu będzie mógł rozpocząć procesy systemu operacyjnego.
Zaczyna się od oprogramowania układowego na płycie głównej, które próbuje uruchomić procesor. Następnie ładuje BIOS, który jest jak mini system operacyjny, który uruchamia drugi sprzęt. Po zakończeniu wyszukuje urządzenie rozruchowe (dysk, dysk CD itp.), A po znalezieniu lokalizuje MBR (główny rekord rozruchowy), ładuje go do pamięci i wykonuje. Jest to mały fragment kodu, który następnie wie, jak zainicjować i uruchomić system operacyjny (lub inne programy ładujące, ponieważ sprawy stały się bardziej skomplikowane). W tym momencie rzeczy takie jak jądro zostaną załadowane i zaczną działać.
To niesamowite, że w ogóle działa!
źródło
System operacyjny „goły” nie działa w niczym. Obsługuje pełny zestaw instrukcji na maszynie fizycznej i ma dostęp do całej pamięci fizycznej, wszystkich rejestrów urządzeń i wszystkich instrukcji uprzywilejowanych, w tym tych, które sterują sprzętem obsługującym pamięć wirtualną.
(Jeśli system operacyjny działa na maszynie wirtualnej, może wydawać się , że znajduje się w takiej samej sytuacji jak powyżej. Różnica polega na tym, że niektóre rzeczy są emulowane lub w inny sposób obsługiwane przez hiperwizora, tj. Poziom, na którym działają maszyny wirtualne. .)
W każdym razie, chociaż system operacyjny może być zaimplementowany w (na przykład) C, nie będzie miał do dyspozycji wszystkich normalnych bibliotek C. W szczególności nie będzie mieć normalnych bibliotek „stdio”. Zamiast tego zaimplementuje (na przykład) sterownik urządzenia dyskowego, który pozwala na odczyt i zapis bloków dysku. Zaimplementuje system plików na wierzchu warstwy bloków dyskowych, a ponadto zaimplementuje wywołania systemowe, które biblioteki wykonawcze aplikacji użytkownika wykonują (na przykład) w celu tworzenia, odczytu i zapisu plików ... i tak dalej.
Musi to być specjalny rodzaj aplikacji (np. System operacyjny), który wie, jak bezpośrednio współpracować ze sprzętem we / wy itp.
Ty nie.
Aplikacja (która była argumentem napisanym w C) jest kompilowana i łączona na innym komputerze w celu uzyskania natywnego obrazu kodu. Następnie obraz jest zapisywany na dysku twardym w miejscu, w którym BIOS może go znaleźć. BIOS ładuje obraz do pamięci i wykonuje instrukcję, aby przejść do punktu wejścia aplikacji.
W aplikacji (zwykle) nie ma „uruchomionego C i wykonywania poleceń”, chyba że jest to w pełni funkcjonalny system operacyjny. W takim przypadku do systemu operacyjnego należy wdrożenie całej wymaganej infrastruktury, aby tak się stało. Bez magii. Tylko dużo kodu.
Odpowiedź Billa obejmuje ładowanie, czyli proces przejścia z wyłączonej maszyny na maszynę, na której działa normalny system operacyjny. Warto jednak zauważyć, że kiedy BIOS wykonuje swoje zadania, (zwykle) daje pełną kontrolę nad sprzętem głównemu systemowi operacyjnemu i nie odgrywa żadnej dalszej roli - aż do następnego uruchomienia systemu. Główny system operacyjny z pewnością nie działa w „BIOS” w konwencjonalnym sensie.
Tak.
Jądro UNIX jest rdzeniem systemu operacyjnego UNIX. Jest to część systemu UNIX, która wykonuje wszystkie opisane powyżej „elementy bez metalu”.
Idea „jądra” polega na tym, że próbujesz rozdzielić oprogramowanie systemowe na podstawowe rzeczy (które wymagają fizycznego dostępu do urządzenia, całej pamięci itp.) I inne niż podstawowe rzeczy. Jądro składa się z podstawowych rzeczy.
W rzeczywistości rozróżnienie między jądrem / rdzeniem a jądrem / rdzeniem jest bardziej skomplikowane. Dyskutowano o tym, co naprawdę należy do jądra, a co nie. (Wyszukaj na przykład mikro-jądro.)
źródło
The idea of a "kernel" is that you try to separate the system software into core stuff
Łatwo zapamiętać, zauważając, że terminkernel
pochodzi z niemieckiegoKern
, co oznacza rdzeń / jądro.Na początku procesor nie miał zasilania.
Mężczyzna powiedział: „niech będzie moc”, a procesor zaczął czytać z podanego adresu w pamięci i wykonać instrukcje tam zawarte. Potem następny i tak dalej, aż do końca mocy.
To był boot. Jego zadaniem było załadowanie innego oprogramowania, aby uzyskać dostęp do środowiska, w którym znajdowało się główne oprogramowanie, i załadowanie go.
Wreszcie przyjazny ekran zaprosił Cię do zalogowania.
źródło
0x7C00
dowolnejx86
kompatybilnej architektury i najpierw musi zostać wypełniony przez BIOS, który zwykle ładuje pierwszy sektor dowolnego urządzenia rozruchowego, które preferuje ... Ładna odpowiedź: -7Przepraszam za spóźnienie, ale opiszę to tak:
Płyta główna zyskuje moc.
Obwody czasowe uruchamiają się i stabilizują w razie potrzeby, wyłącznie na podstawie ich właściwości elektrycznych. Niektóre nowsze urządzenia mogą faktycznie używać bardzo ograniczonego mikroprocesora lub sekwencera.
- jkerian o 5:20 25 października
Zasilanie jest dostarczane do procesora i pamięci RAM.
Procesor ładuje (w oparciu o wewnętrzne okablowanie) dane z systemu BIOS. Na niektórych komputerach BIOS może być dublowany do pamięci RAM, a następnie uruchamiany stamtąd, ale jest to rzadkie IIRC.
-Micheal Steil, 17 błędów Microsoft popełnionych w systemie zabezpieczeń Xbox ( archiwum )
BIOS wykonuje połączenia z portami sprzętowymi i adresami używanymi przez płytę główną dla operacji dyskowych i innych sprzętowych operacji we / wy, a także obraca dyski i uruchamia resztę pamięci RAM.
Kod BIOS (poprzez ustawienia CMOS, przechowywany w sprzęcie) używa niskopoziomowych poleceń IDE lub SATA do odczytu sektora rozruchowego każdego dysku, w kolejności określonej przez CMOS lub zastąpienie menu przez użytkownika.
Pierwszy dysk z sektorem rozruchowym zostaje uruchomiony. Ten sektor rozruchowy to zestaw
NTLDR
, który zawiera instrukcje ładowania większej ilości danych z dysku, ładowania większych , późniejszych etapówGRUB
itp.Wreszcie kod maszynowy systemu operacyjnego jest wykonywany przez program ładujący, bezpośrednio lub pośrednio poprzez ładowanie łańcuchowe ładujące sektor rozruchowy z lokalizacji alternatywnej lub przesuniętej.
Wtedy panika zaprzyjaźnia się z jądrem, duszący się pingwin lub dysk zatrzymuje się z powodu wypadku głowy. =) W alternatywnym scenariuszu jądro konfiguruje tabele procesów, struktury w pamięci i montuje dyski, ładuje sterowniki, moduły oraz GUI lub zestaw usług (jeśli jest na serwerze). Następnie programy są wykonywane, gdy ich nagłówki są odczytywane, a ich zestaw jest wprowadzany do pamięci i odpowiednio mapowany.
źródło
Istnieje wiele dobrych odpowiedzi, ale chciałem to dodać: Wspomniałeś, że pochodzisz z języka Python. Python jest językiem interpretowanym (lub „interpolowanym” lub dowolnym innym, przynajmniej w typowych przypadkach użycia CPython). Oznacza to, że masz inne oprogramowanie (interpreter języka Python), które patrzy na źródło i wykonuje je w jakiś sposób. Jest to dobry model i pozwala na całkiem niezłe języki wysokiego poziomu, dobrze oderwane od rzeczywistego sprzętu. Minusem jest to, że zawsze potrzebujesz najpierw tego oprogramowania tłumacza.
Takie oprogramowanie interpretera jest zwykle napisane w języku kompilującym się z kodem maszynowym, na przykład C lub C ++. Kod maszynowy jest tym, co CPU może obsłużyć. Procesor może jedynie odczytać niektóre bajty z pamięci iw zależności od wartości bajtów rozpocząć określoną operację. Tak więc jedna bajtowa sekwencja to polecenie, aby załadować niektóre dane z pamięci do rejestru, kolejna sekwencja, aby dodać dwie wartości, inna, aby zapisać wartość z rejestru z powrotem do pamięci głównej i wkrótce (rejestr jest specjalnym obszarem pamięci, który jest częścią procesora, w którym może działać najlepiej), większość tych poleceń jest dość niska na tym poziomie. Dla tych instrukcji kodu maszynowego czytelny dla człowieka jest kod asemblera. Zasadniczo ten kod maszynowy jest przechowywany w plikach .exe lub.com w systemie Windows lub w plikach binarnych Linux / Unix.
Teraz, gdy komputer jest uruchamiany, jest głupi, ma jednak pewne okablowanie, które czyta takie instrukcje kodu maszynowego. Na komputerze PC zwykle (obecnie) jest to układ EEPROM na płycie głównej zawierający BIOS (system wyjściowy BasicInput), ten system nie może wiele zrobić, może ułatwić dostęp do niektórych urządzeń itp., A następnie wykonać kluczową operację: przejdź do uruchom i skopiuj kilka pierwszych bajtów (czyli główny rekord rozruchowy, MBR) do pamięci, a następnie powiedz procesorowi „tutaj, masz program”, a następnie procesor potraktuje te bajty jako kod maszynowy i uruchomi go. Zazwyczaj jest to moduł ładujący system operacyjny, który załaduje jądro z niektórymi parametrami, a następnie przekaże sterowanie temu jądrem, które następnie załaduje cały jego sterownik, aby uzyskać dostęp do całego sprzętu, załadować jakiś program pulpitu lub powłoki lub cokolwiek innego i pozwolić użytkownikowi na zalogowanie się i użyj systemu.
źródło
Pytasz „Jak aplikacja może działać bez systemu operacyjnego”. Łatwa odpowiedź brzmi: „OS nie jest aplikacją”. Chociaż system operacyjny można utworzyć za pomocą tych samych narzędzi co aplikacja i zbudować z tego samego surowca, nie są one tym samym. System operacyjny nie musi grać według tych samych zasad co aplikacja.
OTOH, rzeczywisty sprzęt i oprogramowanie sprzętowe można traktować jako „system operacyjny”, w którym działa „aplikacja” systemu operacyjnego. Sprzęt jest bardzo prostym systemem operacyjnym - umie uruchamiać instrukcje zapisane w kodzie maszynowym i wie, że po uruchomieniu powinien sprawdzić bardzo konkretny adres pamięci dla swojej pierwszej instrukcji. Tak więc uruchamia się, a następnie natychmiast uruchamia tę pierwszą instrukcję, a następnie drugą i tak dalej.
Tak więc system operacyjny to po prostu kod maszynowy, który istnieje w znanej lokalizacji i który może bezpośrednio wchodzić w interakcje ze sprzętem.
źródło
Odpowiedź na twoje pytanie wymaga wiedzy o tym, jak wygląda kod natywny (dla procesora) i jak jest interpretowany przez procesor.
Zwykle cały proces kompilacji opiera się na tłumaczeniu rzeczy, które piszesz w C, Pascalu, a nawet Pythonie (za pomocą pypy) i C # na rzeczy rozumiane przez procesor, tj. Proste instrukcje, takie jak „przechowuj coś pod [adres pamięci]”, „dodaj liczby przechowywane pod rejestrem eax i ebx ”,„ funkcja wywołania foo ”,„ porównaj eax do 10 ”. Te instrukcje, wykonywane jeden po drugim, robią rzeczy, które chcesz zrobić ze swoim kodem.
Pomyśl o tym: tak naprawdę nie potrzebujesz systemu operacyjnego, aby wykonać ten natywny kod! Wystarczy załadować ten kod do pamięci i powiedzieć procesorowi, że tam jest, i chcesz go wykonać. Ale nie przejmuj się tym zbytnio. Właśnie o to BIOS powinien się martwić - ładuje twój kod (tylko jeden i jeden sektor), zaraz po uruchomieniu CPU, pod fizycznym adresem 0x7C00. Następnie procesor zaczyna wykonywać ten jeden sektor (512 B) kodu. I możesz robić, co sobie wyobrażasz! Bez żadnego wsparcia ze strony systemu operacyjnego. To dlatego, że TY jesteś systemem operacyjnym. Fajne hę? Bez standardowej biblioteki, bez doładowania, bez Pythona, bez programów, bez sterowników! Musisz napisać wszystko sam.
A jak komunikujesz się ze sprzętem? Masz dwie możliwości:
Teraz pytasz, co to jest jądro. Krótko mówiąc, jądro to wszystko, czego nie widzisz i czego bezpośrednio nie doświadczasz. Zarządza, wraz ze sterownikami, wszystkim, od klawiatury po prawie każdy element sprzętu w komputerze. Komunikujesz się z nim za pomocą powłoki graficznej lub terminala. Lub funkcje w twoim kodzie, teraz wykonane, na szczęście, z obsługą systemu operacyjnego.
Dla lepszego zrozumienia mogę dać ci jedną radę: spróbuj napisać własny system operacyjny. Nawet jeśli ma napisać „Witaj świecie” na ekranie.
źródło
Istnieją pewne różnice w działaniu systemu operacyjnego, które są bardzo zależne od systemu. Aby system był użyteczny, musi mieć pewne przewidywalne zachowanie podczas uruchamiania, takie jak „rozpocznij wykonywanie pod adresem X”. W przypadku systemów, które mają nieulotną pamięć (taką jak pamięć Flash) zamapowaną w przestrzeni programów, jest to dość łatwe, ponieważ wystarczy upewnić się, że umieścisz kod startowy w odpowiednim miejscu w przestrzeni programu procesora. Jest to niezwykle powszechne w przypadku mikrokontrolerów. Niektóre systemy muszą pobrać swoje programy startowe z innej lokalizacji przed ich uruchomieniem. W tych systemach niektóre operacje będą na stałe (lub prawie na stałe). Istnieje kilka procesorów, które pobierają kod startowy przez i2c z innego układu,
Systemy korzystające z rodziny procesorów x86 zwykle używają wieloetapowego procesu rozruchu, który jest dość złożony ze względu na jego ewolucję i problemy ze zgodnością wsteczną. System wykonuje oprogramowanie wewnętrzne (zwane BIOS - Basic Input / Output System lub podobne), które znajduje się w pewnej nieulotnej pamięci na płycie głównej. Czasami część lub całość tego oprogramowania układowego jest kopiowana (przenoszona) do pamięci RAM, aby przyspieszyć jej działanie. Ten kod został napisany ze świadomością, jaki sprzęt będzie obecny i nadaje się do rozruchu.
Startowe oprogramowanie układowe jest zwykle pisane z założeniami o tym, jaki sprzęt będzie obecny w systemie. Wiele lat temu na maszynie 286 prawdopodobnie istniałoby założenie, że będzie istniał kontroler napędu dyskietek pod adresem We / Wy X i ładowałby sektor 0 do określonego miejsca w pamięci, gdyby otrzymał pewien zestaw poleceń (i kod w sektorze 0 wie, jak korzystać z własnych funkcji BIOS-u, aby załadować więcej kodu, a następnie załadować wystarczającą ilość kodu, aby był to system operacyjny) Na mikrokontrolerze może być założenie, że istnieje port szeregowy działający z pewnymi ustawieniami, że powinien czekać na polecenia (w celu zaktualizowania bardziej skomplikowanego oprogramowania układowego) przez X czasu przed kontynuowaniem procesu rozruchu.
Dokładny proces uruchamiania danego systemu nie jest dla Ciebie tak ważny, jak świadomość, że różni się on w różnych systemach, ale także, że wszystkie mają ze sobą wiele wspólnego. Często w kodzie startowym (bootstrapping), gdy trzeba wykonać operacje we / wy, urządzenia we / wy są odpytywane, a nie polegają na przerwaniach. Wynika to z faktu, że przerwania są złożone, używaj pamięci RAM stosu (która może nie być jeszcze w pełni skonfigurowana) i nie musisz się martwić blokowaniem innych operacji, gdy jesteś jedyną operacją.
Po pierwszym załadowaniu jądro systemu operacyjnego (jądro jest główną częścią większości systemów operacyjnych) początkowo będzie działać podobnie jak oprogramowanie układowe. Będzie trzeba go zaprogramować ze znajomością lub odkryć obecny sprzęt, ustawić pamięć RAM jako przestrzeń stosu, wykonać różne testy, skonfigurować różne struktury danych, ewentualnie odkryć i zamontować system plików, a następnie prawdopodobnie uruchomić program, który jest bardziej jak programy, do których przywykłeś pisać (program, który polega na obecności systemu operacyjnego).
Kod systemu operacyjnego jest zwykle zapisywany w kombinacji C i asemblera. Pierwszy kod dla jądra systemu operacyjnego jest prawdopodobnie zawsze w asemblerze i wykonuje takie czynności, jak konfigurowanie stosu, na którym opiera się kod C, a następnie wywołuje funkcję C. Będą tam również inne odręczne asemblery, ponieważ niektóre operacje, które musi wykonać system operacyjny, często nie są wyrażalne w języku C (takie jak przełączanie kontekstu / zamiana stosów). Często należy przekazać specjalne kompilatory do kompilatora C, aby poinformować go, że nie należy polegać na standardowych bibliotekach używanych przez większość programów w języku C i nie oczekiwać, że istnieje
int main(int argc, char *argv[])
w programie. Dodatkowo należy użyć specjalnych opcji linkera, których większość programistów aplikacji nigdy nie używa. Może to spowodować, że program jądra będzie oczekiwał na załadowanie pod określonym adresem lub skonfiguruje rzeczy, które wyglądają tak, jakby w niektórych lokalizacjach były zmienne zewnętrzne, chociaż te zmienne nigdy nie zostały zadeklarowane w żadnym kodzie C (jest to przydatne w przypadku operacji we / wy mapowanych w pamięci lub inne specjalne lokalizacje pamięci).Cała operacja na początku wydaje się magią, ale po przyjrzeniu się jej i zrozumieniu, magia staje się tylko zestawem programów, których wdrożenie wymaga dużo więcej planowania i wiedzy systemowej. Debugowanie ich wymaga jednak magii.
źródło
Aby zrozumieć, jak działają systemy operacyjne, pomocne może być podzielenie ich na dwie kategorie: te, które po prostu dostarczają usługi aplikacjom na żądanie, oraz te, które używają funkcji sprzętowych w CPU, aby uniemożliwić aplikacjom wykonywanie czynności, których nie powinny. MS-DOS był w dawnym stylu; wszystkie wersje systemu Windows od wersji 3.0 były tym drugim stylem (przynajmniej gdy działa coś potężniejszego niż 8086).
Oryginalny komputer IBM z systemem PC-DOS lub MS-DOS byłby przykładem dawnego stylu „OS”. Gdyby aplikacja chciała wyświetlić postać na ekranie, byłoby na to kilka sposobów. Może wywołać procedurę, która prosi MS-DOS o wysłanie go na „standardowe wyjście”. Gdyby to zrobił, MS-DOS sprawdziłby, czy dane wyjściowe były przekierowywane, a jeśli nie, wywołałby procedurę przechowywaną w pamięci ROM (w zbiorze procedur nazywanych przez system Basic Input / Output System), która wyświetli znak przesuń kursor i przesuń kursor („napisz teletype”) Ta procedura BIOS zapisuje następnie parę bajtów gdzieś w zakresie od 0xB800: 0 do 0xB800: 3999; sprzęt karty graficznej Color wielokrotnie pobiera pary bajtów w tym zakresie, używając pierwszego bajtu z każdej pary, aby wybrać kształt znaku, a drugiego, aby wybrać kolory pierwszego planu i tła. Bajty są pobierane i przetwarzane na sygnały czerwony, zielony i niebieski w sekwencji zapewniającej czytelny tekst.
Programy na komputerze IBM mogą wyświetlać tekst przy użyciu procedury „standardowego wyjścia” DOS lub przy użyciu procedury BIOS „zapisu typu teletekstu” lub zapisując go bezpośrednio w pamięci wyświetlacza. Wiele programów, które musiały wyświetlać dużo tekstu, szybko wybrało to drugie podejście, ponieważ może być dosłownie setki razy szybsze niż korzystanie z procedur DOS. Nie było tak, ponieważ procedury DOS i BIOS były wyjątkowo nieefektywne; chyba że wyświetlacz był wygaszony, można go było zapisać tylko w określonych momentach. Procedura BIOS wyprowadzania znaku została zaprojektowana tak, aby można go było wywołać w dowolnym momencie; każde żądanie musiało więc zacząć od nowa, czekając na odpowiedni czas na wykonanie operacji zapisu. Natomiast kod aplikacji, który wiedział, co powinien zrobić, mógł zorganizować się wokół dostępnych możliwości napisania wyświetlacza.
Kluczową kwestią jest to, że podczas gdy DOS i BIOS zapewniały sposób wyświetlania tekstu na wyświetlaczu, nie było nic szczególnie „magicznego” w takich umiejętnościach. Aplikacja, która chciała napisać tekst na wyświetlaczu, mogłaby to zrobić równie skutecznie, przynajmniej jeśli sprzęt wyświetlacza działałby zgodnie z oczekiwaniami aplikacji (jeśli ktoś zainstalował Monochromatyczny adapter ekranu, który był podobny do CGA, ale miał pamięć znaków znajdujący się pod 0xB000: 0000-0xB000: 3999), BIOS automatycznie wyprowadzał tam znaki; aplikacja, która została zaprogramowana do pracy z MDA lub CGA, mogłaby to również zrobić, ale aplikacja, która została zaprogramowana tylko dla CGA, byłaby całkowicie bezużyteczna na MDA.
W nowszych systemach sytuacja wygląda nieco inaczej. Procesory mają różne tryby „uprawnień”. Zaczynają w najbardziej uprzywilejowanym trybie, w którym kod może robić wszystko, co chce. Następnie mogą przejść do trybu ograniczonego, w którym dostępne są tylko wybrane zakresy pamięci lub urządzenia we / wy. Kod nie może przejść bezpośrednio z trybu ograniczonego z powrotem do trybu uprzywilejowanego, ale procesor zdefiniował punkty wejścia w trybie uprzywilejowanym, a kod trybu ograniczonego może poprosić procesor o uruchomienie kodu w jednym z tych punktów wejścia w trybie uprzywilejowanym. Ponadto istnieją punkty wejścia w trybie uprzywilejowanym związane z wieloma operacjami, które byłyby zabronione w trybie ograniczonym. Załóżmy na przykład, że ktoś chciał uruchomić wiele aplikacji MS-DOS jednocześnie, każda z nich ma własny ekran. Gdyby aplikacje mogły pisać bezpośrednio do kontrolera wyświetlania pod 0xB800: 0, nie byłoby sposobu, aby jedna aplikacja nie nadpisała ekranu innej aplikacji. Z drugiej strony, system operacyjny może uruchomić aplikację w trybie ograniczonym i przechwycić dowolny dostęp do pamięci wyświetlacza; jeśli odkryje, że aplikacja, która miała znajdować się w „tle”, próbuje zapisać 0xB800: 160, może zapisać dane w pewnej pamięci, którą odłożyła jako bufor ekranu aplikacji w tle. Jeśli ta aplikacja zostanie później przełączona na pierwszy plan, bufor można następnie skopiować na prawdziwy ekran. system operacyjny może uruchomić aplikację w trybie ograniczonym i zablokować dowolny dostęp do pamięci ekranu; jeśli odkryje, że aplikacja, która miała znajdować się w „tle”, próbuje zapisać 0xB800: 160, może zapisać dane w pewnej pamięci, którą odłożyła jako bufor ekranu aplikacji w tle. Jeśli ta aplikacja zostanie później przełączona na pierwszy plan, bufor można następnie skopiować na prawdziwy ekran. system operacyjny może uruchomić aplikację w trybie ograniczonym i zablokować dowolny dostęp do pamięci ekranu; jeśli odkryje, że aplikacja, która miała znajdować się w „tle”, próbuje zapisać 0xB800: 160, może zapisać dane w pewnej pamięci, którą odłożyła jako bufor ekranu aplikacji w tle. Jeśli ta aplikacja zostanie później przełączona na pierwszy plan, bufor można następnie skopiować na prawdziwy ekran.
Najważniejsze rzeczy do zapamiętania to (1), chociaż często przydatne jest posiadanie standardowego zestawu procedur do wykonywania różnych standardowych usług, takich jak wyświetlanie tekstu, nie robią nic, czego nie mogła zrobić aplikacja działająca w „trybie uprzywilejowanym” jeśli został odpowiednio zaprogramowany do obsługi zainstalowanego sprzętu; (2) chociaż większość działających dzisiaj aplikacji byłaby uniemożliwiana przez ich system operacyjny bezpośrednio wykonując takie operacje wejścia / wyjścia, program, który uruchamia się w trybie uprzywilejowanym, robi wszystko, co chce, i może skonfigurować dowolne reguły dla trybu ograniczonego programy.
źródło
Jak powiedział Stephen C., nie chodzi tylko o uruchomienie systemu operacyjnego, ale także o to, jak działa, współpracuje ze sprzętem i oprogramowaniem na nim.
Dodam tylko do jego odpowiedzi, że możesz rzucić okiem na „Elementy systemów obliczeniowych” . Jest to książka i niektóre narzędzia, które wyjaśniają interakcje komputera, systemu operacyjnego i kompilatorów. Unikalną cechą tego narzędzia jest to, że zapewnia narzędzia do bardzo szybkiego opracowania własnego systemu operacyjnego w symulowanym środowisku, ignorując wiele szczegółów wymaganych w prawdziwym systemie, dzięki czemu można zrozumieć pojęcia . Świetnie sobie radzi, pozwalając zobaczyć las zamiast drzew.
Jeśli chcesz dowiedzieć się więcej na temat interakcji systemu operacyjnego ze sprzętem, sprawdź Minix .
źródło
Twoja aplikacja działa w systemie operacyjnym. Ten system operacyjny zapewnia obsługę aplikacji, np. Otwieranie pliku i zapisywanie w nim bajtów. Usługi te są zwykle świadczone za pośrednictwem wywołań systemowych.
System operacyjny działa na sprzęcie. Sprzęt zapewnia usługi dla systemu operacyjnego, takie jak ustawianie szybkości transmisji portu szeregowego i zapisywanie w nim bajtów. Usługi te są zazwyczaj świadczone za pośrednictwem rejestrów mapowanych w pamięci lub portów I / O.
Aby podać bardzo uproszczony przykład tego, jak to działa:
Twoja aplikacja mówi systemowi operacyjnemu, aby napisał coś do pliku. Do aplikacji system operacyjny udostępnia pojęcia takie jak pliki i katalogi.
Na sprzęcie te pojęcia nie istnieją. Sprzęt zapewnia pojęcia takie jak dyski podzielone na stałe bloki 512 bajtów. System operacyjny decyduje, których bloków użyć dla pliku, a także niektórych innych bloków metadanych, takich jak nazwa pliku, rozmiar i lokalizacja na dysku. Następnie mówi sprzętowi: zapisz te 512 bajtów do sektora o tym numerze na dysku o tym numerze; zapisz te 512 512 bajtów do sektora o tym innym numerze na dysku o tym samym numerze; i tak dalej.
Sposób, w jaki system operacyjny mówi sprzętowi, że jest bardzo zróżnicowany. Jedną z funkcji systemu operacyjnego jest ochrona aplikacji przed tymi różnicami. Na przykład na dysku na jednym sprzęcie system operacyjny musiałby zapisać numer dysku i sektora na porcie we / wy, a następnie zapisać bajty jeden po drugim na osobnym porcie we / wy. Na innym sprzęcie system operacyjny musiałby skopiować całe 512 bajtów sektora do obszaru pamięci, zapisać lokalizację tego obszaru pamięci w specjalnej lokalizacji pamięci oraz zapisać dysk i numer sektora na jeszcze innym specjalna lokalizacja pamięci.
Dzisiejszy sprzęt wysokiej klasy jest niezwykle skomplikowany. Instrukcje zawierające wszystkie szczegóły dotyczące programowania są odbojnikami z tysiącami stron; na przykład najnowsza instrukcja obsługi procesora Intel obejmuje siedem tomów, w sumie ponad 4000 stron - i to tylko dla procesora. Większość innych komponentów ujawnia bloki pamięci lub porty I / O, które system operacyjny może nakazać procesorowi mapować na adresy w przestrzeni adresowej. Kilka z tych komponentów ujawnia jeszcze więcej rzeczy za kilkoma portami I / O lub adresami pamięci; na przykład RTC (zegar czasu rzeczywistego, komponent, który utrzymuje czas komputera, gdy jest on wyłączony) odsłania kilkaset bajtów pamięci za parą portów I / O, i jest to bardzo prosty komponent pochodzący z oryginalny PC / AT. Rzeczy takie jak dyski twarde mają całe oddzielne procesory, z którym system operacyjny rozmawia za pomocą standardowych poleceń. Procesory graficzne są jeszcze bardziej skomplikowane.
Kilka osób w powyższych komentarzach zasugerowało Arduino. Zgadzam się z nimi, o wiele łatwiej jest to zrozumieć - ATmega328, który robi wszystko w Arduino Uno, z wyjątkiem odsłonięcia złącza USB jako portu szeregowego, ma instrukcję z zaledwie kilkuset stron. Na Arduino działasz bezpośrednio na sprzęcie, bez żadnego systemu operacyjnego pomiędzy; tylko kilka małych procedur bibliotecznych, których nie musisz używać, jeśli nie chcesz.
źródło
Przykłady możliwe do uruchomienia
Z technicznego punktu widzenia program działający bez systemu operacyjnego to system operacyjny. Zobaczmy więc, jak stworzyć i uruchomić kilka maleńkich systemów operacyjnych witaj na świecie.
Kod wszystkich poniższych przykładów znajduje się w tym repozytorium GitHub .
Sektor rozruchowy
Na x86 najprostszym i najniższym poziomem, co możesz zrobić, jest utworzenie głównego sektora rozruchowego (MBR) , który jest rodzajem sektora rozruchowego , a następnie zainstalowanie go na dysku.
Tutaj tworzymy jedną za pomocą jednego
printf
połączenia:Wynik:
Testowane na Ubuntu 18.04, QEMU 2.11.1.
main.img
zawiera następujące elementy:\364
in octal ==0xf4
in hex: kodowaniehlt
instrukcji, która mówi CPU, aby przestał działać.Dlatego nasz program nic nie zrobi: zacznij i zatrzymaj.
Używamy ósemki, ponieważ
\x
liczby szesnastkowe nie są określone przez POSIX.Możemy łatwo uzyskać to kodowanie za pomocą:
ale
0xf4
kodowanie jest również udokumentowane w podręczniku Intela.%509s
wyprodukuj 509 miejsc. Konieczne do wypełnienia pliku do bajtu 510.\125\252
in octal ==,0x55
po którym następuje0xaa
: magiczne bajty wymagane przez sprzęt. Muszą to być bajty 511 i 512.Jeśli nie jest obecny, sprzęt nie będzie traktował tego jako dysku rozruchowego.
Pamiętaj, że nawet bez robienia kilku znaków jest już wydrukowanych na ekranie. Są one drukowane przez oprogramowanie układowe i służą do identyfikacji systemu.
Uruchom na prawdziwym sprzęcie
Emulatory są fajne, ale sprzęt to prawdziwa okazja.
Zauważ jednak, że jest to niebezpieczne i możesz pomyłkowo wyczyścić dysk: rób to tylko na starych komputerach, które nie zawierają krytycznych danych! Lub jeszcze lepiej, devboardy takie jak Raspberry Pi, patrz przykład ARM poniżej.
W przypadku typowego laptopa musisz zrobić coś takiego:
Wypal obraz na pamięci USB (zniszczy twoje dane!):
podłącz USB do komputera
włącz to
każ mu uruchomić z USB.
Oznacza to, że oprogramowanie układowe wybiera USB przed dyskiem twardym.
Jeśli nie jest to domyślne zachowanie twojego komputera, po włączeniu naciskaj Enter, F12, ESC lub inne takie dziwne klawisze, aż pojawi się menu rozruchu, w którym możesz wybrać rozruch z USB.
Często w tych menu można skonfigurować kolejność wyszukiwania.
Na przykład na moim starym Lenovo Thinkpad T430, UEFI BIOS 1.16 widzę:
Witaj świecie
Teraz, gdy stworzyliśmy minimalny program, przejdźmy do cześć świata.
Oczywiste pytanie brzmi: jak zrobić IO? Kilka opcji:
port szeregowy . Jest to bardzo prosty znormalizowany protokół, który wysyła i pobiera znaki z terminala hosta.
Źródło .
Niestety nie jest narażony na większość współczesnych laptopów, ale jest powszechną drogą do tworzenia płyt deweloperskich, patrz przykłady ARM poniżej.
To naprawdę szkoda, ponieważ takie interfejsy są naprawdę przydatne na przykład do debugowania jądra Linuksa .
użyj funkcji debugowania układów. ARM na przykład nazywa ich semihosting . Na prawdziwym sprzęcie wymaga dodatkowej obsługi sprzętu i oprogramowania, ale na emulatorach może być bezpłatną wygodną opcją. Przykład .
Tutaj zrobimy przykład BIOS-u, ponieważ jest prostszy na x86. Pamiętaj jednak, że nie jest to najbardziej niezawodna metoda.
sieć elektryczna
link.ld
Złóż i połącz z:
Wynik:
Testowane na: Lenovo Thinkpad T430, UEFI BIOS 1.16. Dysk wygenerowany na hoście Ubuntu 18.04.
Oprócz standardowych instrukcji montażu dla użytkownika, mamy:
.code16
: informuje GAS, aby wyprowadził 16-bitowy kodcli
: wyłącz przerwania programowe. Mogą one spowodować, że procesor zacznie ponownie działać pohlt
int $0x10
: wykonuje połączenie BIOS. To właśnie drukuje znaki jeden po drugim.Ważne flagi linków to:
--oformat binary
: wypisuje nieprzetworzony kod binarnego zestawu, nie wypaczaj go w pliku ELF, jak ma to miejsce w przypadku zwykłych plików wykonywalnych dla użytkownikaUżyj C zamiast montażu
Ponieważ C kompiluje się w asemblerze, używanie C bez standardowej biblioteki jest dość proste, w zasadzie potrzebujesz:
main
, w szczególności:DO ZROBIENIA: link, więc przykład x86 na GitHub. Oto ARM, który stworzyłem .
Sprawa staje się jeszcze przyjemniejsza, jeśli chcesz korzystać ze standardowej biblioteki, ponieważ nie mamy jądra Linux, które implementuje większość standardowych funkcji biblioteki C za pośrednictwem POSIX .
Kilka możliwości, bez przechodzenia na w pełni funkcjonalny system operacyjny, taki jak Linux, to:
Newlib
Szczegółowy przykład na stronie : https://electronics.stackexchange.com/questions/223929/c-standard-libraries-on-bare-metal/223931
W Newlib musisz samodzielnie wdrożyć syscalls, ale otrzymujesz bardzo minimalny system i bardzo łatwo jest je zaimplementować.
Na przykład, można przekierować
printf
do układów UART lub ARM, lub realizowaćexit()
z semihosting .wbudowane systemy operacyjne, takie jak FreeRTOS i Zephyr .
Takie systemy operacyjne zazwyczaj umożliwiają wyłączenie planowania wyprzedzającego, co daje pełną kontrolę nad czasem działania programu.
Można je postrzegać jako rodzaj wstępnie wdrożonego Newlib.
RAMIĘ
W ARM ogólne pomysły są takie same. Przesłałem:
kilka prostych przykładów bezmetalowych QEMU tutaj na GitHub . Prompt.c przykład bierze sygnału wejściowego z terminala głównego i oddaje moc przez cały symulowanym UART:
Zobacz także: https://stackoverflow.com/questions/38914019/how-to-make-bare-metal-arm-programs-and-run-them-on-qemu/50981397#50981397
w pełni zautomatyzowana konfiguracja migacza Raspberry Pi pod adresem : https://github.com/cirosantilli/raspberry-pi-bare-metal-blinker
Zobacz także: https://stackoverflow.com/questions/29837892/how-to-run-ac-program-with-no-os-on-the-raspberry-pi/40063032#40063032
W przypadku Raspberry Pi https://github.com/dwelch67/raspberrypi wygląda jak najpopularniejszy dostępny obecnie samouczek.
Niektóre różnice w stosunku do x86 obejmują:
IO odbywa się poprzez bezpośrednie pisanie na magiczne adresy, nie ma instrukcji
in
iout
instrukcji.Nazywa się to We / Wy mapowanym na pamięć .
w przypadku niektórych prawdziwych urządzeń, takich jak Raspberry Pi, możesz samodzielnie dodać oprogramowanie układowe (BIOS) do obrazu dysku.
To dobrze, ponieważ sprawia, że aktualizacja oprogramowania układowego jest bardziej przejrzysta.
Oprogramowanie układowe
W rzeczywistości sektor rozruchowy nie jest pierwszym oprogramowaniem działającym na procesorze systemu.
Najpierw działa tak zwane oprogramowanie układowe , które jest oprogramowaniem:
Dobrze znane oprogramowanie wewnętrzne obejmuje:
Oprogramowanie wewnętrzne działa na przykład:
zapętlaj każdy dysk twardy, USB, sieć itp., aż znajdziesz coś rozruchowego.
Kiedy uruchamiamy QEMU,
-hda
mówi, żemain.img
jest to dysk twardy podłączony do sprzętu ihda
jest pierwszą próbą i jest używana.załaduj pierwsze 512 bajtów na adres pamięci RAM
0x7c00
, umieść tam RIP procesora i pozwól mu działaćpokaż na ekranie takie rzeczy jak menu startowe lub wywołania drukowania BIOS-u
Oprogramowanie układowe oferuje funkcje podobne do systemu operacyjnego, od których zależy większość systemów operacyjnych. Np. Podzestaw Python został przeniesiony do działania w systemie BIOS / UEFI: https://www.youtube.com/watch?v=bYQ_lq5dcvM
Można argumentować, że oprogramowanie układowe jest nierozróżnialne od systemów operacyjnych i że oprogramowanie układowe jest jedynym „prawdziwym” programowaniem bez systemu operacyjnego.
Jak to ujął to deweloper CoreOS :
Stan początkowy po BIOS
Podobnie jak wiele rzeczy w sprzęcie, standaryzacja jest słaba, a jedną z rzeczy, na których nie należy polegać, jest stan początkowy rejestrów, gdy kod zaczyna działać po BIOS.
Więc zrób sobie przysługę i użyj kodu inicjalizacji, takiego jak: https://stackoverflow.com/a/32509555/895245
Rejestruje się
%ds
i%es
ma ważne skutki uboczne, więc powinieneś je wyzerować, nawet jeśli nie używasz ich wyraźnie.Zauważ, że niektóre emulatory są ładniejsze niż prawdziwy sprzęt i zapewniają dobry stan początkowy. Potem, gdy idziesz na prawdziwym sprzęcie, wszystko się psuje.
GNU GRUB Multiboot
Sektory rozruchowe są proste, ale nie są zbyt wygodne:
Z tych powodów GNU GRUB stworzył wygodniejszy format pliku o nazwie multiboot.
Minimalny przykład działania: https://github.com/cirosantilli/x86-bare-metal-examples/tree/d217b180be4220a0b4a453f31275d38e697a99e0/multiboot/hello-world
Używam go również w moim repozytorium przykładów GitHub, aby móc z łatwością uruchamiać wszystkie przykłady na prawdziwym sprzęcie bez milionowego spalania USB. Na QEMU wygląda to tak:
Jeśli przygotujesz system operacyjny jako plik z wieloma uruchomieniami, GRUB będzie mógł go znaleźć w zwykłym systemie plików.
To właśnie robi większość dystrybucji, umieszczając obrazy systemu operacyjnego
/boot
.Pliki Multiboot są w zasadzie plikiem ELF ze specjalnym nagłówkiem. Są one określone przez GRUB pod adresem : https://www.gnu.org/software/grub/manual/multiboot/multiboot.html
Możesz zmienić plik z wieloma uruchomieniami na dysk rozruchowy za pomocą
grub-mkrescue
.El Torito
Format, który można wypalić na płytach CD: https://en.wikipedia.org/wiki/El_Torito_%28CD-ROM_standard%29
Możliwe jest również utworzenie obrazu hybrydowego działającego na ISO lub USB. Można to zrobić za pomocą
grub-mkrescue
( przykład ), a także przymake isoimage
użyciu jądra Linuxisohybrid
.Zasoby
źródło