Wprowadzenie do systemów operacyjnych
Cześć, Habr! Chcę przedstawić wam serię artykułów-przekładów jednej interesującej, moim zdaniem, literatury — OSTEP. W tym materiale dogłębnie omawiana jest praca systemów operacyjnych podobnych do Unix, a mianowicie — zarządzanie procesami, różnymi harmonogramami, pamięcią i innymi podobnymi komponentami, które tworzą nowoczesny OS. Oryginały wszystkich materiałów możecie zobaczyć tutaj . Proszę pamiętać, że tłumaczenie zostało wykonane w sposób nieprofesjonalny (dosyć luźno), ale mam nadzieję, że ogólny sens zachowałem.
Laboratoria dotyczące tego tematu można znaleźć tutaj:
— oryginał:
— oryginał:
— moja osobista adaptacja:
Możecie również zajrzeć na mój kanał w =)
Działanie programu
Co się dzieje, gdy działa jakiś program? Uruchomiony program wykonuje jedną prostą rzecz — realizuje instrukcje. Każdej sekundy miliony, a nawet prawdopodobnie miliardy instrukcji są wydobywane przez procesor z pamięci RAM, następnie dekodowane (na przykład, rozpoznawane, do jakiego rodzaju należą te instrukcje) i wykonywane. Mogą to być dodawanie dwóch liczb, dostęp do pamięci, sprawdzenie warunku, przejście do funkcji i tak dalej. Po zakończeniu wykonania jednej instrukcji, procesor przechodzi do wykonania kolejnej. I tak instrukcja za instrukcją, są one realizowane, dopóki program się nie zakończy.
Przykład ten jest oczywiście przedstawiony w uproszczony sposób — w rzeczywistości, aby przyspieszyć działanie procesora, nowoczesny sprzęt pozwala na wykonywanie instrukcji poza kolejnością, przewidywanie możliwych wyników, równoczesne wykonywanie instrukcji i inne podobne sztuczki.
Model obliczeniowy von Neumanna
Opisana przez nas uproszczona forma działania przypomina model obliczeniowy von Neumanna. Von Neumann to jeden z pionierów systemów komputerowych, także jest jednym z autorów teorii gier. Podczas pracy programu dzieje się jeszcze mnóstwo innych zdarzeń, działa wiele innych procesów i zewnętrznej logiki, których głównym celem jest uproszczenie uruchamiania, działania i utrzymania systemu.
Istnieje zestaw oprogramowania odpowiedzialnego za łatwość uruchamiania programów (a nawet umożliwiającego uruchamianie kilku programów jednocześnie), który pozwala programom dzielić tę samą pamięć, a także współdziałać z różnymi urządzeniami. Taki zestaw oprogramowania nazywa się systemem operacyjnym, a jego zadania obejmują monitorowanie tego, aby system działał prawidłowo i efektywnie, a także zapewnienie łatwości zarządzania tym systemem.
System operacyjny
System operacyjny, w skrócie OS — to zbiór wzajemnie powiązanych programów, przeznaczonych do zarządzania zasobami komputera i organizowania interakcji użytkownika z komputerem..
System operacyjny osiąga swoją efektywność przede wszystkim dzięki najważniejszej technice — technice wirtualizacji. System operacyjny wchodzi w interakcję z fizycznymi zasobami (procesorem, pamięcią, dyskiem i podobnymi) i przekształca je w bardziej ogólną, posiadającą większe możliwości oraz łatwiejszą w użyciu formę samego siebie. Dlatego dla ogólnego zrozumienia można w sposób bardzo ogólny porównać system operacyjny do maszyny wirtualnej.
Aby umożliwić użytkownikom wydawanie poleceń systemowi operacyjnemu i tym samym korzystanie z możliwości maszyny wirtualnej (takich jak: uruchamianie programu, przydzielanie pamięci, dostęp do pliku itd.), system operacyjny udostępnia pewien interfejs, zwany API (application programming interface), do którego można wykonywać wywołania (call). Typowy system operacyjny umożliwia wykonanie setek wywołań systemowych.
I wreszcie, ponieważ wirtualizacja pozwala wielu programom działać (w ten sposób współdzieląc CPU) i jednocześnie uzyskiwać dostęp do ich instrukcji oraz danych (dzieląc pamięć), a także uzyskiwać dostęp do dysków (w ten sposób współdzieląc urządzenia wejścia-wyjścia), system operacyjny nazywany jest również menedżerem zasobów. Każdy procesor, dysk i pamięć to zasoby systemu, a zatem jedną z ról systemu operacyjnego jest zarządzanie tymi zasobami, robiąc to efektywnie, sprawiedliwie, a w zależności od zadania, dla którego ten system operacyjny został zaprojektowany.
Wirtualizacja CPU
Przyjrzyjmy się następującemu programowi:
(https://www.youtube.com/watch?v=zDwT5fUcki4&feature=youtu.be)

Nie wykonuje żadnych specjalnych działań, w zasadzie to, co robi, to wywołuje funkcję spin(), której zadaniem jest cykliczne sprawdzanie czasu i zwracanie po upływie jednej sekundy. W ten sposób nieustannie powtarza ciąg, który użytkownik przekazał jako argument.
Uruchomimy ten program i przekażemy mu jako argument literę “A”. Wynik nie jest szczególnie interesujący — system po prostu wykonuje program, który okresowo wyświetla na ekranie literę “A”.
Teraz spróbujemy wariantu, w którym uruchomionych jest wiele instancji tego samego programu, ale wyświetlających różne litery, aby było to bardziej zrozumiałe. W tym przypadku wynik będzie nieco inny. Mimo że mamy tylko jeden procesor, program działa jednocześnie. Jak to możliwe? A to dlatego, że system operacyjny, przy pomocy możliwości sprzętu, tworzy iluzję. Iluzję, że w systemie jest kilka wirtualnych procesorów, zamieniając jeden fizyczny procesor w teoretycznie nieskończoną liczbę, a tym samym umożliwiając programom wydawać się, że działają jednocześnie. Tę iluzję nazywamy Wirtualizacją CPU.
Taka sytuacja rodzi wiele pytań, na przykład, jeśli kilka programów chce uruchomić się jednocześnie, to który z nich zostanie uruchomiony? Za to pytanie odpowiadają „polityki” OS. Polityki te są wykorzystywane w wielu miejscach OS i odpowiadają na podobne pytania, a także są podstawowymi mechanizmami, które OS wdraża. Stąd wzięła się rola OS jako zarządcy zasobów.
Wirtualizacja pamięci
Teraz przyjrzyjmy się pamięci. Fizyczny model pamięci w nowoczesnych systemach przedstawia się jako tablica bajtów. Aby przeczytać z pamięci, należy wskazać adres komórki, aby uzyskać do niej dostęp. Aby zapisać lub zaktualizować dane, należy również wskazać dane i adres komórki, do której mają być zapisane.
Dostęp do pamięci następuje nieprzerwanie w trakcie pracy programu. Program przechowuje w pamięci całą swoją strukturę danych i odwołuje się do niej, wykonując różne instrukcje. Instrukcje zresztą również są przechowywane w pamięci, więc dostęp do niej następuje także przy każdym zapytaniu o następną instrukcję.
Wywołanie malloc()
Rozważmy następujący program, który alokuje obszar pamięci używając wywołania malloc() (https://youtu.be/jnlKRnoT1m0):

Program wykonuje kilka działań. Po pierwsze, alokuje pewną ilość pamięci (linia 7), następnie wyświetla adres przydzielonej komórki (linia 9) i zapisuje zero w pierwszym slocie przydzielonej pamięci. Następnie program wchodzi w pętlę, w której inkrementuje wartość zapisaną w pamięci pod adresem w zmiennej ‘p’. Dodatkowo, wyświetla identyfikator własnego procesu. Identyfikator procesu jest unikalny dla każdego uruchomionego procesu. Uruchamiając kilka kopii, natkniemy się na ciekawy rezultat: W pierwszym przypadku, jeśli nic nie zrobimy i po prostu uruchomimy kilka kopii, adresy będą różne. Ale to nie pasuje do naszej teorii! Zgadza się, ponieważ w nowoczesnych dystrybucjach domyślnie włączona jest funkcja randomizacji pamięci. Jeśli ją wyłączymy, otrzymamy oczekiwany rezultat – adresy pamięci dwóch jednocześnie działających programów będą się pokrywać.

W rezultacie okazuje się, że dwa niezależne programy działają w swoich własnych prywatnych przestrzeniach adresowych, które z kolei są mapowane przez system operacyjny na pamięć fizyczną. Dlatego wykorzystanie adresów pamięci w ramach jednego programu nie wpłynie w żaden sposób na inne, a każdemu programowi wydaje się, że ma swój własny kawałek pamięci fizycznej, całkowicie przekazany do jego dyspozycji. Rzeczywistość jest jednak taka, że pamięć fizyczna jest zasobem współdzielonym, którego zarządzanie sprawuje system operacyjny.
Spójność
Kolejny z ważnych tematów w zakresie systemów operacyjnych to spójność. Termin ten jest używany, gdy mowa o problemach w systemie, które mogą występować podczas pracy z wieloma rzeczami jednocześnie w ramach jednego programu. Problemy ze spójnością występują nawet w samym systemie operacyjnym. W poprzednich przykładach z wirtualizacją pamięci i procesora zrozumieliśmy, że system operacyjny zarządza wieloma rzeczami jednocześnie - uruchamia pierwszy proces, następnie drugi i tak dalej. Okazało się, że takie zachowanie może prowadzić do pewnych problemów. Na przykład nowoczesne programy wielowątkowe napotykają na takie trudności.
Przyjrzyjmy się następującemu programowi:

Program w funkcji głównej tworzy dwa wątki, używając wywołania Pthread_create(). W tym przykładzie wątek można postrzegać jako funkcję uruchomioną w tej samej przestrzeni pamięci obok innych funkcji, przy czym liczba uruchomionych jednocześnie funkcji zdecydowanie przekracza jedną. W tym przykładzie każdy wątek startuje i wykonuje funkcję worker(), która z kolei po prostu inkrementuje zmienną,.
. Uruchomimy ten program z argumentem 1000. Jak już mogłeś się domyślić, rezultatem powinno być 2000, ponieważ każdy wątek inkrementował zmienną 1000 razy. Jednak to nie jest takie proste. Spróbujmy uruchomić program z liczbą powtórzeń rzędu większym o rząd.

Podając na wejście liczbę, na przykład 100000, spodziewamy się zobaczyć na wyjściu liczbę 200000. Jednak uruchamiając liczbę 100000 kilka razy, nie tylko nie zobaczymy poprawnej odpowiedzi, ale otrzymamy różne niepoprawne odpowiedzi. Rozwiązanie tego problemu leży w tym, że do zwiększenia liczby potrzebne są trzy operacje — odczytanie liczby z pamięci, inkrementacja i następnie zapis liczby z powrotem. Ponieważ wszystkie te instrukcje nie są realizowane atomowo (wszystkie jednocześnie), takie dziwne rzeczy mogą się zdarzyć. Problem ten nazywa się w programowaniu stan wyścigu. Kiedy nieznane siły w nieznanym momencie mogą wpłynąć na wykonanie jakiejkolwiek z Twoich operacji.
Źródło: habr.com
