Dlaczego oprogramowanie jest specyficzne dla systemu operacyjnego?

77

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: Diagram

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?

użytkownik139929
źródło
15
Czy drukujesz do okna? czy konsola? czy do pamięci graficznej? Jak tam umieścić dane? Patrząc na printf dla Apple'a] [+ byłby cicho inny niż dla Mac OS 7 i znowu zupełnie inny niż Mac OS X (po prostu trzymanie się jednej „linii” komputerów).
3
Ponieważ jeśli napisałeś ten kod dla Mac OS 7, pojawiłby się w tekście w nowym oknie. Gdybyś to zrobił na Apple] [+, zapisywałby bezpośrednio w jakimś segmencie pamięci. W systemie Mac OS X wypisuje go na konsolę. To trzy różne sposoby pisania obsługi kodu w oparciu o sprzęt wykonawczy obsługiwany przez warstwę biblioteki.
2
@StevenBurnap yep - en.wikipedia.org/wiki/Aztec_C
10
Twoja funkcja FFT z przyjemnością uruchomi się w systemie Windows lub Linux (na tym samym procesorze), nawet bez ponownej kompilacji. Ale jak zamierzasz wyświetlić wynik? Oczywiście przy użyciu interfejsu API systemu operacyjnego. ( printfz msvcr90.dll nie jest taki sam jak printfz libc.so.6)
immibis
9
Nawet jeśli interfejsy API nie są „częścią systemu operacyjnego”, nadal różnią się w przypadku przejścia z jednego systemu operacyjnego do drugiego. (Co oczywiście rodzi pytanie o to, co tak naprawdę oznacza zdanie „nie jest częścią systemu operacyjnego”, zgodnie ze schematem.)
Theodoros Chatzigiannakis

Odpowiedzi:

78

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.

Rzeczywistość
źródło
6
+1 „Dlaczego oprogramowanie jest specyficzne dla systemu operacyjnego?” Ponieważ historia.
Paul Draper,
2
czy pochodzi model bezpieczeństwa procesora x86? dlaczego i kiedy wynaleziono ten model?
n611x007
8
@naxa Nie, długo wcześniej niż x86, po raz pierwszy częściowo zaimplementowano dla Multics w 1969 roku, który jest pierwszym systemem operacyjnym z przydatnymi funkcjami dzielenia czasu dla wielu użytkowników wymagającymi tego modelu w komputerze GE-645 , jednak ta implementacja była niepełna i polegała na obsługa oprogramowania, pierwszą pełną i bezpieczną implementacją sprzętową była jego następca, Honeywell 6180 . Było to w pełni oparte na sprzęcie i pozwoliło Multics na uruchamianie kodu od wielu użytkowników bez możliwości ingerencji krzyżowej.
Rzeczywistość
@Vality Również IBM LPAR to ~ 1972.
Elliott Frisch
@ElliottFrisch wow, to imponujące. Nie zdawałem sobie sprawy, że było tak wcześnie. Dzięki za te informacje.
Rzeczywistość
48

Jak widać, interfejsy API nie są wskazane jako część systemu operacyjnego.

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.

Charles E. Grant
źródło
4
Co jeśli wszystko, co zrobił program, to dodanie dwóch liczb, bez danych wejściowych lub wyjściowych. Czy ten program nadal byłby specyficzny dla systemu operacyjnego?
Paul
2
Systemy operacyjne mają na celu umieszczenie większości elementów specyficznych dla sprzętu za / w warstwie abstrakcji. Jednak sam system operacyjny (abstrakcja) może różnić się od implementacji do implementacji. Istnieje POSIX, że niektóre systemy operacyjne (mniej lub bardziej) się stosują, a niektóre inne, ale ogólnie systemy operacyjne po prostu różnią się zbytnio „widoczną” częścią abstrakcji. Jak powiedziano wcześniej: nie można otworzyć / home / user w systemie Windows i nie można uzyskać dostępu do HKEY_LOCAL_MACHINE \ ... w systemie * N * X. Możesz w tym celu napisać oprogramowanie wirtualne („emulacja”), aby pomóc zbliżyć te systemy, ale zawsze będzie to „firma zewnętrzna” (z PO PO).
RobIII
16
@Paul Tak. W szczególności sposób, w jaki jest spakowany w pliku wykonywalnym, byłby specyficzny dla systemu operacyjnego.
OrangeDog
4
@ TimSeguine Nie zgadzam się z twoim przykładem XP w porównaniu z 7. Microsoft wykonuje wiele pracy, aby upewnić się, że ten sam API istnieje w 7, tak jak w XP. Wyraźnie widać, że program został zaprojektowany do działania z określonym interfejsem API lub kontraktem. Nowy system operacyjny przestrzegał tego samego interfejsu API / umowy. W przypadku Windows ten interfejs API jest jednak bardzo zastrzeżony, dlatego żaden inny dostawca systemu operacyjnego go nie obsługuje. Nawet wtedy istnieje mnóstwo przykładów programów, które NIE działają na 7.
ArTs
3
@Paul: Program, który nie ma wejścia / wyjścia jest pustym programem , który powinien się skompilować do no-op.
Bergi,
14

Czy kompilator robi coś specyficznego dla systemu operacyjnego podczas kompilacji?

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.

Caleb
źródło
12

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.

Steven Burnap
źródło
2
Warto zauważyć, że prostsze formaty plików wykonywalnych można ładować przy użyciu tylko niewielkiej ilości pamięci RAM (jeśli w ogóle) poza wymaganą do przechowywania załadowanego kodu, podczas gdy bardziej złożone formaty mogą wymagać znacznie większego miejsca na pamięć RAM w trakcie, aw niektórych przypadkach nawet po załadowaniu. MS-DOS ładowałby pliki COM o rozmiarze do 63,75K, po prostu czytając kolejne bajty do pamięci RAM, zaczynając od przesunięcia 0x100 dowolnego segmentu, ładując CX z końcowym adresem i przeskakując do tego. Kompilacja jednoprzebiegowa może być wykonana bez łatania wstecznego (przydatne w dyskietkach) do ...
supercat
1
... posiadanie kompilatora dołącza do każdej procedury listę wszystkich punktów łat, z których każda zawierałaby adres poprzedniej takiej listy, i umieszczała adres ostatniej listy na końcu kodu. System operacyjny po prostu ładowałby kod jako nieprzetworzone bajty, ale niewielka procedura w kodzie mogłaby zastosować wszystkie niezbędne poprawki adresu przed uruchomieniem głównej części kodu.
supercat
9

Z innej mojej odpowiedzi :

Rozważ wczesne maszyny DOS i jaki prawdziwy wkład Microsoft w świat miał:

Autocad musiał napisać sterowniki dla każdej drukarki, na której mogliby drukować. Podobnie lotos 1-2-3. W rzeczywistości, jeśli chcesz wydrukować oprogramowanie, musisz napisać własne sterowniki. Gdyby było 10 drukarek i 10 programów, to 100 różnych kawałków zasadniczo tego samego kodu musiałoby zostać napisanych osobno i niezależnie.

To, co Windows 3.1 próbował osiągnąć (wraz z GEM i wieloma innymi warstwami abstrakcji), sprawia, że ​​producent drukarki napisał jeden sterownik dla swojej drukarki, a programista napisał jeden sterownik dla klasy drukarek Windows.

Teraz, przy 10 programach i 10 drukarkach, trzeba napisać tylko 20 kawałków kodu, a ponieważ strona Microsoft kodu była taka sama dla wszystkich, przykłady z MS oznaczały, że miałeś bardzo mało do zrobienia.

Teraz program nie był ograniczony tylko do 10 drukarek, które wybrali do obsługi, ale do wszystkich drukarek, których producenci dostarczyli sterowniki dla systemu Windows.

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ć.

Adam Davis
źródło
7

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. EXEPlik nie ma ten sam format jako ELFplik, 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.

Mehrdad
źródło
Ciekawostka: istnieje znormalizowany format binarny POSIZ, który można uruchamiać w różnych systemach operacyjnych. Po prostu nie jest powszechnie używany.
Marcin
@Marcin: Wygląda na to, że nie uważasz Windowsa za system operacyjny. (A może mówisz, że Windows może uruchamiać pliki binarne POSIX ?!) Na potrzeby mojej odpowiedzi POSIX nie jest rodzajem standardu, o którym mówię. X w POSIX oznacza Unix. Nigdy nie był przeznaczony do użycia np. W systemie Windows, nawet jeśli Windows ma podsystem POSIX.
Mehrdad
1. Coś może działać w wielu systemach operacyjnych bez uruchamiania we wszystkich systemach operacyjnych; 2. Windows od NT jest w stanie uruchomić pliki binarne posix.
Marcin
1
@Marcin: (1) Jak powiedziałem, X w POSIX oznacza UNIX . Nie jest to standard, który miał być przestrzegany przez inne systemy operacyjne, to tylko próba osiągnięcia wspólnego mianownika między różnymi Uniksami, co jest świetne, ale nie takie niesamowite. Fakt, że istnieje wiele odmian systemów operacyjnych Unix, jest całkowicie nieistotny z punktu widzenia tego, co starałem się uczynić w kwestii kompatybilności w innych systemach operacyjnych niż Unix. (2) Czy możesz podać odniesienie do punktu 2?
Mehrdad
1
@Mehrdad: Marcin ma rację; Windows SUA (podsystem aplikacji uniksowych) jest zgodny z POSIX
MSalters
5

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.

Euro Micelli
źródło
3

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.

Elliott Frisch
źródło
3
Java działa jednak na maszynie wirtualnej, która nie jest wielosystemowa. Musisz użyć innego pliku binarnego JVM dla każdego systemu operacyjnego
Izkata
3
@Izkata Prawda, ale nie rekompilujesz oprogramowania (tylko JVM). Zobacz także moje ostatnie zdanie. Ale zaznaczę, że Sun miał mikroprocesor, który mógł bezpośrednio wykonywać bajt-code.
Elliott Frisch
3
Java to system operacyjny, chociaż zwykle nie jest uważany za jeden. Oprogramowanie Java jest specyficzne dla systemu operacyjnego Java i istnieją emulatory systemu operacyjnego Java dla większości „prawdziwych” systemów operacyjnych. Ale możesz zrobić to samo z dowolnym hostem i docelowym systemem operacyjnym - jak na przykład uruchamianie oprogramowania Windows w systemie Linux za pomocą WINE.
immibis
@immibis Chciałbym być bardziej szczegółowy. Java Foundation Classes (JFC, standardowa biblioteka Java) to framework. Sama Java jest językiem. JVM jest podobny do systemu operacyjnego: w nazwie ma „maszynę wirtualną” i wykonuje podobne funkcje do systemu operacyjnego z perspektywy uruchomionego w nim kodu.
1

Mówisz

oprogramowanie wyprodukowane przy użyciu języków programowania dla niektórych systemów operacyjnych działa tylko z nimi

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.

Dan
źródło
6
Wydaje się całkiem jasne, że OP pyta o pliki binarne, a nie kod źródłowy.
Caleb
0

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:

  • Potwierdź, że okna dialogowe mają przyciski w innej kolejności w systemach Windows i OSX; pomylisz się, a użytkownicy klikną niewłaściwy przycisk pamięci mięśniowej. System Windows ma w tej kolejności „Ok”, „Anuluj”. W OSX zamieniono kolejność, a tekst przycisku „zrób to” to krótki opis czynności, którą należy wykonać: „Anuluj”, „Przenieś do kosza”.
  • Zachowanie „cofnij się” jest inne dla iOS i Androida. Aplikacje iOS rysują w razie potrzeby własny przycisk Wstecz, zwykle w lewym górnym rogu. Android ma dedykowany przycisk w lewym dolnym rogu lub w prawym dolnym rogu, w zależności od obrotu ekranu. Szybkie porty do Androida będą działać nieprawidłowo, jeśli przycisk Wstecz systemu operacyjnego zostanie zignorowany.
  • Pęd przewijania różni się w systemach iOS, OSX i Android. Niestety, jeśli nie piszesz natywnego kodu interfejsu użytkownika, prawdopodobnie będziesz musiał napisać własne zachowanie przewijania.

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.

Nick Pinney
źródło
-2

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 WINDOWSs). 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 x86i ARM.

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 13hFunkcje 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 (npCOMna 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ć :)

Luaan
źródło
to wydaje się jedynie powtórzyć punkty wyjaśnione w to i to wcześniejsze odpowiedzi, które zostały zaksięgowane wczoraj
komara