Próbuję ustalić szczegóły techniczne, dlaczego oprogramowanie wyprodukowane przy użyciu języków programowania dla niektórych systemów operacyjnych działa tylko z nimi.
Rozumiem, że pliki binarne są specyficzne dla niektórych procesorów ze względu na język maszynowy, który rozumieją i różne zestawy instrukcji dla różnych procesorów. Ale skąd bierze się specyfika systemu operacyjnego? Zakładałem, że to interfejsy API dostarczane przez system operacyjny, ale potem zobaczyłem ten diagram w książce:
Systemy operacyjne - wewnętrzne i zasady projektowania, 7 edycja - W. Stallings (Pearson, 2012)
Jak widać, interfejsy API nie są wskazane jako część systemu operacyjnego.
Jeśli na przykład zbuduję prosty program w C, używając następującego kodu:
#include<stdio.h>
main()
{
printf("Hello World");
}
Czy kompilator robi coś specyficznego dla systemu operacyjnego podczas kompilacji?
źródło
printf
z msvcr90.dll nie jest taki sam jakprintf
z libc.so.6)Odpowiedzi:
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.
źródło
Myślę, że za dużo czytasz w schemacie. Tak, system operacyjny określi interfejs binarny określający sposób wywoływania funkcji systemu operacyjnego, a także określi format pliku wykonywalnego, ale zapewni także interfejs API w sensie zapewnienia katalogu funkcji, które można wywołać przez aplikacja do wywoływania usług systemu operacyjnego.
Myślę, że schemat próbuje tylko podkreślić, że funkcje systemu operacyjnego są zwykle wywoływane za pomocą innego mechanizmu niż zwykłe wywołanie biblioteki. Większość popularnych systemów operacyjnych korzysta z przerwań procesorów w celu uzyskania dostępu do funkcji systemu operacyjnego. Typowe nowoczesne systemy operacyjne nie pozwalają programowi użytkownika na bezpośredni dostęp do jakiegokolwiek sprzętu. Jeśli chcesz napisać znak do konsoli, musisz poprosić system operacyjny, aby zrobił to za Ciebie. Wywołanie systemowe używane do pisania na konsoli różni się w zależności od systemu operacyjnego, więc jest jeden przykład tego, dlaczego oprogramowanie jest specyficzne dla systemu operacyjnego.
printf jest funkcją z biblioteki czasu wykonywania C, a w typowej implementacji jest dość złożoną funkcją. Jeśli google, możesz znaleźć źródło dla kilku wersji online. Zobacz tę stronę z przewodnikiem po jednym . Na dole wykonuje się jedno lub więcej wywołań systemowych, a każde z nich jest specyficzne dla systemu operacyjnego hosta.
źródło
Prawdopodobnie. W pewnym momencie podczas kompilacji i łączenia kod jest przekształcany w plik binarny specyficzny dla systemu operacyjnego i łączony z dowolnymi wymaganymi bibliotekami. Twój program musi być zapisany w formacie, którego oczekuje system operacyjny, aby system operacyjny mógł załadować program i rozpocząć jego wykonywanie. Ponadto wywołujesz standardową funkcję biblioteczną
printf()
, która na pewnym poziomie jest implementowana pod względem usług świadczonych przez system operacyjny.Biblioteki zapewniają interfejs - warstwę abstrakcji od systemu operacyjnego i sprzętu - i umożliwia rekompilację programu dla innego systemu operacyjnego lub innego sprzętu. Ale ta abstrakcja istnieje na poziomie źródła - po skompilowaniu i połączeniu programu jest on powiązany z konkretną implementacją tego interfejsu, która jest specyficzna dla danego systemu operacyjnego.
źródło
Istnieje wiele przyczyn, ale jednym z bardzo ważnych powodów jest to, że system operacyjny musi wiedzieć, jak odczytać serię bajtów tworzących program w pamięci, znaleźć biblioteki, które są dołączone do tego programu i załadować je do pamięci, oraz następnie zacznij wykonywać kod programu. Aby to zrobić, twórcy systemu operacyjnego tworzą specjalny format dla tej serii bajtów, aby kod systemu operacyjnego wiedział, gdzie szukać różnych części struktury programu. Ponieważ główne systemy operacyjne mają różnych autorów, formaty te często mają niewiele wspólnego ze sobą. W szczególności format wykonywalny systemu Windows ma niewiele wspólnego z formatem ELF używanym przez większość wariantów Uniksa. Tak więc cały ten ładowanie, dynamiczne łączenie i wykonywanie kodu musi być specyficzne dla systemu operacyjnego.
Następnie każdy system operacyjny udostępnia inny zestaw bibliotek do komunikacji z warstwą sprzętową. Są to interfejsy API, o których wspominasz, i są to na ogół biblioteki, które prezentują prostszy interfejs dla programisty, tłumacząc go na bardziej złożone, bardziej szczegółowe wywołania w głębi samego systemu operacyjnego, które często są nieudokumentowane lub zabezpieczone. Ta warstwa jest często dość szara, a nowsze interfejsy API „OS” są zbudowane częściowo lub całkowicie na starszych interfejsach API. Na przykład w systemie Windows wiele nowszych interfejsów API, które Microsoft stworzył na przestrzeni lat, jest zasadniczo warstwą oryginalnych interfejsów API Win32.
Problem, który nie pojawia się w twoim przykładzie, ale jest jednym z większych problemów, z którymi spotykają się programiści, to interfejs z menedżerem okien w celu przedstawienia GUI. To, czy menedżer okien jest częścią „systemu operacyjnego”, zależy czasami od twojego punktu widzenia, a także od samego systemu operacyjnego, przy czym interfejs GUI w systemie Windows jest zintegrowany z systemem operacyjnym na głębszym poziomie, podczas gdy GUI w systemie Linux i OS X są bardziej bezpośrednio oddzielone. Jest to bardzo ważne, ponieważ dziś to, co ludzie zwykle nazywają „systemem operacyjnym”, jest znacznie większą bestią niż to, co podręczniki opisują, ponieważ zawiera wiele, wiele komponentów na poziomie aplikacji.
Wreszcie, nie tylko problem z systemem operacyjnym, ale ważnym problemem w generowaniu plików wykonywalnych jest to, że różne maszyny mają różne cele języka asemblera, a zatem rzeczywisty wygenerowany kod obiektowy musi się różnić. Nie jest to ściśle kwestia „systemu operacyjnego”, ale raczej problem sprzętowy, ale oznacza, że będziesz potrzebować różnych wersji dla różnych platform sprzętowych.
źródło
Z innej mojej odpowiedzi :
System operacyjny zapewnia więc aplikacjom usługi, dzięki czemu aplikacje nie muszą wykonywać zbędnej pracy.
Twój przykładowy program C używa printf, który wysyła znaki na standardowe wyjście - zasób specyficzny dla systemu operacyjnego, który wyświetla znaki w interfejsie użytkownika. Program nie musi wiedzieć, gdzie jest interfejs użytkownika - może być w systemie DOS, może znajdować się w oknie graficznym, może być przekierowany do innego programu i użyty jako dane wejściowe do innego procesu.
Ponieważ system operacyjny zapewnia te zasoby, programiści mogą osiągnąć znacznie więcej przy niewielkiej pracy.
Jednak nawet uruchomienie programu jest skomplikowane. System operacyjny oczekuje, że plik wykonywalny na początku będzie zawierał pewne informacje, które podpowiedzą mu, jak należy go uruchomić, aw niektórych przypadkach (bardziej zaawansowane środowiska, takie jak Android lub iOS), jakie zasoby będą wymagane, ponieważ wymagają one zasobów poza „piaskownica” - środek bezpieczeństwa, który pomaga chronić użytkowników i inne aplikacje przed źle działającymi programami.
Więc nawet jeśli wykonywalny kod maszynowy jest taki sam i nie są wymagane żadne zasoby systemu operacyjnego, program skompilowany dla systemu Windows nie będzie działał w systemie operacyjnym OS X bez dodatkowej warstwy emulacji lub translacji, nawet na tym samym dokładnym sprzęcie.
Wczesne systemy operacyjne w stylu DOS często mogły współdzielić programy, ponieważ zaimplementowały to samo API w sprzęcie (BIOS) i systemie operacyjnym podłączonym do sprzętu w celu świadczenia usług. Więc jeśli napisałeś i skompilowałeś program COM - który jest tylko obrazem pamięci szeregu instrukcji procesora - możesz go uruchomić na CP / M, MS-DOS i kilku innych systemach operacyjnych. W rzeczywistości nadal można uruchamiać programy COM na nowoczesnych komputerach z systemem Windows. Inne systemy operacyjne nie używają tych samych zaczepów API BIOS-u, więc programy COM nie będą na nich działały bez warstwy emulacji lub translacji. Programy EXE mają strukturę, która zawiera znacznie więcej niż tylko instrukcje procesora, więc wraz z problemami API nie będzie działać na komputerze, który nie rozumie, jak załadować go do pamięci i wykonać.
źródło
Faktycznie, prawdziwa odpowiedź jest taka, że jeśli każdy OS zrobił zrozumieć samą wykonywalny binarny układ pliku, a tylko ograniczają się do standardowych funkcji (jak w standardowej bibliotece C), że OS przewidzianych (które systemy operacyjne dają), to oprogramowanie będzie , w rzeczywistości działa na dowolnym systemie operacyjnym.
Oczywiście w rzeczywistości tak nie jest.
EXE
Plik nie ma ten sam format jakoELF
plik, choć oba zawierają kod binarny dla tego samego procesora. * Więc każdy system operacyjny będzie musiał być w stanie zinterpretować wszystkie formaty plików, a oni po prostu tego nie zrobił w na początku i nie było powodu, aby zaczęli to robić później (prawie na pewno raczej z przyczyn komercyjnych niż technicznych).Co więcej, twój program prawdopodobnie musi robić rzeczy, których biblioteka C nie określa, jak to zrobić (nawet w przypadku prostych rzeczy, takich jak wyświetlanie zawartości katalogu), aw takich przypadkach każdy system operacyjny udostępnia własne funkcje do osiągnięcia zadanie, naturalnie co oznacza, że nie będzie to najniższy wspólny mianownik do użycia (jeśli nie sprawiają , że mianownik siebie).
Zasadniczo jest to całkowicie możliwe. W rzeczywistości WINE uruchamia pliki wykonywalne systemu Windows bezpośrednio w systemie Linux.
Ale to mnóstwo pracy i (zwykle) nieuzasadnione z handlowego punktu widzenia.
* Uwaga: Plik wykonywalny zawiera znacznie więcej niż tylko kod binarny. Istnieje mnóstwo informacji, które informują system operacyjny, od jakich bibliotek zależy plik, ile pamięci stosu potrzebuje, jakie funkcje eksportuje do innych bibliotek, które mogą od niego zależeć, gdzie system operacyjny może znaleźć odpowiednie informacje debugowania, jak „ ponownie zlokalizuj „plik w pamięci, jeśli to konieczne, jak sprawić, by obsługa wyjątków działała poprawnie itp. itd.… znowu, może istnieć jeden format, na który wszyscy się zgadzają, ale po prostu nie.
źródło
Diagram ma warstwę „aplikacji” (głównie) oddzieloną od warstwy „systemu operacyjnego” „bibliotekami”, co oznacza, że „aplikacja” i „system operacyjny” nie muszą się o sobie wzajemnie wiedzieć. To uproszczenie na schemacie, ale nie do końca prawda.
Problem polega na tym, że „biblioteka” składa się z trzech części: implementacji, interfejsu aplikacji i interfejsu systemu operacyjnego. Zasadniczo pierwsze dwa można uczynić „uniwersalnymi”, jeśli chodzi o system operacyjny (zależy to od miejsca, w którym go podzielono), ale trzecia część - interfejs do systemu operacyjnego - generalnie nie może. Interfejs do systemu operacyjnego będzie koniecznie zależeć od systemu operacyjnego, udostępnianych przez niego interfejsów API, mechanizmu pakowania (np. Format pliku wykorzystywany przez bibliotekę DLL systemu Windows) itp.
Ponieważ „biblioteka” jest ogólnie udostępniana jako pojedynczy pakiet, oznacza to, że gdy program wybierze „bibliotekę” do użycia, zatwierdza się do określonego systemu operacyjnego. Dzieje się tak na jeden z dwóch sposobów: a) programista wybiera kompletnie wcześniej, a następnie powiązanie między biblioteką a aplikacją może być uniwersalne, ale sama biblioteka jest związana z systemem operacyjnym; lub b) programista konfiguruje ustawienia, aby biblioteka była wybierana podczas uruchamiania programu, ale sam mechanizm wiązania między programem a biblioteką jest zależny od systemu operacyjnego (np. mechanizm DLL w systemie Windows). Każda ma swoje zalety i wady, ale tak czy inaczej musisz dokonać wcześniejszego wyboru.
Nie oznacza to, że jest to niemożliwe, ale musisz być bardzo mądry. Aby rozwiązać ten problem, musisz wybrać ścieżkę wybierania biblioteki w czasie wykonywania i musisz opracować uniwersalny mechanizm wiązania, który nie zależy od systemu operacyjnego (więc jesteś odpowiedzialny za jego utrzymanie, dużo więcej pracy). Czasami warto.
Nie musisz tego robić, ale jeśli zamierzasz to zrobić, istnieje duża szansa, że nie chcesz być powiązany z konkretnym procesorem, więc napiszesz maszynę wirtualną i skompilujesz twój program do formatu neutralnego kodu procesora.
Do tej pory powinieneś zauważyć, dokąd zmierzam. Platformy językowe, takie jak Java, właśnie to robią. Środowisko wykonawcze Java (biblioteka) definiuje neutralne powiązanie systemu operacyjnego między programem Java i biblioteką (sposób, w jaki środowisko wykonawcze Java otwiera się i uruchamia program), i zapewnia implementację specyficzną dla bieżącego systemu operacyjnego. .NET robi to samo w pewnym stopniu, z tym wyjątkiem, że Microsoft nie zapewnia „biblioteki” (środowiska wykonawczego) dla niczego poza Windows (ale inni to robią - patrz Mono). I właściwie Flash robi to samo, chociaż jego zakres jest ograniczony do przeglądarki.
Wreszcie istnieją sposoby na zrobienie tego samego bez niestandardowego mechanizmu wiązania. Możesz użyć konwencjonalnych narzędzi, ale odłóż krok wiązania do biblioteki, dopóki użytkownik nie wybierze systemu operacyjnego. Dokładnie tak się dzieje, gdy dystrybuujesz kod źródłowy. Użytkownik bierze program i wiąże go z procesorem (kompiluje) i systemem operacyjnym (łączy go), gdy jest gotowy do uruchomienia.
Wszystko zależy od tego, jak pokroisz warstwy. Na koniec dnia zawsze masz urządzenie komputerowe wykonane z określonego sprzętu z określonym kodem maszynowym. Warstwy są tam głównie jako ramy koncepcyjne.
źródło
Oprogramowanie nie zawsze jest specyficzne dla systemu operacyjnego. Zarówno Java, jak i wcześniejszy system kodu p (a nawet ScummVM) pozwalają na oprogramowanie, które można przenosić między systemami operacyjnymi. Infocom (twórcy Zork i maszyny Z ) miał także relacyjną bazę danych opartą na innej maszynie wirtualnej. Jednak na pewnym poziomie coś musi przełożyć nawet te abstrakcje na rzeczywiste instrukcje, które należy wykonać na komputerze.
źródło
Mówisz
Ale program, który podasz jako przykład, będzie działał na wielu systemach operacyjnych, a nawet w niektórych środowiskach typu bare-metal.
Ważne jest tutaj rozróżnienie między kodem źródłowym a skompilowanym plikiem binarnym. Język programowania C został specjalnie zaprojektowany, aby był niezależny od systemu operacyjnego w formie źródłowej. Robi to, pozostawiając implementacji interpretację takich rzeczy jak „print to the console”. Ale C może być zgodny z czymś, co jest specyficzne dla systemu operacyjnego (zobacz inne odpowiedzi z powodów). Na przykład formaty plików PE lub ELF.
źródło
Inne osoby dobrze omawiały szczegóły techniczne, chciałbym wspomnieć o mniej technicznym powodzie, po stronie UX / UI:
Napisz raz, poczuj się niezręcznie wszędzie
Każdy system operacyjny ma własne interfejsy API interfejsu użytkownika i standardy projektowania. Możliwe jest napisanie jednego interfejsu użytkownika dla programu i uruchomienie go w wielu systemach operacyjnych, ale robi to wszystko, ale gwarantuje, że program będzie się czuł nie na miejscu. Stworzenie dobrego interfejsu użytkownika wymaga dopracowania szczegółów dla każdej obsługiwanej platformy.
Wiele z nich to małe szczegóły, ale pomylcie się, a sfrustrujecie użytkowników:
Nawet jeśli jest technicznie możliwe napisanie jednej bazy kodu interfejsu użytkownika, która działa wszędzie, najlepiej wprowadzić poprawki dla każdego obsługiwanego systemu operacyjnego.
źródło
Ważnym rozróżnieniem w tym miejscu jest oddzielenie kompilatora od linkera. Kompilator najprawdopodobniej wytwarza mniej więcej taką samą moc wyjściową (różnice wynikają głównie z różnych
#if WINDOWS
s). Z drugiej strony linker musi obsłużyć wszystkie rzeczy specyficzne dla platformy - łączenie bibliotek, budowanie pliku wykonywalnego itp.Innymi słowy, kompilator dba przede wszystkim o architekturę procesora, ponieważ generuje on rzeczywiście działający kod, i musi korzystać z instrukcji i zasobów procesora (zwróć uwagę, że IL .NET lub kod bajtowy JVM będą uważane za zestawy instrukcji wirtualnego procesora W tym widoku). Dlatego na przykład musisz skompilować kod osobno dla
x86
iARM
.Linker, z drugiej strony, musi wziąć wszystkie te surowe dane i instrukcje i umieścić je w formacie zrozumiałym dla modułu ładującego (w dzisiejszych czasach prawie zawsze byłby to system operacyjny), a także łączenia wszelkich statycznie powiązanych bibliotek (który obejmuje również kod wymagany do dynamicznego łączenia, alokacji pamięci itp.).
Innymi słowy, możesz skompilować kod tylko raz i uruchomić go zarówno w systemie Linux, jak i Windows - ale musisz połączyć go dwa razy, tworząc dwa różne pliki wykonywalne. Teraz, w praktyce, często musisz uwzględniać również kod (tam właśnie wchodzą dyrektywy (pre) kompilatora), więc nawet kompilacja raz-link dwukrotnie nie jest często używana. Nie wspominając już o tym, że ludzie traktują kompilację i linkowanie jako jeden krok podczas kompilacji (tak jak nie obchodzi Cię już część samego kompilatora).
Oprogramowanie z epoki DOS było często bardziej przenośne binarnie, ale musisz zrozumieć, że zostało również skompilowane nie przeciwko DOS lub Unixowi, ale raczej przeciwko pewnej umowie, która była wspólna dla większości komputerów PC w stylu IBM - odciążając to, co dziś wywołuje API oprogramowanie przerywa. Nie wymagało to statycznego łączenia, ponieważ wystarczyło tylko ustawić niezbędne rejestry, wywołać np.
int 13h
Funkcje graficzne, a procesor właśnie przeskoczył do wskaźnika pamięci zadeklarowanego w tabeli przerwań. Oczywiście znowu ćwiczenie było trudniejsze, ponieważ aby uzyskać efekt pedał na metal, trzeba było napisać wszystkie te metody samodzielnie, ale w zasadzie oznaczało to całkowite obejście systemu operacyjnego. I oczywiście jest coś, co niezmiennie wymaga interakcji z API systemu operacyjnego - zakończenie programu. Ale nadal, jeśli użyłeś najprostszych dostępnych formatów (npCOM
na DOSie, który nie ma nagłówka, tylko instrukcje) i nie chciał wyjść, no cóż - na szczęście! Oczywiście możesz również obsługiwać prawidłowe zakończenie w środowisku wykonawczym, więc możesz mieć kod zarówno dla zakończenia Unixa, jak i dla zakończenia DOS w tym samym pliku wykonywalnym, i wykryć w czasie wykonywania, którego użyć :)źródło