Zacząłem myśleć o tym problemie w kontekście etykiety na liście mailingowej jądra systemu Linux. Jako najbardziej znany i prawdopodobnie najbardziej udany i ważny projekt wolnego oprogramowania na świecie, jądro Linuksa cieszy się dużą popularnością. A założyciel i lider projektu, Linus Torvalds, najwyraźniej nie wymaga tu przedstawiania.
Linus od czasu do czasu wzbudza kontrowersje swoim płomieniem na LKML. Te płomienie często, jak sam przyznaje, mają związek z łamaniem przestrzeni użytkownika. Co prowadzi mnie do mojego pytania.
Czy mogę spojrzeć z historycznego punktu widzenia na to, dlaczego łamanie przestrzeni użytkownika jest tak złe? Jak rozumiem, łamanie przestrzeni użytkownika wymagałoby poprawek na poziomie aplikacji, ale czy to takie złe, jeśli poprawia kod jądra?
Jak rozumiem, Linus stwierdził, że nieprzestrzeganie przestrzeni użytkownika przebija wszystko inne, w tym jakość kodu. Dlaczego jest to tak ważne i jakie są zalety i wady takiej polityki?
(Oczywiście istnieją pewne wady takiej polityki, konsekwentnie stosowanej, ponieważ Linus czasami ma „nieporozumienia” ze swoimi najlepszymi porucznikami na temat LKML w tym temacie. O ile wiem, zawsze wychodzi mu to na dobre.)
źródło
Odpowiedzi:
Powód nie jest historyczny, ale praktyczny. Istnieje wiele, wiele programów, które działają na jądrze Linux; jeśli interfejs jądra zepsuje te programy, wszyscy będą musieli je zaktualizować.
Teraz prawdą jest, że większość programów w rzeczywistości nie zależy bezpośrednio od interfejsów jądra ( wywołania systemowe ), ale tylko od interfejsów standardowej biblioteki C ( otoczki C wokół wywołań systemowych). Och, ale która standardowa biblioteka? Glibc? uClibC? Dietlibc? Bioniczny? Musl? itp.
Istnieje jednak wiele programów, które implementują usługi specyficzne dla systemu operacyjnego i zależą od interfejsów jądra, które nie są ujawniane przez bibliotekę standardową. (W systemie Linux wiele z nich jest oferowanych za pośrednictwem
/proc
i/sys
.)A potem są statycznie skompilowane pliki binarne. Jeśli aktualizacja jądra psuje jedno z nich, jedynym rozwiązaniem byłoby ich ponowne skompilowanie. Jeśli masz źródło: Linux obsługuje również oprogramowanie zastrzeżone.
Nawet gdy źródło jest dostępne, zebranie go może być uciążliwe. Zwłaszcza podczas aktualizacji jądra, aby naprawić błąd w sprzęcie. Ludzie często aktualizują jądro niezależnie od reszty systemu, ponieważ potrzebują wsparcia sprzętowego. W słowach Linusa Torvaldsa :
On wyjaśnia również, że jednym z powodów, aby ta reguła jest silny, aby uniknąć piekło zależności gdzie nie chcesz mieć tylko uaktualnić inny program, aby uzyskać niektóre nowsze jądro do pracy, ale także uaktualnić kolejny program, a kolejna, i kolejna , ponieważ wszystko zależy od określonej wersji wszystkiego.
W przestrzeni użytkownika te wzajemne zależności są zwykle rozwiązywane przez utrzymywanie różnych wersji bibliotek; ale możesz uruchomić tylko jedno jądro, więc musi obsługiwać wszystko, co ludzie mogą z nim zrobić.
oficjalnie ,
W praktyce jednak
To, co zmienia się częściej, to interfejsy, które mają być używane tylko przez programy związane ze sprzętem, w
/sys
. (/proc
z drugiej strony, która od czasu wprowadzenia/sys
została zarezerwowana dla usług niezwiązanych ze sprzętem, prawie nigdy nie psuje się w sposób niezgodny).W podsumowaniu,
i to źle, ponieważ istnieje tylko jedno jądro, które ludzie chcą aktualizować niezależnie od reszty swojego systemu, ale istnieje wiele aplikacji o złożonych współzależnościach. Łatwiej jest utrzymać stabilne jądro niż aktualizować tysiące aplikacji w milionach różnych konfiguracji.
źródło
W każdym systemie współzależnym istnieją zasadniczo dwie możliwości. Abstrakcja i integracja. (Celowo nie używam terminów technicznych). W przypadku Abstraction mówisz, że kiedy wywołujesz interfejs API, który, choć kod stojący za interfejsem API może ulec zmianie, wynik będzie zawsze taki sam. Na przykład, gdy dzwonimy
fs.open()
, nie obchodzi nas, czy jest to dysk sieciowy, dysk SSD czy dysk twardy, zawsze otrzymamy otwarty deskryptor pliku, z którym możemy coś zrobić. Celem „integracji” jest zapewnienie „najlepszego” sposobu na zrobienie czegoś, nawet jeśli sposób się zmieni. Na przykład otwarcie pliku może być inne w przypadku udziału sieciowego niż pliku na dysku. Oba sposoby są dość szeroko stosowane we współczesnym komputerze z systemem Linux.Z punktu widzenia programistów jest to pytanie „działa z dowolną wersją” lub „działa z określoną wersją”. Doskonałym tego przykładem jest OpenGL. Większość gier jest skonfigurowana do pracy z określoną wersją OpenGL. Nie ma znaczenia, czy kompilujesz ze źródła. Jeśli gra została napisana z myślą o wykorzystaniu OpenGL 1.1 i próbujesz uruchomić ją na 3.x, nie będziesz się dobrze bawić. Na drugim końcu spektrum niektóre połączenia powinny działać bez względu na wszystko. Na przykład chcę zadzwonić
fs.open()
Nie chcę się przejmować wersją jądra, na której jestem. Chcę tylko deskryptora pliku.Są zalety na każdy sposób. Integracja zapewnia „nowsze” funkcje kosztem kompatybilności wstecznej. Podczas gdy abstrakcja zapewnia stabilność w przypadku „nowszych” połączeń. Chociaż należy zauważyć, że jest to kwestia priorytetu, a nie możliwości.
Z punktu widzenia społeczności, bez naprawdę dobrego powodu, abstrakcja jest zawsze lepsza w złożonym systemie. Wyobraź sobie na przykład, że
fs.open()
działało inaczej w zależności od wersji jądra. Wówczas prosta biblioteka interakcji systemu plików wymagałaby utrzymania kilkuset różnych metod „otwartego pliku” (lub prawdopodobnie bloków). Kiedy pojawi się nowa wersja jądra, nie będziesz w stanie „uaktualnić”, będziesz musiał przetestować każdy używany program. Jądro 6.2.2 (fałszywe) może po prostu zepsuć edytor tekstu.W niektórych przykładach ze świata rzeczywistego OSX nie przejmuje się niszczeniem przestrzeni użytkownika. Częściej dążą do „integracji” zamiast „abstrakcji”. I przy każdej dużej aktualizacji systemu operacyjnego rzeczy się psują. To nie znaczy, że jeden sposób jest lepszy od drugiego. To decyzja dotycząca wyboru i projektu.
Co najważniejsze, ekosystem Linux jest wypełniony niesamowitymi projektami typu open source, w których ludzie lub grupy pracują nad projektem w wolnym czasie lub dlatego, że narzędzie jest przydatne. Mając to na uwadze, w momencie, gdy przestaje być zabawą i zaczyna być PIA, programiści pójdą gdzie indziej.
Na przykład przesłałem łatkę do
BuildNotify.py
. Nie dlatego, że jestem altruistą, ale dlatego, że używam tego narzędzia i chciałem funkcji. To było łatwe, więc tutaj, mam łatkę. Gdyby to było skomplikowane lub uciążliwe, nie użyłbym tegoBuildNotify.py
i znalazłbym coś innego. Jeśli za każdym razem, gdy pojawiała się aktualizacja jądra, mój edytor tekstowy pękał, używałbym innego systemu operacyjnego. Mój wkład w społeczność (bez względu na to, jak niewielki) nie byłby kontynuowany lub istniał, i tak dalej.Podjęto więc decyzję projektową dotyczącą abstrakcyjnych wywołań systemowych, więc kiedy to robię
fs.open()
, po prostu działa. Oznacza to utrzymaniefs.open
długo pofs.open2()
zdobyciu popularności.Historycznie taki jest cel systemów POSIX w ogóle. „Oto zestaw połączeń i oczekiwane wartości zwrotne, wymyślisz środek”. Ponownie ze względów przenośności. Dlaczego Linus decyduje się na zastosowanie tej metodologii, jest wewnętrzną cechą jego mózgu i trzeba by go zapytać, aby dokładnie wiedział dlaczego. Gdybym to był ja, wybrałbym abstrakcję zamiast integracji w złożonym systemie.
źródło
To decyzja projektowa i wybór. Linus chce być w stanie zagwarantować programistom przestrzeni użytkownika, że z wyjątkiem niezwykle rzadkich i wyjątkowych (np. Związanych z bezpieczeństwem) zmian w jądrze nie zepsują swoich aplikacji.
Zaletą jest to, że twórcy przestrzeni użytkownika nie zobaczą, jak ich kod nagle pęka w nowych jądrach z arbitralnych i kapryśnych powodów.
Wadą jest to, że jądro musi utrzymywać stary kod i stare wywołania systemowe itp. Na zawsze (lub przynajmniej dawno minęły daty przydatności).
źródło