Usługi systemu operacyjnego
Transkrypt
Usługi systemu operacyjnego
Projektowanie oprogramowania systemów USŁUGI I ZASOBY SYSTEMU OPERACYJNEGO plan rola systemu operacyjnego w zarządzaniu zasobami procesów Unix - filozofia „wszystko jest plikiem” usługi i zasoby systemowe API dostępu do plików zarządzanie pamięcią zegary rola systemu operacyjnego w zarządzaniu zasobami procesów System operacyjny jest środowiskiem do uruchamiania i kontroli zadań użytkownika Zajmuje się zarządzaniem zasobami komputera planowaniem oraz przydziałem czasu procesora (scheduling) przydziałem pamięci operacyjnej zadaniom dostarcza mechanizmy IPC obsługuje sprzęt i zapewnia współbieżnym zadaniom pozbawiony interferencji do niego dostęp zarządza plikami … zarządzanie zasobami interferencja procesów – szkodliwe zjawisko będące efektem wielozadaniowości różne procesy równocześnie uzyskują dostęp do tego samego sprzętu i w przypadkowej kolejności wykonują na nim różne operacje rolą systemu operacyjnego jest szeregowanie dostępu do zasobów i zapewnienie spójności operacji poszczególnych procesów system operacyjny jako „strażnik” zasobów warstwa abstrakcji sprzętu (HAL) – każdy z procesów „ma wrażenie”, że pracuje na własnym komputerze, z własnymi zasobami – w tym również nieistniejącymi (wirtualnymi) rola systemu operacyjnego w zarządzaniu zasobami Przydział zasobów Tworzenie i usuwanie deskryptorów zasobów Realizacja żądań przydziału i zwolnienia zasobów Synchronizacja dostępu do zasobów (zapobieganie interferencji) Autoryzacja dostępu do zasobów (system uprawnień) Odzyskiwanie zwolnionych zasobów Accounting (gromadzenie danych statystycznych o wykorzystaniu zasobów) zarządzane zasoby procesy czas procesora obiekty synchronizacji i IPC pamięć operacyjna przydział pamięci do procesów ochrona pamięci pliki tworzenie i kasowanie plików i katalogów operacje odczytu i zapisu urządzenia wejścia/wyjścia nośniki danych filozofia Unix – „wszystko jest plikiem” prawie wszystkie zasoby systemowe w Uniksie reprezentowane są przez pliki istniejące w systemie plików pozwala to w spójny sposób nadawać użytkownikom i procesom uprawnienia do tych zasobów i zarządzać ich dostępem ten sam zestaw narzędzi może być wykorzystywany z wieloma rodzajami narzędzi dostęp do zasobów może być realizowany poprzez otwarcie deskryptora pliku reprezentującego dane urządzenie w systemie plików pliki reprezentujące urządzenia mogą istnieć w fizycznym systemie plików (na dysku) lub w wirtualnych systemach plików, montowanych do rzeczywistych lokalizacji na dysku (np. /proc) „wszystko jest plikiem” zasoby będące reprezentowane przez pliki rzeczywiste pliki i katalogi gniazda sieciowe i potoki urządzenia we/wy (sterowniki urządzeń): system plików /dev urządzenia znakowe (niebuforowany dostęp) urządzenia blokowe (buforowany dostęp) pseudo-urządzenia (np. /dev/null, /dev/random – generator liczb losowych) obiekty synchronizacji (semafory, mutexy, …) pamięć współdzielona (shmfs) procesory, pamięć fizyczna, procesy, timery,… (system plików procfs) wiele wywołań systemowych możliwych jest do zrealizowania jako operacja odczytu/zapisu określonego pliku „wszystko jest plikiem” wiele systemów nie-Unixowych naśladuje w ograniczonym zakresie podejście „wszystko jest plikiem” Unixa CP/M, DOS, OS/2: pliki specjalne CON$, PRN$, NUL$, CLOCK$, LPTn, COMn Windows NT wewnętrznie system jest podobny do Unixa, większość urządzeń i zasobów również może być otwarta jako plik (uchwyt pliku, HANDLE) zasoby nie są widoczne jako pliki na dysku, ale istnieją w swoich osobnych przestrzeniach nazw (np. \\.\pipes\ - dla nazwanych potoków) API dostępu do plików Systemy operacyjne dostarczają warstwę abstrakcji, dzięki której dostęp do plików znajdujących się w różnych systemach plików (lokalnych, sieciowych, wirtualnych, przenośnych, wymiennych …) odbywa się w identyczny sposób Różnice w realizacji operacji w różnych systemach plików są obsługiwane przez sterownik w sposób przezroczysty dla użytkownika, przy pomocy pojedynczego zestawu wywołań systemowych System operacyjny dostarcza ujednolicony, ustandardyzowany interfejs programistyczny (API) typowe operacje na plikach Otwieranie i zamykanie plików (tworzenie deskryptorów plików lub uchwytów dla obiektów znajdujących się w systemie plików) deskryptor pliku – abstrakcyjny identyfikator (liczba), zwykle indeks do tablicy deskryptorów zarządzanej przez OS dla danego procesu Odczyt, zapis i pozycja w pliku Zarządzanie metadanymi pliku (czas ostatniej modyfikacji, tagi, etc) Zarządzanie katalogami Określanie zasad współdzielenia otwartych plików między procesami, blokowanie Uprawnienia dostępu do plików Szyfrowanie warstwy API plikowych systemy operacyjne, języki programowania i biblioteki dostarczają warstwowego modelu dostępu do plików zapewniającego abstrakcję sprzętu, platformy, języka oprogramowania C++ standard library UNIX Windows C++97 iostream library (fstream class) C++97 iostream library (fstream class) C89 stdio library (FILE pointer) C89 stdio library (FILE pointer) C standard library emulated POSIX file API (int file descriptor) Native OS API Unix/POSIX file API (int file descriptor) Win32 file API (HANDLE) warstwy API - niskopoziomowe POSIX natywne API: open()/creat() – zwracają deskryptor pliku, tablica deskryptorów zarządzana przez OS niskopoziomowe operacje na plikach brak buforowania na poziomie biblioteki – każda operacja na deskryptorze jest osobnym wywołaniem systemowym buforowanie na poziomie cache systemu operacyjnego Windows natywne API: CreateFile() open()/creat() – zaimplementowane w bibliotece standaardowej na bazie natywnego API, tablica deskryptorów zarządzana przez bibliotekę odwzorowanie 1:1 pomiędzy emulowanymi funkcjami API POSIXa a wywoływaniami systemowymi Win32 API większa kontrola nad uprawnieniami i blokowaniem plików za pomocą API Win32 emulacja API POSIX dla zgodności z biblioteką standardową C89 warstwy API – buforowane I/O Biblioteka standardowa C89 na bazie niskopoziomowego API zgodnego z POSIX buduje podsystem buforowanego wejścia/wyjścia oparty na strukturze FILE buforowanie ma na celu uniknięcia (kosztownych) wywołań systemowych dla każdej podstawowej operacji I/O jednorazowo wczytywane/zapisywane są większe porcje danych odczyt/zapis pojedynczych znaków nie wiąże się z częstymi wywołaniami systemowymi – zysk wydajności Struktura FILE zawiera w sobie informacje (book keeping) na temat właściwości otwartego deskryptora pliku Możliwe jest uzyskanie deskryptora pliku z e struktury FILE (funkcja fileno()), jednakże pozycja „w deskryptorze” jest inna niż w strumieniu FILE* otwieranie pliku istnieje szereg sposobów na otwarcie deskryptora pliku, w zależności od rodzaju pliku przykład (Unix): open() – otwarcie dowolnego pliku creat() – utworzenie dowolnego pliku socket() – utworzenie gniazda sieciowego accept() – utworzenie gniazda poprzez zaakceptowanie połączenia przychodzącego socketpair() – utworzenie pary połączonych gniazd pipe() – utworzenie pary połączonych gniazd Unixowych (potoku) opendir() – otwarcie deskryptora identyfikującego katalog porównanie operacji - odczyt operacja typ charakterystyka open()/creat() int (file descriptor) natywne API posix, niebuforowane, niskopoziomowe I/O CreateFile() HANDLE natywne API Win32, niebuforowane, niskopoziomowe I/O fopen(), _wfopen(), freopen() FILE* buforowane API strumieniowe dostarczane przez bibliotekę std C w oparciu o API niskopoziomowe fdopen() FILE* utworzenie buforowanego I/O w oparciu o otwarty deskryptor pliku porównanie operacji – zamykanie pliku operacja typ charakterystyka close() int (file descriptor) natywne API posix CloseHandle() HANDLE natywne API Win32, funkcja zamyka dowolny uchwyt (nie tylko pliku) fclose() FILE* buforowane API C stdio, powoduje zapisanie zawartości buforów i zwolnienie deskryptora pliku podstawowe operacje I/O operacja typ charakterystyka read()/write() int (file descriptor) natywne API posix ReadFile()/WriteFile() HANDLE natywne API Win32 ReadFileEx()/ WriteFileEx() HANDLE natywne API Win32 – umożliwiają asynchroniczny odczyt/zapis plików (w tle) fread(),fwrite() FILE* buforowane API C stdio fflush() FILE* buforowane API C stdio – zapis buforów pliku na dysk (synchronizacja) operacje I/O na pojedynczych znakach operacja typ charakterystyka getc() FILE* buforowane API C stdio – odczyt następnego znaku ze strumienia putc() FILE* buforowane API C stdio – zapis pojedynczego znaku do strumienia ungetc() FILE* buforowane API C stdio – „cofnięcie” odczytu ostatniego znaku odczyt i zmiana pozycji w pliku operacja typ charakterystyka lseek(), lseek64() int (file descriptor) oczyt lub zmiana pozycji wskaźnika pliku (położenia kursora odczytu/zapisu) fseek()/ftell()/ rewind()/fgetpos()/ fsetpos() FILE* buforowane API C stdio – odczyt (ftell(), fgetpos()) lub zmiana pozycji (fseek(), rewind(), fsetpos()) wskaźnika w buforowanym strumieniu – nie musi wiązać się ze zmianą pozycji wskaźnika pliku! GetFilePointer()/ SetFilePointer()/ SetFilePointerEx() HANDLE odpowienik lseek64() w natywnym API Win32 sprawdzenia/ustawienie końca pliku operacja typ charakterystyka feof() FILE* sprawdzenie czy pozycja strumienia jest na końcu pliku ftruncate() int (file descriptor) ucięcie pliku na określonej pozycji SetEndOfFile() HANDLE ustawienie końca pliku w aktualnej pozycji (Win32) przeglądanie katalogów - POSIX operacja typ charakterystyka opendir(), fdopendir() DIR* otwarcie katalogu o określonej ścieżce/deskryptorze pliku readdir() DIR* odczyt rekordu w katalogu seekdir() DIR* przewijanie do określonego rekordu w katalogu scandir() DIR* wyszukiwanie rekordu spełniającego określony warunek closedir() DIR* zamknięcie katalogu przeglądanie katalogów – Win32 operacja typ charakterystyka FindFirstFile() HANDLE rozpoczyna enumeracje plików spełniających określony warunek (np. znajdujących się w określonym katalogu) FindNextFile() HANDLE kolejne iteracje wyszukiwania FindClose() HANDLE zakończenie wyszukiwania przeglądanie katalogów przykłady Windows POSIX tworzenie i usuwanie katalogów i plików operacja typ charakterystyka CreateDirectory()/ CreateDirectoryEx() tworzy katalog o podanej ścieżce (Win32) RemoveDirectory() usuwa istniejący (pusty) katalog DeleteFile() usuwa plik mkdir() tworzy katalog o podanej ścieżce (POSIX) rmdir() usuwa istniejący (pusty) katalog unlink() usuwa nazwę z systemu plików, plik zostanie usunięty po zamknięciu ostatniego aktywnego deskryptora odnoszącego się do niego remove() usuwa plik lub katalog – woła unlink() dla plików, rmdir() dla katalogów tworzenie plików tymczasowych operacja typ charakterystyka GetTempPath() pobiera ścieżkę katalogu tymczasowego, do którego proces ma prawo zapisu GetTempFileName() generuje nazwę pliku tymczasowego i opcjonalnie tworzy plik o unikatowej nazwie mkdtemp() tworzy unikatowy katalog tymczasowy, do którego proces ma prawo zapisu mktemp() generuje unikatową nazwę pliku tymczasowego (nie używać! – wyścigi) mkstemp() int tworzy plik tymczasowy o unikatowej nazwie i zwraca jego deskryptor tmpfile() FILE* tworzy plik tymczasowy o unikatowej nazwie i zwraca otwarty FILE* do niego operacje na plikach przenośnie Język C – używaj API POSIX dla niebuforowanego I/O, stdio dla buforowanego C++ - używaj biblioteki Boost.Filesystem Java, C# - zawierają warstwę abstrakcji dla OS zarządzanie pamięcią proces podczas tworzenia otrzymuje od OS pewien obszar „pamięci wolnej”, który potocznie nazywany jest „areną” arena zarządzana jest przez alokator pamięci procesu, który jest implementowany przez dany język programowania/bibliotekę standardową alokator procesu jest używany do przydzielania pamięci na: stosy wątków procesu obiekty i struktury tworzone w pamięci wolnej („na stercie”) rozmiar areny może być powiększany wraz z rosnącymi potrzebami procesu odbywa się to w sposób niewidoczny dla użytkownika – alokator procesu sam wywołuje funkcje systemu operacyjnego służące do powiększania areny alokator procesu Interfejs alokatora procesu dostarczany przez bibliotekę standardową języka C składa się z: malloc() – alokuje ciągły region pamięci o zadanym rozmiarze ze sterty calloc() – alokuje ciągły obszar pamięci dla N-elementowego wektora obiektów o zadanym rozmiarze realloc() – zmienia rozmiar przydzielonego wcześniej bloku pamięci, być może przenosząc go w inne miejsce free() – zwalnia pamięć zalokowaną przez malloc(), calloc() lub realloc() Na bazie powyższego API tworzone są bardziej zaawansowane mechanizmy alokacji pamięci – odśmiecacze, alokator C++ (new/delete) działanie alokatora procesu Pamięć zalokowana przy pomocy alokatora procesu pozostaje zajęta do momentu wywołania free() Pamięć nie zwolniona stanowi „wyciek” - jest tracona do momentu zakończenia procesu Alokacja wielu małych bloków prowadzi do fragmentacji pamięci, która obniża wydajność alokatora wraz z działaniem programu zegary Zegary są niezbędną usługą systemu dla programów, które przeprowadzają pewne operacje okresowo, np. służących do przetwarzania multimediów online, wykonujących pomiary itp. Programowalne zegary umożliwiają uruchamianie operacji po określonym czasie lub z zadaną częstotliwością W systemach POSIX zegary implementuje się za pomocą sygnałów deskryptorów plików Na Windows zegary implementuje się za pomocą obiektów synchronizacji funkcji callback komunikatów okien zegary - POSIX System POSIX umożliwia stworzenie „budzików” interwałowych w oparciu o kilka zegarów zegar czasu rzeczywistego – jego odczyt może się zmienić w trakcie działania programu poprzez przestawienie aktualnej godziny zegar monotoniczny – odczyt nigdy się nie zmienia – zalecany do tworzenia aplikacji działających synchronicznie zegar czasu procesora procesu – mierzy tylko czas, który OS spędził wykonując aktualny proces zegar czasu procesora wątku – mierzy tylko czas, który OS spędził w aktualnym wątku zegary POSIX - sygnały Tworzymy „budzik” za pomocą funkcji timer_create() podając rodzaj zegara i wypełniając strukturę sigevent określającą identyfikator sygnału, który zostanie uruchomiony Instalujemy w procesie procedurę obsługi w/w sygnału Nastawiamy „budzik” jako pojedynczy lub okresowy za pomocą timer_settime() „Budzik” spowoduje wyemitowanie sygnału i w efekcie uruchomienie procedury jego obsługi Niepotrzeby „budzik” zwalniamy przez wywołanie timer_delete() zegary POSIX - pliki Tworzymy „budzik” za pomocą funkcji timerfd_create() podając rodzaj zegara, funkcja zwraca deskryptor pliku reprezentujący zegar Nastawiamy „budzik” jako pojedynczy lub okresowy za pomocą timerfd_settime() Używamy deskryptora pliku w pętli I/O w wywołaniu funkcji do multipleksacji I/O – select() lub poll() „Budzik” spowoduje obudzenie select() lub poll() i zwrócenie deskryptora zegara jako posiadającego dane do odczytu Niepotrzeby „budzik” zwalniamy przez wywołanie close() „Wszystko jest plikiem”... zegary Windows API Win32 jest niezbyt spójne – niektóre rodzaje zegarów pozwalają tylko określić czas jako relatywny w stosunku do teraz (odporny na przestawienie zegara systemowego), inne tylko jako absolutny zegary Windows – „multimedialny” Ustawiamy „budzik” relatywny w stosunku do teraz za pomocą funkcji timeSetEvent() jako pojedynczy lub okresowy W zależności od parametru budzik może: uruchomić funkcję typu callback (w swoim własnym wątku zarządzanym przez OS) zasygnalizować obiekt zdarzenia (Windows Event) poprzez SetEvent() lub PulseEvent() Jeśli budzik ma sygnalizować zdarzenie, to uruchamiamy funkcję oczekującą na zdarzenie np. WaitForMultipleObjects() Kiedy budzik już nie jest potrzebny wyłączamy go za pomocą timeKillEvent() zegary Windows - nazwane Tworzymy budzik za pomocą CreateWaitableTimer(), opcjonalnie podając jego nazwę Nastawiamy budzik za pomocą SetWaitableTimer(), podając czas jako absolutny lub relatywny, okres kolejnych wywołań i opcjonalną funkcję callback Po upłynięciu czasu uchwyt zegara (HANDLE) stanie się zasygnalizowany i zostanie uruchomiona opcjonalna funkcja callback Na zegar możemy czekać przy pomocy funkcji WaitForMultipleObjects() Niepotrzebny zegar niszczymy za pomocą CloseHandle() zegary Windows - okna Zegar możemy powiązać z oknem UI Windows, wówczas po upływie określonego czasu zostanie wysłany przez OS komunikat okna WM_TIMER Ustawiamy budzik za pomocą funkcji SetTimer() podając uchwyt okna, do którego ma zostać wysłany komunikat oraz identyfikator komunikatu zegara Po upływie określonego czasu (tylko relatywnego) wysyłany jest komunikat okna WM_TIMER wraz z identyfikatorem zegara Niepotrzebny zegar niszczymy funkcją KillTimer() Przydatne rozwiązanie dla tworzenia animacji w obiektach UI