Wspominasz, jak jeśli kod jest specyficzny dla procesora, dlaczego musi być specyficzny również dla systemu operacyjnego. W rzeczywistości jest to bardziej interesujące pytanie, na które wiele odpowiedzi tutaj zakładało.
Model bezpieczeństwa procesora
Pierwszy program uruchamiany na większości architektur CPU działa wewnątrz tak zwanego pierścienia wewnętrznego lub pierścienia 0 . Sposób, w jaki konkretny łuk procesora implementuje pierścienie, jest różny, ale oznacza to, że prawie każdy nowoczesny procesor ma co najmniej 2 tryby działania, jeden uprzywilejowany i uruchamia kod „bare metal”, który może wykonać dowolną legalną operację, którą może wykonać procesor, a drugi jest niezaufany i uruchamia chroniony kod, który może wykonywać tylko zdefiniowany bezpieczny zestaw funkcji. Niektóre procesory mają jednak znacznie wyższą ziarnistość, a do bezpiecznego korzystania z maszyn wirtualnych potrzeba co najmniej 1 lub 2 dodatkowych pierścieni (często oznaczonych liczbami ujemnymi), jednak wykracza to poza zakres tej odpowiedzi.
Gdzie wchodzi system operacyjny
Wczesne systemy operacyjne jednozadaniowe
W bardzo wczesnym DOS i innych wczesnych systemach opartych na pojedynczym zadaniu cały kod był uruchamiany w pierścieniu wewnętrznym, każdy program, który kiedykolwiek uruchomiłeś, miał pełną moc nad całym komputerem i mógł zrobić dosłownie wszystko, jeśli źle się zachował, w tym skasował wszystkie dane lub nawet spowodował uszkodzenie sprzętu w kilku ekstremalnych przypadkach, takich jak ustawienie nieprawidłowych trybów wyświetlania na bardzo starych ekranach, co gorsza, może to być spowodowane błędnym kodem bez złośliwości.
W rzeczywistości ten kod był w dużej mierze niezależny od systemu operacyjnego, o ile posiadano moduł ładujący zdolny do załadowania programu do pamięci (dość prosty dla wczesnych formatów binarnych), a kod nie polegał na żadnych sterownikach, wdrażając sam dostęp sprzętowy, pod którym powinien działać dowolny system operacyjny, pod warunkiem, że działa on w pierścieniu 0. Uwaga, bardzo prosty system taki jak ten zwykle nazywany jest monitorem, jeśli jest po prostu używany do uruchamiania innych programów i nie oferuje żadnych dodatkowych funkcji.
Nowoczesne wielozadaniowe systemy operacyjne
Nowocześniejsze systemy operacyjne, w tym UNIX , wersje Windows zaczynające się od NT i różne inne niejasne systemy operacyjne postanowiły poprawić tę sytuację, użytkownicy chcieli dodatkowych funkcji, takich jak wielozadaniowość, aby mogli uruchomić więcej niż jedną aplikację na raz i ochronę, więc błąd ( lub złośliwy kod) w aplikacji nie może już powodować nieograniczonego uszkodzenia urządzenia i danych.
Dokonano tego przy użyciu pierścieni wspomnianych powyżej, system operacyjny zająłby jedyne miejsce w pierścieniu 0, a aplikacje działałyby w zewnętrznych niezaufanych pierścieniach, zdolnych do wykonywania ograniczonego zestawu operacji dozwolonych przez system operacyjny.
Jednak to zwiększone narzędzie i ochrona wiązały się z pewnym kosztem, programy musiały teraz współpracować z systemem operacyjnym, aby wykonywać zadania, których nie wolno im było wykonywać samodzielnie, nie mogły już na przykład przejmować bezpośredniej kontroli nad dyskiem twardym, uzyskując dostęp do jego pamięci i zmieniać dowolnie danych, zamiast tego musieli poprosić system operacyjny o wykonanie dla nich tych zadań, aby mógł sprawdzić, czy wolno im wykonać operację, nie zmieniając plików, które do nich nie należą, sprawdziłby również, czy operacja rzeczywiście była prawidłowa i nie pozostawiłby sprzętu w nieokreślonym stanie.
Każdy system operacyjny zdecydował się na inną implementację tych zabezpieczeń, częściowo opartą na architekturze, dla której system został zaprojektowany, a częściowo w oparciu o projekt i zasady danego systemu operacyjnego, na przykład UNIX skupił się na komputerach odpowiednich do użytku przez wielu użytkowników i skoncentrowanych dostępne do tego funkcje, podczas gdy system Windows został zaprojektowany tak, aby był prostszy i działał na wolniejszym sprzęcie z jednym użytkownikiem. Sposób, w jaki programy w przestrzeni użytkownika również komunikują się z systemem operacyjnym, jest zupełnie inny na X86, tak jak na przykład w przypadku ARM lub MIPS, zmuszając wieloplatformowy system operacyjny do podejmowania decyzji w oparciu o potrzebę pracy na sprzęcie, do którego jest przeznaczony.
Te specyficzne dla systemu operacyjnego interakcje są zwykle nazywane „wywołaniami systemowymi” i obejmują sposób, w jaki program kosmiczny użytkownika współdziała ze sprzętem za pośrednictwem systemu operacyjnego, różnią się zasadniczo w zależności od funkcji systemu operacyjnego, a zatem program, który wykonuje swoją pracę za pośrednictwem wywołań systemowych, musi być specyficzne dla systemu operacyjnego.
Program ładujący
Oprócz wywołań systemowych, każdy system operacyjny zapewnia inną metodę ładowania programu z dodatkowego nośnika pamięci i do pamięci , aby mógł być załadowany przez określony system operacyjny, program musi zawierać specjalny nagłówek, który opisuje system operacyjny, jak to może być załadowane i uruchom.
Nagłówek ten był na tyle prosty, że napisanie modułu ładującego dla innego formatu było prawie trywialne, jednak w przypadku nowoczesnych formatów, takich jak elf, które obsługują zaawansowane funkcje, takie jak dynamiczne łączenie i słabe deklaracje, system operacyjny próbuje teraz załadować pliki binarne które nie zostały zaprojektowane do tego, oznacza to, że nawet gdyby nie było niezgodności wywołań systemowych, niezwykle trudno jest nawet umieścić program w pamięci RAM w sposób, w jaki można go uruchomić.
Biblioteki
Programy rzadko używają wywołań systemowych bezpośrednio, jednak prawie wyłącznie zyskują swoją funkcjonalność, chociaż biblioteki, które zawijają wywołania systemowe w nieco bardziej przyjaznym formacie dla języka programowania, na przykład C ma C Standard Library i glibc pod Linuksem i podobne oraz biblioteki win32 pod Windows NT i nowsze wersje, większość innych języków programowania ma również podobne biblioteki, które odpowiednio zawijają funkcjonalność systemu.
Biblioteki te mogą nawet do pewnego stopnia rozwiązać problemy międzyplatformowe, jak opisano powyżej, istnieje szereg bibliotek, które są zaprojektowane wokół zapewniania jednolitej platformy dla aplikacji, a jednocześnie wewnętrznie zarządzają połączeniami z szeroką gamą systemów operacyjnych, takich jak SDL , co oznacza, że chociaż programy nie mogą być kompatybilne binarnie, programy korzystające z tych bibliotek mogą mieć wspólne źródło między platformami, co czyni portowanie tak prostym jak rekompilacja.
Wyjątki od powyższego
Pomimo wszystkiego, co tu powiedziałem, próbowano pokonać ograniczenia związane z niemożnością uruchamiania programów na więcej niż jednym systemie operacyjnym. Dobrymi przykładami są projekt Wine, który z powodzeniem emulował zarówno program ładujący program win32, format binarny, jak i biblioteki systemowe pozwalające na uruchamianie programów Windows w różnych systemach UNIX. Istnieje również warstwa kompatybilności pozwalająca kilku systemom operacyjnym BSD UNIX na uruchamianie oprogramowania Linux i oczywiście podkładka Apple umożliwiająca uruchamianie starego oprogramowania MacOS pod MacOS X.
Projekty te jednak wymagają ogromnego nakładu pracy ręcznej. W zależności od tego, jak różne są dwa systemy operacyjne, trudność waha się od dość niewielkiej sztuczki do prawie całkowitej emulacji drugiego systemu operacyjnego, co jest często bardziej skomplikowane niż pisanie całego systemu operacyjnego, więc jest to wyjątek, a nie reguła.